【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);
    else
        shooting = 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);
        else
            shooting = 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。如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我可能也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Redis生产实战-Redis集群故障探测以及降级方案设计

Redis 集群故障探测 在生产环境中&#xff0c;如果 Redis 集群崩溃了&#xff0c;那么会导致大量的请求打到数据库中&#xff0c;会导致整个系统都崩溃&#xff0c;所以系统需要可以识别缓存故障&#xff0c;限流保护数据库&#xff0c;并且启动接口的降级机制 降级方案设计 …

贪心算法及相关题目

贪心算法概念 贪心算法是指&#xff0c;在对问题求解时&#xff0c;总是做出在当前看来是最好的选择。也就是说&#xff0c;不从整体最优上加以考虑&#xff0c;算法得到的是在某种意义上的局部最优解 。 贪心算法性质&#xff08;判断是否可以使用贪心算法&#xff09; 1、贪…

【力扣周赛】第 372 场周赛( ⭐查询 离线做法)TODO

文章目录 竞赛链接Q1&#xff1a;2937. 使三个字符串相等竞赛时代码——检查三个字符串的最长公共前缀子串 Q2&#xff1a;2938. 区分黑球与白球竞赛时代码 Q3&#xff1a;2939. 最大异或乘积竞赛时代码——位运算解法2—— O ( 1 ) O(1) O(1)做法&#x1f44d;&#xff08;大量…

php中json_encode编码json中出现HTML代码导致无法正常输出的解决办法

$options JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT; $data array(key > <p>test</p>);echo json_encode($data, $options);JSON_HEX_TAG, JSON_HEX_AMP, JSON_HEX_APOS, 和 JSON_HEX_QUOT 是 PHP 中 json_encode() 函数的常量选项&#…

Pytorch-CNN轴承故障一维信号分类(二)

目录 前言 1 数据集制作与加载 1.1 导入数据 1.2 数据加载&#xff0c;训练数据、测试数据分组&#xff0c;数据分batch 2 CNN-2D分类模型和训练、评估 2.1 定义CNN-2d分类模型 2.2 定义模型参数 2.3 模型结构 2.4 模型训练 2.5 模型评估 3 CNN-1D分类模型和训练、评…

【密码学基础】Diffie-Hellman密钥交换协议

DH介绍 Diffie-Hellman密钥协议算法是一种确保共享密钥安全穿越不安全网络的方法。 这个机制的巧妙在于需要安全通信的双方可以用这个方法确定对称密钥&#xff0c;然后可以用这个密钥进行加密和解密。 但是注意&#xff0c;这个密钥交换协议 只能用于密钥的交换&#xff0c;而…

java应用打包运行的4种方法

文章目录 1、方法一&#xff1a;打成jar部署运行2、方法二&#xff1a;通过自制启动器的方式运行3、方法三&#xff1a;使用jpackage把java和jdk一起打包4、方法四&#xff1a;使用GraalVM编译为原生应用4.1、使用native-image-agent(Graalvm内工具)工具来收集这些运行库信息4.…

软件无线电SDR-频谱采集python实现

sdr做的频谱采集&#xff0c;保存的500张频谱图&#xff0c;能看出来是什么东西吗&#xff1f;

成都工业学院Web技术基础(WEB)实验四:CSS3布局应用

写在前面 1、基于2022级计算机大类实验指导书 2、代码仅提供参考&#xff0c;前端变化比较大&#xff0c;按照要求&#xff0c;只能做到像&#xff0c;不能做到一模一样 3、图片和文字仅为示例&#xff0c;需要自行替换 4、如果代码不满足你的要求&#xff0c;请寻求其他的…

perl使用Archive::Tar模块进行文件打包

使用perl的Archive::Tar 模块打包文件夹中的指定文件&#xff0c;输出格式 .tar.gz 。下面是perl代码&#xff1a; #! /usr/bin/perl use v5.14; use File::Find; use Archive::Tar;my filesArry (); my $callback sub {push filesArry, $File::Find::name if -f; };find($c…

【Hive】启动beeline连接hive报错解决

1、解决报错2、在datagrip上连接hive 1、解决报错 刚开始一直报错&#xff1a;启动不起来 hive-site.xml需要配置hiveserver2相关的 在hive-site.xml文件中添加如下配置信息 <!-- 指定hiveserver2连接的host --> <property><name>hive.server2.thrift.bin…

如何打印富文本控件中的内容?

出于某种原因&#xff0c;人们确实对打印富文本控件中的内容感到困惑。 我并非打印方面的专家&#xff0c;但是经过对资料的研究的&#xff0c;我也算弄明白了&#xff0c;今天在此记录一下。 解决问题的关键是这个消息&#xff1a;EM_FORMATRANGE。 每次发送这个消息的时候&a…

Vue 3项目的目录结构

使用vite创建完VUE项目后&#xff0c;使用VS Code编辑器打开项目目录&#xff0c;可以看到一个默认生成的项目目录结构 下图是目录结构&#xff1a; 详细介绍.vscode&#xff1a;存放VS Code编辑器的相关配置。 node_modules&#xff1a;存放项目的各种依赖和安装的插件。…

C# WPF上位机开发(通讯协议的编写)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 作为上位机&#xff0c;它很重要的一个部分就是需要和外面的设备进行数据沟通的。很多时候&#xff0c;也就是在这个沟通的过程当中&#xff0c;上…

极狐GitLab 与 Flux 集成实现 GitOps

目录 flux 和 GitOps 极狐GitLab 与 flux 的集成 flux 命令行安装 极狐GitLab flux GitOps GitOps Demo 写在最后 flux 和 GitOps 众所周知&#xff0c;weaveworks 公司在 2017 年提出了 GitOps 这个概念&#xff0c;而 flux 是 weaveworks 开源的一款对 Kubernetes 上的…

【总结】机器学习中的15种分类算法

目录 一、机器学习中的分类算法 1.1 基础分类算法 1.2 集成分类算法 1.3 其它分类算法&#xff1a; 二、各种机器学习分类算法的优缺点 分类算法也称为模式识别&#xff0c;是一种机器学习算法&#xff0c;其主要目的是从数据中发现规律并将数据分成不同的类别。分类算法通…

golang学习笔记——go流水线示例

range与数组、切片、集合 Go 语言中 range 关键字用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。在数组和切片中它返回元素的索引和索引对应的值&#xff0c;在集合中返回 key-value 对。 for 循环的 range 格式可以对 slice、map、数组、字…

计算机网络课程设计【Python实现】

一、网络聊天程序的设计与实现 1、实验目的 使用Socket编程&#xff0c;了解Socket通信的原理&#xff0c;会使用Socket进行简单的网络编程&#xff0c;并在此基础上编写聊天程序&#xff0c;运行服务器端和客户端&#xff0c;实现多个客户端通过服务器端进行通信。 2、总体设…

MongoDB 的复制(副本集)

本文主要介绍MongoDB的复制&#xff08;副本集&#xff09;。 目录 MongoDB的复制&#xff08;副本集&#xff09;特点搭建步骤注意事项 MongoDB的复制&#xff08;副本集&#xff09; MongoDB复制是一种提供数据冗余和高可用性的方法。复制是通过在多个节点上维护数据的副本来…