【unity小技巧】FPS游戏后坐力制作思路

参考原视频链接 :https://www.bilibili.com/video/BV1j44y1S7fX/
注意:本文为学习笔记记录,推荐支持原作者,去看原视频自己手敲代码理解更加深入
免责声明:向宇的博客免责声明

文章目录

  • 前言
  • 不加后座力效果
  • 简单添加后座力
  • 限制后座力
  • 子弹落点控制
    • 1. 如果我们想要一个`七字型`的弹道,那就先让前四发子弹往右,后面有十发子弹往左,最后的区间就五五开
    • 2. 如果要T形的话可以直接修改配置即可
  • 镜像弹道
  • 添加散射
  • 完整代码
  • 感谢
  • 完结

前言

在第一人称射击(FPS)游戏中,控制枪械的后坐力是为了增加游戏的真实性和挑战性。开枪后的后坐力通常会导致玩家的视角和枪口偏移,影响接下来的射击精准度。以下是一些常见的方法来模拟和控制后坐力:

  1. 视角偏移:
    当玩家开枪时,可以通过编程让玩家的视角向上或者侧向偏移,模拟因后坐力导致的枪口跳动。这种偏移通常是瞬间的,并且随后会有一个恢复过程,视角逐渐回到原位。

    实现步骤:

    • 监听射击事件。
    • 在射击时,立即调整玩家的摄像机旋转角度,向上偏移一定的角度(模拟垂直后坐力)。
    • 根据需要,也可以添加水平方向的随机偏移(模拟水平后坐力)。
    • 通过插值函数(如Lerp或Slerp)逐渐将摄像机旋转角度恢复到射击前的状态。
  2. 枪械动画:
    可以为枪械创建后坐力动画,在射击时播放这个动画。动画可以在3D建模软件中预先制作好,也可以通过程序动态生成。

    实现步骤:

    • 设计并制作后坐力动画。
    • 射击时触发动画的播放。
    • 动画结束后,可以让枪械逐渐回到初始位置,或者在多次连续射击中保持一定的偏移量。
  3. 枪械物理模拟:
    使用物理引擎来模拟枪械的后坐力效果。这种方法可以使得后坐力更加自然和可预测。

    实现步骤:

    • 在枪械模型上附加一个物理组件(如刚体)。
    • 射击时,对枪械施加一个向后的力或冲量。
    • 利用物理引擎来处理这个力的效果,使得枪械产生后退和上抬的动作。

结合这些方法,开发者可以创造出符合游戏设计需求的后坐力效果,使得射击体验既具有挑战性又富有乐趣。在实际应用中,通常会根据不同武器的特性来调整后坐力的大小和表现方式,以便玩家可以通过技能和习惯来掌握每种武器的射击特点。

本文主要是探究第一种办法,因为枪械的射击通常我们会选择射线投射的方式实现,要实现后座力效果和第一种最匹配。

不加后座力效果

射击的具体实现可以看我之前的文章:一个通用的FPS枪支不同武器射击控制脚本
在这里插入图片描述

简单添加后座力

我们先做摄像机给摄像机添加一个父类,实现后座力的原理就是控制摄像机这个父类的xy旋转偏移值

public class GunRecoil : MonoBehaviour
{private Vector2 recoil; // 后坐力向量,存储X和Y方向上的偏移量public float addSpeed = 0.1f;// 后坐力增加速度public float subSpeed = 10f;// 后坐力减少速度void Update(){recoil.x = Mathf.MoveTowards(recoil.x, 0, subSpeed * Time.deltaTime);recoil.y = Mathf.MoveTowards(recoil.y, 0, subSpeed * Time.deltaTime);// 将recoil.y应用到物体的X轴旋转角度,将recoil.x应用到物体的Y轴旋转角度transform.localEulerAngles = new Vector3(-recoil.y, recoil.x, 0);//测试if (Input.GetKey(KeyCode.Mouse0)){AddRecoil();}}// 添加后坐力public void AddRecoil(){recoil.x += Random.Range(-1, 1) * addSpeed;recoil.y += addSpeed;}
}

效果
在这里插入图片描述

限制后座力

我们想要更加精准,可以修改addSpeed和subSpeed为Vector2值的,当然我们不希望后座力导致视角一直往上抬,需要限制一个值

private Vector2 recoil; // 后坐力向量,存储X和Y方向上的偏移量public Vector2 addSpeed = new Vector2(0.5f, 0.75f); // 后坐力增加速度
public Vector2 subSpeed = new Vector2(3f, 5f); // 后坐力减少速度
public Vector2 maxRecoil = new Vector2(1, 5); // 后坐力的最大值void Update()
{// 使用Mathf.MoveTowards方法逐渐将recoil.x和recoil.y减少到0// subSpeed.x和subSpeed.y分别表示在每秒内减少的量,乘以Time.deltaTime可以使速度与帧率无关recoil.x = Mathf.MoveTowards(recoil.x, 0, subSpeed.x * Time.deltaTime);recoil.y = Mathf.MoveTowards(recoil.y, 0, subSpeed.y * Time.deltaTime);// 将recoil.y应用到物体的X轴旋转角度,将recoil.x应用到物体的Y轴旋转角度transform.localEulerAngles = new Vector3(-recoil.y, recoil.x, 0);//测试if (Input.GetKey(KeyCode.Mouse0)){AddRecoil();}
}// 添加后坐力
public void AddRecoil()
{// // 通过随机取值范围[-1, 1]来增加recoil.x的值,并限制在-maxRecoil.x和maxRecoil.x之间recoil.x = Mathf.Clamp(recoil.x + Random.Range(-1, 1) * addSpeed.x, -maxRecoil.x, maxRecoil.x);// // 增加recoil.y的值,并限制在0和maxRecoil.y之间recoil.y = Mathf.Clamp(recoil.y + addSpeed.y, 0, maxRecoil.y);
}

效果
在这里插入图片描述

子弹落点控制

修改GunRecoil

public static GunRecoil Instance;
private void Awake() {Instance = this;
}public void AddRecoil(int xDir)
{// // 通过随机取值范围[-1, 1]来增加recoil.x的值,并限制在-maxRecoil.x和maxRecoil.x之间recoil.x = Mathf.Clamp(recoil.x + xDir * addSpeed.x, -maxRecoil.x, maxRecoil.x);// // 增加recoil.y的值,并限制在0和maxRecoil.y之间,纵向后座力一般只有往上的recoil.y = Mathf.Clamp(recoil.y + addSpeed.y, 0, maxRecoil.y);
}//。。。[System.Serializable]
public struct AmmoPosData
{   public Data[] datas; // 子弹数量对应的数据// 根据子弹数量获取子弹偏移方向public int GetDir(int ammoCount){int maxId = datas.Length - 1;int nextId = 0;Data dt;// 在数据列表中查找匹配的数据do{dt = datas[nextId];nextId++;} while (nextId <= maxId && ammoCount > dt.ammo);// 根据随机数确定偏移方向float random = Random.Range(0, 1f);if (random < dt.left){return -1; // 左偏移}else if (random < dt.left + dt.right){return 1; // 右偏移}return 0; // 不偏移}[System.Serializable]public struct Data{public int ammo; // 子弹数量public float left; // 左偏移概率public float right; // 右偏移概率}
}

修改GunSystem调用,及发射子弹脚本

private int shootAmmo;//射击第几颗子弹
public AmmoPosData ammoData;private void Update(){// 射击if (readyToShoot && shooting && !reloading && bulletsLeft > 0){bulletsShot = bulletsPerTap;Shoot();//射击脚本}if (!shooting) shootAmmo = 0;
}private void Shoot()
{shootAmmo++;GunRecoil.Instance.AddRecoil(ammoData.GetDir(shootAmmo));//。。。
}

1. 如果我们想要一个七字型的弹道,那就先让前四发子弹往右,后面有十发子弹往左,最后的区间就五五开

在这里插入图片描述
效果
在这里插入图片描述

2. 如果要T形的话可以直接修改配置即可

在这里插入图片描述
效果

在这里插入图片描述

镜像弹道

修改GunRecoil

//。。。[System.Serializable]
public struct AmmoPosData
{public float mirrorRate;//镜像概率public Data[] datas; // 子弹数量对应的数据// 根据子弹数量获取子弹偏移方向,和是否镜像public int GetDir(int ammoCount, bool mirror){int maxId = datas.Length - 1;int nextId = 0;Data dt;// 在数据列表中查找匹配的数据do{dt = datas[nextId];nextId++;} while (nextId <= maxId && ammoCount > dt.ammo);// 根据随机数确定偏移方向float random = Random.Range(0, 1f);if (random < dt.left){return mirror ? 1 : -1; // 左偏移}else if (random < dt.left + dt.right){return mirror ? -1 : 1; // 右偏移}return 0; // 不偏移}//按概率判断是否镜像public bool GetMirror(){if(Random.Range(0, 1f) < mirrorRate)return true;return false;}[System.Serializable]public struct Data{public int ammo; // 子弹数量public float left; // 左偏移概率public float right; // 右偏移概率}
}

修改GunSystem调用,及发射子弹脚本

bool isMirror;private void Update(){// 射击if (readyToShoot && shooting && !reloading && bulletsLeft > 0){bulletsShot = bulletsPerTap;Shoot();//射击脚本}if (!shooting) shootAmmo = 0;
}private void Shoot()
{shootAmmo++;//一般在第一发子弹决定弹道的镜像if(shootAmmo == 1)isMirror = ammoData.GetMirror();GunRecoil.Instance.AddRecoil(ammoData.GetDir(shootAmmo, isMirror));//。。。
}

配置镜像概率为0.5
在这里插入图片描述

测试可以看到弹道有一半的概率变成镜像
在这里插入图片描述

添加散射

现在的弹道太规律了,子弹是指哪里就打哪里,然而实际游戏中子弹可能会根据落点区间的大小基于瞄准射线进行一定的偏移,准星的大小就侧面体现了落点区间的大小
在这里插入图片描述
其实就是定义射击时的散布度

[Tooltip("射击时的散布度")]
public float spread;private void Shoot()
{shootAmmo++;//一般在第一发子弹决定弹道的镜像if(shootAmmo == 1)isMirror = ammoData.GetMirror();GunRecoil.Instance.AddRecoil(ammoData.GetDir(shootAmmo, isMirror));// 散布float x = Random.Range(-spread, spread);float y = Random.Range(-spread, spread);// 计算带有散布的射击方向Vector3 direction = fpsCam.transform.forward + new Vector3(x, y, 0);// 射线检测if (Physics.Raycast(fpsCam.transform.position, direction, out RaycastHit rayHit, range)){if (rayHit.collider.CompareTag("Wall")){Debug.Log("击中墙壁");// 击中敌人特效var res = Instantiate(hitSpecialEffectsWall, rayHit.point, Quaternion.Euler(0, 90, 0));res.transform.parent = rayHit.transform;//设置父类}}
}

完整代码

//后座力
public class GunRecoil : MonoBehaviour
{public static GunRecoil Instance;private void Awake(){Instance = this;}private Vector2 recoil; // 后坐力向量,存储X和Y方向上的偏移量public Vector2 addSpeed = new Vector2(0.5f, 0.75f); // 后坐力增加速度public Vector2 subSpeed = new Vector2(3f, 5f); // 后坐力减少速度public Vector2 maxRecoil = new Vector2(1, 5); // 后坐力的最大值void Update(){// 使用Mathf.MoveTowards方法逐渐将recoil.x和recoil.y减少到0// subSpeed.x和subSpeed.y分别表示在每秒内减少的量,乘以Time.deltaTime可以使速度与帧率无关recoil.x = Mathf.MoveTowards(recoil.x, 0, subSpeed.x * Time.deltaTime);recoil.y = Mathf.MoveTowards(recoil.y, 0, subSpeed.y * Time.deltaTime);// 将recoil.y应用到物体的X轴旋转角度,将recoil.x应用到物体的Y轴旋转角度transform.localEulerAngles = new Vector3(-recoil.y, recoil.x, 0);}// 添加后坐力public void AddRecoil(int xDir){// // 通过随机取值范围[-1, 1]来增加recoil.x的值,并限制在-maxRecoil.x和maxRecoil.x之间recoil.x = Mathf.Clamp(recoil.x + xDir * addSpeed.x, -maxRecoil.x, maxRecoil.x);// // 增加recoil.y的值,并限制在0和maxRecoil.y之间,纵向后座力一般只有往上的recoil.y = Mathf.Clamp(recoil.y + addSpeed.y, 0, maxRecoil.y);}
}[System.Serializable]
public struct AmmoPosData
{public float mirrorRate;//镜像概率public Data[] datas; // 子弹数量对应的数据// 根据子弹数量获取子弹偏移方向,和是否镜像public int GetDir(int ammoCount, bool mirror){int maxId = datas.Length - 1;int nextId = 0;Data dt;// 在数据列表中查找匹配的数据do{dt = datas[nextId];nextId++;} while (nextId <= maxId && ammoCount > dt.ammo);// 根据随机数确定偏移方向float random = Random.Range(0, 1f);if (random < dt.left){return mirror ? 1 : -1; // 左偏移}else if (random < dt.left + dt.right){return mirror ? -1 : 1; // 右偏移}return 0; // 不偏移}//按概率判断是否镜像public bool GetMirror(){if(Random.Range(0, 1f) < mirrorRate)return true;return false;}[System.Serializable]public struct Data{public int ammo; // 子弹数量public float left; // 左偏移概率public float right; // 右偏移概率}
}

感谢

【视频】https://www.bilibili.com/video/BV1j44y1S7fX/

完结

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,以便我第一时间收到反馈,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

好了,我是向宇,https://xiangyu.blog.csdn.net

一位在小公司默默奋斗的开发者,出于兴趣爱好,于是最近才开始自习unity。如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我可能也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/216602.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何在Cloudflare创建自己的反向代理

大家在使用Cloudflare做反向代理的时候会遇到一个问题&#xff0c;命名已经配置好了&#xff0c;但是还是访问不了&#xff0c;是因为Cloudflare的workers.dev域名在中国大陆区域已经被污染无法访问&#xff0c;所以需要自有域名进行解析。 本文的主要内容有以下三部分 1、域…

Linux系统编程:高级IO总结

非阻塞IO基本概念 高级IO核心就一个概念&#xff1a;非阻塞IO。 与该概念相对的&#xff0c;就是我们之前学习过的阻塞IO。 非阻塞IO&#xff08;Non-blocking I/O&#xff09;是一种IO模型&#xff0c;用于实现异步IO操作&#xff0c;使应用程序能够在等待IO操作完成的同时…

洛谷 P8802 [蓝桥杯 2022 国 B] 出差

文章目录 [蓝桥杯 2022 国 B] 出差题目链接题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 提示 思路解析CODE [蓝桥杯 2022 国 B] 出差 题目链接 https://www.luogu.com.cn/problem/P8802 题目描述 A \mathrm{A} A 国有 N N N 个城市&#xff0c;编号为 1 … N …

数据库范式(详细介绍)

目录 第一范式&#xff08;原子性&#xff09; 第二范式&#xff08;主键唯一性&#xff09; 第三范式&#xff08;原子性主键唯一性&#xff09; BC范式(3NFplus) 第一范式&#xff08;原子性&#xff09; 确保每列保证原子性&#xff0c;保证这个属性&#xff08;字段&am…

SpringBoot AOP切面实现对自定义注解的属性动态修改

文章目录 需求问题解决方案示例代码 需求 项目中共用了一个redis&#xff0c;而项目中部分代码使用了JetCache的Cached注解。所以需要给Cached动态配置area属性值&#xff0c;用来区分dev和test环境。 问题 自定义注解的属性值需要常量值&#xff0c;即static final修饰&…

学习-面试java基础-(集合)

String 为什么不可变&#xff1f; 1线程安全 2支持hash映射和缓存。因为String的hash值经常会使用到&#xff0c;比如作为 Map 的键&#xff0c;不可变的特性使得 hash 值也不会变&#xff0c;不需要重新计算。 3出于安全考虑。网络地址URL、文件路径path、密码通常情况下都是以…

Python之Requests库使用总结

概述 Requests是python中一个很Pythonic的HTTP库&#xff0c;用于构建HTTP请求与解析响应 Requests开发哲学 Beautiful is better than ugly.(美丽优于丑陋) Explicit is better than implicit.(直白优于含蓄) Simple is better than complex.(简单优于复杂) Complex is bett…

回顾【数学基础】找出断层,继续前进, 使用chatGPT学习并解决实际问题:微积分

已经学过的算术、代数、几何。跳过。 从微积分开始 想象一下&#xff0c;你在画一条曲线&#xff0c;或者在一个大草地上奔跑。微积分就是一种数学工具&#xff0c;帮助我们了解这条曲线的形状&#xff0c;或者你奔跑的方式。 微分&#xff08;就像研究曲线上的每一小点&…

FFmpeg的AVIOPROBE

文章目录 定义 可能你一直有疑问&#xff0c;ffmpeg的avformat是怎么提前知道码流是编码格式或者容器&#xff1f;恭喜你&#xff0c;看到这里&#xff0c;你找到答案了&#xff0c;在这里&#xff0c;ffmpeg通过这些probe函数来提前获取码流的编码格式。 看到下面的avs2_prob…

C++1114新标准——统一初始化(Uniform Initialization)、Initializer_list(初始化列表)

系列文章目录 C11&14新标准——Variadic templates&#xff08;数量不定的模板参数&#xff09; C11&14新标准——Uniform Initialization&#xff08;统一初始化&#xff09;、Initializer_list&#xff08;初始化列表&#xff09; 文章目录 系列文章目录1. 定义2. I…

装饰者模式(Decorator Pattern)

1 什么是装饰者模式&#xff1f; 1.1 Head First Design Pattern 定义 装饰者模式动态地将责任附加到对象上。若要扩展功能&#xff0c;装饰者提供了比继承更有弹性的替代方案。 1.2 大佬博客 设计模式是什么鬼&#xff08;装饰&#xff09; 2 装饰者模式 2.1 基本介绍 …

Goby 漏洞发布| 亿赛通电子文档安全管理系统 LinkFilterService 接口权限绕过漏洞

漏洞名称&#xff1a;亿赛通电子文档安全管理系统 LinkFilterService 接口权限绕过漏洞 English Name&#xff1a;Esafenet Electronic Document Security Management System LinkFilterService API Permission Bypass Vulnerability CVSS core: 9.3 影响资产数&#xff1a;…

MySQL BinLog 数据还原恢复

博文目录 文章目录 查看状态查看 binlog 开关及存储路径查看 binlog 配置 如 存储格式 binlog_format查看当前还存在的日志查看当前正在使用的日志 切换日志确定日志确定日志文件日志格式改写日志简要说明确定日志位置以事件为单位查看日志分析日志 还原数据 查看状态 查看 b…

智能优化算法应用:基于花授粉算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于花授粉算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于花授粉算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.花授粉算法4.实验参数设定5.算法结果6.参考文…

设计模式(2)--对象创建(1)--抽象工厂

1. 意图 提供一个创建一系列相关或相互依赖对象的接口&#xff0c;而无需指定它们具体的类。 2. 四种角色 抽象产品(Product)、具体产品(Concrete Product)、抽象工厂(Abstract Factory)、具体工厂(Concrete Factory)。 3. 优点 3.1 分离了具体的类。Client只需使用抽象工厂类…

解析代理IP在跨境电商和社媒营销中的关键作用

跨境电商和社媒营销领域的从业者深知&#xff0c;代理IP的价值愈发凸显。在推广营销的过程中&#xff0c;频繁遇到因IP关联而封禁账号的情况&#xff0c;或因使用不安全IP而导致异常问题。 这些问题促使人们开始高度重视代理IP的作用。但实际上&#xff0c;代理IP究竟是何物&a…

(数据结构)单链表的定义

#include<stdio.h> typedef struct LNode {int data;struct LNode* next; }LNode,*LinkList; //LNode为结构体类型&#xff0c;LinkList为指向单链表的指针 //初始化一个空的单链表 void InitList(LinkList L) {L NULL; //空表&#xff0c;暂时没有任何节点 } //判断单…

SCUM私人服务器搭建部署教程

以下是搭建SCUM私服的步骤&#xff1a; 1. 下载并安装SteamCMD。SteamCMD是一个命令行工具&#xff0c;用于从Steam下载和更新游戏服务器。你可以从Steam官网下载并安装它。 2. 创建一个文件夹来存储服务器文件。在你的计算机上创建一个文件夹&#xff0c;用于存储SCUM服务器文…

面试拼多多前端开发岗,已拿到offer,这些知识点该放出来了

一面&#xff1a; CSS 1.盒模型 2.css文件中开头加*号/上下盒子重叠问题&#xff08;为正值如何/负值如何&#xff09; 3.伪类和伪元素的了解&#xff0c;伪类有什么作用 JS 4.原型链 5.继承 6.为什么3.tostring()会报错 7.var先使用会怎样 8.函数表达式和函数声明有…

MistralAI发布全球首个MoE大模型-Mixtral 8x7B,创新超越GPT-4

引言 MistralAI&#xff0c;一家法国的初创企业&#xff0c;近期在AI界引发了轰动&#xff0c;刚刚发布了全球首个基于MoE&#xff08;Mixture of Experts&#xff0c;混合专家&#xff09;技术的大型语言模型——Mistral-8x7B-MoE。这一里程碑事件标志着AI技术的一个重要突破…