【unity实战】一个通用的FPS枪支不同武器射击控制脚本

2023-12-11 07:04

本文主要是介绍【unity实战】一个通用的FPS枪支不同武器射击控制脚本,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 前言
  • 模型素材
  • 文章用到的粒子火光特效
  • 射击效果
  • 换弹
  • 瞄准
  • 开枪抖动效果
  • 设置显示文本
  • 最终代码
  • 不同武器射击效果
    • 1. 手枪
    • 2. 机枪
    • 3. 狙击枪
    • 4. 霰弹枪
    • 5. 加特林
  • 其他
  • 感谢
  • 完结

前言

实现FPS枪支不同武器效果,比如手枪,喷子,狙击枪,机枪,其实我最开始的想法是先做一个基类脚本,写一些公共属性和方法,然后再起不同的武器脚本这个基础基类,实现不同的武器效果。

这样的实现思路其实是没什么问题的,直到我看到这个视频:https://www.youtube.com/watch?v=bqNW08Tac0Y,作者只用一个脚本就实现了不同的武器效果更加方便,下面我就参考一下作者的思路实现一下大致的效果。

顺带说一下,在第一人称射击(FPS)游戏中实现子弹射击效果,可以通过不同的技术和方法来完成。以下是几种常见的实现方式:

  1. 射线投射(Raycasting):
    这是最常用的方法之一。射线投射意味着从枪口发出一个虚拟的射线,并检测这个射线与游戏世界中的对象之间的交互。如果射线与某个对象相交,那么就可以认为子弹击中了该对象。

    实现步骤:

    • 从玩家的摄像机或枪口位置发出一条射线。
    • 使用物理引擎提供的射线投射功能来检测射线路径上的碰撞。
    • 如果射线与对象相交,根据交互结果执行相应的逻辑,比如扣除生命值、播放受击动画等。
    • 在射击点显示击中效果,如粒子效果或贴图。
  2. 抛射物模拟(Projectile Simulation):
    对于需要模拟子弹飞行轨迹的情况,比如远距离狙击、火箭筒或者抛射武器,可以使用抛射物模拟。

    实现步骤:

    • 创建一个子弹实体,并赋予它初始速度和方向。
    • 通过物理引擎模拟子弹的飞行轨迹,考虑重力、空气阻力等因素。
    • 检测子弹与其他对象的碰撞,并在碰撞发生时处理相应的逻辑。
    • 在子弹飞行过程中可以添加轨迹效果,如拖尾。

每种方法都有其适用场景和优缺点。射线投射适合快速射击和近距离交火,抛射物模拟适合远距离和弧线射击。在实际开发中,这些方法可以组合使用,以达到最佳的效果。

模型素材

不会配置模型可以看我之前的文章,进行下载和配置:
unity中导入下载的3D模型及albedo/baseColor、normal 、AO/Occlus、metallic、roughness贴图纹理设置

文章用到的粒子火光特效

https://assetstore.unity.com/packages/vfx/particles/legacy-particle-pack-73777
在这里插入图片描述

射击效果

[Tooltip("是否正在射击")]
bool shooting;
[Tooltip("是否允许按住射击")]
public bool allowButtonHold;
[Tooltip("是否可以射击")]
bool readyToShoot;
[Tooltip("是否在换弹")]
bool reloading;
[Tooltip("弹夹容量")]
public int magazineSize;
[Tooltip("当前弹夹容量")]
public int bulletsLeft;
[Tooltip("储备弹药容量")]
public int reservedAmmoCapacity = 300;
[Tooltip("当前剩余射击发射的子弹数")]
public int bulletsShot;
[Tooltip("枪口火焰特效")]
public ParticleSystem muzzleFlash;
[Tooltip("子弹击中效果")]
public GameObject bulletHoleGraphic;
[Tooltip("射击间隔时间")]
public float timeBetweenShooting;
[Tooltip("连发射击之间的间隔时间")]
public float timeBetweenShots;
[Tooltip("射击时的散布度")]
public float spread;
[Tooltip("射击的最大距离")]
public float range;
[Tooltip("每次射击发射的子弹数")]
public int bulletsPerTap;
[Tooltip("是否允许按住射击")]
public bool allowButtonHold;
[Tooltip("每次射击造成的伤害")]
public int damage;  // 伤害public Camera fpsCam;private void Awake()
{bulletsLeft = magazineSize;readyToShoot = true;
}private void Update()
{MyInput();
}private void MyInput()
{if (allowButtonHold)shooting = Input.GetKey(KeyCode.Mouse0);elseshooting = Input.GetKeyDown(KeyCode.Mouse0);// 射击if (readyToShoot && shooting && !reloading && bulletsLeft > 0){bulletsShot = bulletsPerTap;Shoot();}
}private void Shoot()
{readyToShoot = false;// 散布float x = Random.Range(-spread, spread);float y = Random.Range(-spread, spread);// 计算带有散布的射击方向Vector3 direction = fpsCam.transform.forward + new Vector3(x, y, 0);//场景显示红线,方便调试查看Debug.DrawRay(fpsCam.transform.position, direction * range, Color.red);// 射线检测if (Physics.Raycast(fpsCam.transform.position, direction, out RaycastHit rayHit, range)){Debug.Log(rayHit.collider.name);muzzleFlash.Play();//枪口火焰/火光//TODO:相机震动if (rayHit.collider.CompareTag("Enemy")){Debug.Log("击中敌人");Rigidbody rb = rayHit.transform.GetComponent<Rigidbody>();if (rb != null){rb.constraints = RigidbodyConstraints.None; // 解除刚体约束rb.AddForce(transform.parent.transform.forward * 500); // 给敌人施加一个力}// 击中敌人特效var res1 = Instantiate(bulletHoleGraphic, rayHit.point, Quaternion.Euler(0, 180, 0));Destroy(res1, 0.5f);//TODO:扣血}}bulletsLeft--;bulletsShot--;Invoke("ResetShot", timeBetweenShooting);if (bulletsShot > 0 && bulletsLeft > 0)Invoke("Shoot", timeBetweenShots);
}private void ResetShot()
{readyToShoot = true;
}

换弹

private void MyInput()
{//。。。if (Input.GetKeyDown(KeyCode.R) && bulletsLeft < magazineSize && !reloading)Reload();
}//换弹
private void Reload()
{reloading = true;Invoke("ReloadFinished", reloadTime);
}private void ReloadFinished()
{if (reservedAmmoCapacity <= 0) return;//计算需要填装的子弹数=1个弹匣子弹数-当前弹匣子弹数int bullectToLoad = magazineSize - bulletsLeft;//计算备弹需扣除子弹数int bullectToReduce = (reservedAmmoCapacity >= bullectToLoad) ? bullectToLoad : reservedAmmoCapacity;reservedAmmoCapacity -= bullectToReduce;//减少备弹数bulletsLeft += bullectToReduce;//当前子弹数增加bulletsLeft = magazineSize;reloading = false;
}

瞄准

private void MyInput()
{//。。。//瞄准DetermineAim();
}void DetermineAim()
{Vector3 target = normalLocalPosition; // 默认目标位置为正常瞄准时的本地位置if (Input.GetMouseButton(1)) target = aimingLocalPosition; // 如果按下鼠标右键,目标位置为瞄准时的本地位置Vector3 desiredPosition = Vector3.Lerp(transform.localPosition, target, Time.deltaTime * aimSmoothing); // 使用插值平滑过渡到目标位置transform.localPosition = desiredPosition; // 更新枪支的本地位置
}

效果
在这里插入图片描述

开枪抖动效果

如果你的枪模型没有开枪动画的话,这个方法就很方便了

private void Shoot()
{transform.localPosition -= Vector3.forward * 0.1f; // 后坐力使枪支向后移动//。。。
}

设置显示文本

private void Update()
{//。。。SetUI();
}// 设置文本
private void SetUI()
{text.SetText(bulletsLeft + " / " + reservedAmmoCapacity);
}

最终代码

public class GunSystem : MonoBehaviour
{public Camera fpsCam;[Header("枪械状态")][Tooltip("是否正在射击")]bool shooting;[Tooltip("是否可以射击")]bool readyToShoot;[Tooltip("是否在换弹")]bool reloading;[Header("弹夹")][Tooltip("弹夹容量")]public int magazineSize;[Tooltip("当前弹夹容量")]public int bulletsLeft;[Tooltip("储备弹药容量")]public int reservedAmmoCapacity = 300;[Tooltip("当前剩余射击发射的子弹数")]public int bulletsShot;[Header("射击")][Tooltip("射击间隔时间")]public float timeBetweenShooting;[Tooltip("射击时的散布度")]public float spread;[Tooltip("射击的最大距离")]public float range;[Tooltip("每次射击发射的子弹数")]public int bulletsPerTap;[Tooltip("是否允许按住射击")]public bool allowButtonHold;[Tooltip("每次射击造成的伤害")]public int damage;  // 伤害[Tooltip("装填弹药的时间")]public float reloadTime;[Tooltip("连发射击之间的间隔时间")]public float timeBetweenShots;[Header("瞄准")][Tooltip("正常情况的本地位置")]public Vector3 normalLocalPosition;[Tooltip("瞄准时的本地位置")]public Vector3 aimingLocalPosition;[Tooltip("瞄准过程的平滑度")]public float aimSmoothing = 10;[Header("效果")][Tooltip("枪口火焰特效")]public ParticleSystem muzzleFlash;[Tooltip("子弹击中效果")]public GameObject bulletHoleGraphic;[Header("UI")]public TextMeshProUGUI text;  // 弹药显示文本private void Awake(){bulletsLeft = magazineSize;readyToShoot = true;}private void Update(){MyInput();SetUI();}// 设置文本private void SetUI(){text.SetText(bulletsLeft + " / " + reservedAmmoCapacity);}private void MyInput(){if (allowButtonHold)shooting = Input.GetKey(KeyCode.Mouse0);elseshooting = Input.GetKeyDown(KeyCode.Mouse0);// 射击if (readyToShoot && shooting && !reloading && bulletsLeft > 0){bulletsShot = bulletsPerTap;Shoot();}//换弹if (Input.GetKeyDown(KeyCode.R) && bulletsLeft < magazineSize && !reloading)Reload();//瞄准DetermineAim();}private void Shoot(){readyToShoot = false;transform.localPosition -= Vector3.forward * 0.1f; // 后坐力使枪支向后移动// 散布float x = Random.Range(-spread, spread);float y = Random.Range(-spread, spread);// 计算带有散布的射击方向Vector3 direction = fpsCam.transform.forward + new Vector3(x, y, 0);//场景显示红线,方便调试查看Debug.DrawRay(fpsCam.transform.position, direction * range, Color.red);// 射线检测if (Physics.Raycast(fpsCam.transform.position, direction, out RaycastHit rayHit, range)){Debug.Log(rayHit.collider.name);muzzleFlash.Play();//枪口火焰/火光//相机震动if (rayHit.collider.CompareTag("Enemy")){Debug.Log("击中敌人");// Rigidbody rb = rayHit.transform.GetComponent<Rigidbody>();// if (rb != null)// {//     rb.constraints = RigidbodyConstraints.None; // 解除刚体约束//     rb.AddForce(transform.parent.transform.forward * 500); // 给敌人施加一个力// }// 击中敌人特效var res = Instantiate(bulletHoleGraphic, rayHit.point, Quaternion.Euler(0, 180, 0));res.transform.parent = rayHit.transform;//设置父类//TODO:扣血}}bulletsLeft--;bulletsShot--;Invoke("ResetShot", timeBetweenShooting);if (bulletsShot > 0 && bulletsLeft > 0)Invoke("Shoot", timeBetweenShots);}void DetermineAim(){Vector3 target = normalLocalPosition; // 默认目标位置为正常瞄准时的本地位置if (Input.GetMouseButton(1)) target = aimingLocalPosition; // 如果按下鼠标右键,目标位置为瞄准时的本地位置Vector3 desiredPosition = Vector3.Lerp(transform.localPosition, target, Time.deltaTime * aimSmoothing); // 使用插值平滑过渡到目标位置transform.localPosition = desiredPosition; // 更新枪支的本地位置}private void ResetShot(){readyToShoot = true;}//换弹private void Reload(){reloading = true;Invoke("ReloadFinished", reloadTime);}private void ReloadFinished(){if (reservedAmmoCapacity <= 0) return;//计算需要填装的子弹数=1个弹匣子弹数-当前弹匣子弹数int bullectToLoad = magazineSize - bulletsLeft;//计算备弹需扣除子弹数int bullectToReduce = (reservedAmmoCapacity >= bullectToLoad) ? bullectToLoad : reservedAmmoCapacity;reservedAmmoCapacity -= bullectToReduce;//减少备弹数bulletsLeft += bullectToReduce;//当前子弹数增加bulletsLeft = magazineSize;reloading = false;}
}

不同武器射击效果

注意:这里为了方便,我就用一把枪做演示了

1. 手枪

参数配置
在这里插入图片描述
效果
在这里插入图片描述

2. 机枪

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

3. 狙击枪

参数,狙击枪其实和手枪参数差不多,可以就需要修改射击间隔时间、换弹时间和伤害
在这里插入图片描述
效果
在这里插入图片描述

4. 霰弹枪

参数
在这里插入图片描述

效果
在这里插入图片描述

5. 加特林

参数
在这里插入图片描述

效果
在这里插入图片描述

其他

可以看到其实还有很多功能没有实现,比如后座力或者放大镜等等效果,这篇文章说的已经够多了,后面我再单独做其他内容的探究吧!

感谢

【视频】https://www.youtube.com/watch?v=bqNW08Tac0Y

完结

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

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

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

这篇关于【unity实战】一个通用的FPS枪支不同武器射击控制脚本的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/479859

相关文章

Python远程控制MySQL的完整指南

《Python远程控制MySQL的完整指南》MySQL是最流行的关系型数据库之一,Python通过多种方式可以与MySQL进行交互,下面小编就为大家详细介绍一下Python操作MySQL的常用方法和最... 目录1. 准备工作2. 连接mysql数据库使用mysql-connector使用PyMySQL3.

如何搭建并配置HTTPD文件服务及访问权限控制

《如何搭建并配置HTTPD文件服务及访问权限控制》:本文主要介绍如何搭建并配置HTTPD文件服务及访问权限控制的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、安装HTTPD服务二、HTTPD服务目录结构三、配置修改四、服务启动五、基于用户访问权限控制六、

Java Spring 中的监听器Listener详解与实战教程

《JavaSpring中的监听器Listener详解与实战教程》Spring提供了多种监听器机制,可以用于监听应用生命周期、会话生命周期和请求处理过程中的事件,:本文主要介绍JavaSprin... 目录一、监听器的作用1.1 应用生命周期管理1.2 会话管理1.3 请求处理监控二、创建监听器2.1 Ser

Apache 高级配置实战之从连接保持到日志分析的完整指南

《Apache高级配置实战之从连接保持到日志分析的完整指南》本文带你从连接保持优化开始,一路走到访问控制和日志管理,最后用AWStats来分析网站数据,对Apache配置日志分析相关知识感兴趣的朋友... 目录Apache 高级配置实战:从连接保持到日志分析的完整指南前言 一、Apache 连接保持 - 性

MQTT SpringBoot整合实战教程

《MQTTSpringBoot整合实战教程》:本文主要介绍MQTTSpringBoot整合实战教程,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考... 目录MQTT-SpringBoot创建简单 SpringBoot 项目导入必须依赖增加MQTT相关配置编写

JavaScript实战:智能密码生成器开发指南

本文通过JavaScript实战开发智能密码生成器,详解如何运用crypto.getRandomValues实现加密级随机密码生成,包含多字符组合、安全强度可视化、易混淆字符排除等企业级功能。学习密码强度检测算法与信息熵计算原理,获取可直接嵌入项目的完整代码,提升Web应用的安全开发能力 目录

Redis迷你版微信抢红包实战

《Redis迷你版微信抢红包实战》本文主要介绍了Redis迷你版微信抢红包实战... 目录1 思路分析1.1hCckRX 流程1.2 注意点①拆红包:二倍均值算法②发红包:list③抢红包&记录:hset2 代码实现2.1 拆红包splitRedPacket2.2 发红包sendRedPacket2.3 抢

Golang实现Redis分布式锁(Lua脚本+可重入+自动续期)

《Golang实现Redis分布式锁(Lua脚本+可重入+自动续期)》本文主要介绍了Golang分布式锁实现,采用Redis+Lua脚本确保原子性,持可重入和自动续期,用于防止超卖及重复下单,具有一定... 目录1 概念应用场景分布式锁必备特性2 思路分析宕机与过期防止误删keyLua保证原子性可重入锁自动

springboot项目redis缓存异常实战案例详解(提供解决方案)

《springboot项目redis缓存异常实战案例详解(提供解决方案)》redis基本上是高并发场景上会用到的一个高性能的key-value数据库,属于nosql类型,一般用作于缓存,一般是结合数据... 目录缓存异常实践案例缓存穿透问题缓存击穿问题(其中也解决了穿透问题)完整代码缓存异常实践案例Red

Spring Boot拦截器Interceptor与过滤器Filter深度解析(区别、实现与实战指南)

《SpringBoot拦截器Interceptor与过滤器Filter深度解析(区别、实现与实战指南)》:本文主要介绍SpringBoot拦截器Interceptor与过滤器Filter深度解析... 目录Spring Boot拦截器(Interceptor)与过滤器(Filter)深度解析:区别、实现与实