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

news2024/11/26 22:26:35

文章目录

  • 前言
  • 模型素材
  • 文章用到的粒子火光特效
  • 射击效果
  • 换弹
  • 瞄准
  • 开枪抖动效果
  • 设置显示文本
  • 最终代码
  • 不同武器射击效果
    • 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。如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我可能也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
在这里插入图片描述

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

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

相关文章

Dubbo入门直接上手,结合微服务详解

Dubbo 高性能、轻量级的 Java RPC 框架 RPC&#xff1a; Remote Procedure Call 远程过程调用&#xff0c;简单来说就是它允许一个计算机程序通过网络请求调用另一个计算机上的程序&#xff0c;就像本地调用一样。有非常多的协议和技术来都实现了RPC的过程&#xff0c;比如&a…

MySQL数据库,创建和管理表

创建数据库&#xff1a; 方式一&#xff1a;创建数据库 CREATE DATABASE 数据库名&#xff1b;&#xff08;使用的是默认的字符集&#xff09; 方式二&#xff1a;创建数据库并指定字符集 CREATE DATABASE 数据库名 CHARACTER SET 字符集&#xff1b; 方式三&#xff1a;判断数…

【原创】【一类问题的通法】【真题+李6卷6+李4卷4(+李6卷5)分析】合同矩阵A B有PTAP=B,求可逆阵P的策略

【铺垫】二次型做的变换与相应二次型矩阵的对应&#xff1a;二次型f&#xff08;x1&#xff0c;x2&#xff0c;x3&#xff09;xTAx&#xff0c;g&#xff08;y1&#xff0c;y2&#xff0c;y3&#xff09;yTBy ①若f在可逆变换xPy下化为g&#xff0c;即P为可逆阵&#xff0c;有P…

【大数据】Hudi 核心知识点详解(二)

&#x1f60a; 如果您觉得这篇文章有用 ✔️ 的话&#xff0c;请给博主一个一键三连 &#x1f680;&#x1f680;&#x1f680; 吧 &#xff08;点赞 &#x1f9e1;、关注 &#x1f49b;、收藏 &#x1f49a;&#xff09;&#xff01;&#xff01;&#xff01;您的支持 &#x…

设计模式篇---代理模式

文章目录 概念结构实例静态代理动态代理 总结 概念 代理模式&#xff1a;给某一个对象提供一个代理或占位符&#xff0c;并由代理对象来控制对原对象的访问。 比如我们想从其他国家买东西&#xff0c;但我们无法直接联系外国的商家&#xff0c;可以找代理商&#xff0c;让他们…

ipa文件怎么去除包体内的插件在线签名工具步骤

当开发者完成iOS应用的开发并构建完成后&#xff0c;应用程序会被打包为一个.ipa文件&#xff0c;这是一个iOS App Store的安装包格式。在某些情况下&#xff0c;开发者可能需要去除.ipa文件中包含的插件&#xff08;通常指的是app extension、frameworks或watch apps等&#x…

内测分发是什么?十年的前端开发者带你了解

内测分发是软件开发过程中的一个阶段&#xff0c;特别指软件还未完全完成或准备对外广泛发布前&#xff0c;向一定范围的用户群体提供该软件版本的测试机会&#xff0c;以便收集反馈和修复潜在的问题。在讲解内测分发之前&#xff0c;我们需要明确几个相关概念&#xff1a; 软件…

12.视图

目录 1.视图的含义与作用 2.视图的创建与查看 1.创建视图的语法形式 2、查看视图&#xff1a; 1.使用DESCRIBE语句查看视图基本信息 2.使用SHOW TABLE STATUS语查看视图基本信息查看视图的信息 3.使用SHOW CREATE VIEW语查看视图详细信息 4.在views表中查看视图详细信息…

23.12.10日总结

周总结 这周三的晚自习&#xff0c;学姐讲了一下git的合作开发&#xff0c;还有懒加载&#xff0c;防抖&#xff0c;节流 答辩的时候问了几个问题&#xff1a; 为什么在js中0.10.2!0.3? 在js中进行属性运算时&#xff0c;会出现0.10.20.300000000000000004js遵循IEEE754标…

DIP——边缘提取与分割

1.使用canny算法进行边缘提取 本实验比较简单&#xff0c;基本思路是对原图像进行一个高斯模糊处理&#xff0c;用于去噪&#xff0c;之后转换为灰度图&#xff0c;直接调用cv库中的canny记性边缘提取。若想直接得到彩色边缘&#xff0c;则通过按位与操作&#xff0c;将原始彩色…

docker-centos中基于keepalived+niginx模拟主从热备完整过程

文章目录 一、环境准备二、主机1、环境搭建1.1 镜像拉取1.2 创建网桥1.3 启动容器1.4 配置镜像源1.5 下载工具包1.6 下载keepalived1.7 下载nginx 2、配置2.1 配置keepalived2.2 配置nginx2.2.1 查看nginx.conf2.2.2 修改index.html 3、启动3.1 启动nginx3.2 启动keepalived 4、…

MySQL - 聚簇索引和非聚簇索引,回表查询,索引覆盖,索引下推,最左匹配原则

聚簇索引和非聚簇索引 聚簇索引和非聚簇索引是 InnoDB 里面的叫法 一张表它一定有聚簇索引&#xff0c;一张表只有一个聚簇索引在物理上也是连续存储的 它产生的过程如下&#xff1a; 表中有无有主键索引&#xff0c;如果有&#xff0c;则使用主键索引作为聚簇索引&#xff1b;…

Kafka 最佳实践:构建可靠、高性能的分布式消息系统

Apache Kafka 是一个强大的分布式消息系统&#xff0c;被广泛应用于实时数据流处理和事件驱动架构。为了充分发挥 Kafka 的优势&#xff0c;需要遵循一些最佳实践&#xff0c;确保系统在高负载下稳定运行&#xff0c;数据可靠传递。本文将深入探讨 Kafka 的一些最佳实践&#x…

TailwindCSS 配置可视化检查器

问题 TailwindCSS 框架为我们提供了大量默认的类和属性&#xff0c;而且开发者也能够自定义类和配置。 对于初学者来说&#xff0c;这些配置其实是比较复杂的&#xff0c;这也是tailwindcss最大的入手成本&#xff0c;开发者的记忆负担和心智负担也都比较大。 有没有办法能够…

【BUG】微信小程序image不会随着url动态变化

问题描述&#xff1a; 第一次打开界面&#xff0c;显示的是默认头像而不是用户头像&#xff0c;似乎image里面的src只要第一次有值就不会再更新了 解决 不要给src里面的变量设置初始值&#xff0c;而是直接赋空值

ChatGPT 应用开发(一)ChatGPT OpenAI API 免代理调用方式(通过 Cloudflare 的 AI Gateway)

前言 开发 ChatGPT 应用&#xff0c;我觉得最前置的点就是能使用 ChatGPT API 接口。首先我自己要能成功访问&#xff0c;这没问题&#xff0c;会魔法就可以本地调用。 那用户如何调用到我的应用 API 呢&#xff0c;我的理解是通过用户能访问到的中转服务器向 OpenAI 发起访问…

[软件工具]文本去重含有重复的全部删除不是保留一个重复的方法

文本去重含有重复的全部删除不是保留一个重复的方法 第一步&#xff1a;首先打开软件 第二步&#xff1a;设置好保存目录后&#xff0c;将文件夹拖拽到列表&#xff0c;软件会自动识别导入txt 第三步&#xff1a;点击开始处理&#xff0c;即可完成任务 本软件支持批量处理&a…

Go1.21.0 程序启动过程

版本说明 Go 1.21.0操作系统&#xff1a;Windows11 Intel64 结论先行 开发关注版 在 Go 语言中&#xff0c;启动顺序通常如下&#xff1a; 导入包&#xff1a;首先&#xff0c;Go 编译器按照源文件中的 import 语句导入所有需要的包。初始化常量和变量&#xff1a;接着&am…

uc_16_UDP协议_HTTP协议

1 UDP协议 适合游戏、视频等情景&#xff0c;安全性要求不高&#xff0c;效率要求高。 1&#xff09;UDP不提供客户机与服务器的链接&#xff1a; UDP的客户机与服务器不必存在长期关系。一个UDP的客户机在通过一个套接字向一个UDP服务器发送了一个数据报之后&#xff0c;马上…

UE小:物品拼装功能

蓝图B1的实现步骤&#xff1a; 获取玩家控制器和视角&#xff1a;首先获取玩家控制器&#xff0c;然后使用Deproject Screen to World节点将屏幕上的鼠标位置转换为世界空间中的一条射线。 射线检测&#xff1a;使用Line Trace by Channel或Line Trace for Objects节点发射射线…