【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

相关文章

Java使用ANTLR4对Lua脚本语法校验详解

《Java使用ANTLR4对Lua脚本语法校验详解》ANTLR是一个强大的解析器生成器,用于读取、处理、执行或翻译结构化文本或二进制文件,下面就跟随小编一起看看Java如何使用ANTLR4对Lua脚本... 目录什么是ANTLR?第一个例子ANTLR4 的工作流程Lua脚本语法校验准备一个Lua Gramm

Python列表去重的4种核心方法与实战指南详解

《Python列表去重的4种核心方法与实战指南详解》在Python开发中,处理列表数据时经常需要去除重复元素,本文将详细介绍4种最实用的列表去重方法,有需要的小伙伴可以根据自己的需要进行选择... 目录方法1:集合(set)去重法(最快速)方法2:顺序遍历法(保持顺序)方法3:副本删除法(原地修改)方法4:

在Spring Boot中浅尝内存泄漏的实战记录

《在SpringBoot中浅尝内存泄漏的实战记录》本文给大家分享在SpringBoot中浅尝内存泄漏的实战记录,结合实例代码给大家介绍的非常详细,感兴趣的朋友一起看看吧... 目录使用静态集合持有对象引用,阻止GC回收关键点:可执行代码:验证:1,运行程序(启动时添加JVM参数限制堆大小):2,访问 htt

Spring Security基于数据库的ABAC属性权限模型实战开发教程

《SpringSecurity基于数据库的ABAC属性权限模型实战开发教程》:本文主要介绍SpringSecurity基于数据库的ABAC属性权限模型实战开发教程,本文给大家介绍的非常详细,对大... 目录1. 前言2. 权限决策依据RBACABAC综合对比3. 数据库表结构说明4. 实战开始5. MyBA

微信公众号脚本-获取热搜自动新建草稿并发布文章

《微信公众号脚本-获取热搜自动新建草稿并发布文章》本来想写一个自动化发布微信公众号的小绿书的脚本,但是微信公众号官网没有小绿书的接口,那就写一个获取热搜微信普通文章的脚本吧,:本文主要介绍微信公众... 目录介绍思路前期准备环境要求获取接口token获取热搜获取热搜数据下载热搜图片给图片加上标题文字上传图片

用js控制视频播放进度基本示例代码

《用js控制视频播放进度基本示例代码》写前端的时候,很多的时候是需要支持要网页视频播放的功能,下面这篇文章主要给大家介绍了关于用js控制视频播放进度的相关资料,文中通过代码介绍的非常详细,需要的朋友可... 目录前言html部分:JavaScript部分:注意:总结前言在javascript中控制视频播放

Spring Boot + MyBatis Plus 高效开发实战从入门到进阶优化(推荐)

《SpringBoot+MyBatisPlus高效开发实战从入门到进阶优化(推荐)》本文将详细介绍SpringBoot+MyBatisPlus的完整开发流程,并深入剖析分页查询、批量操作、动... 目录Spring Boot + MyBATis Plus 高效开发实战:从入门到进阶优化1. MyBatis

MyBatis 动态 SQL 优化之标签的实战与技巧(常见用法)

《MyBatis动态SQL优化之标签的实战与技巧(常见用法)》本文通过详细的示例和实际应用场景,介绍了如何有效利用这些标签来优化MyBatis配置,提升开发效率,确保SQL的高效执行和安全性,感... 目录动态SQL详解一、动态SQL的核心概念1.1 什么是动态SQL?1.2 动态SQL的优点1.3 动态S

Pandas使用SQLite3实战

《Pandas使用SQLite3实战》本文主要介绍了Pandas使用SQLite3实战,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学... 目录1 环境准备2 从 SQLite3VlfrWQzgt 读取数据到 DataFrame基础用法:读

使用Java实现通用树形结构构建工具类

《使用Java实现通用树形结构构建工具类》这篇文章主要为大家详细介绍了如何使用Java实现通用树形结构构建工具类,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录完整代码一、设计思想与核心功能二、核心实现原理1. 数据结构准备阶段2. 循环依赖检测算法3. 树形结构构建4. 搜索子