fps枪械案例

news2024/12/22 14:20:04

文章目录

  • 一、 介绍
  • 二、 知识点
  • 三、 鼠标移动控制视角
  • 四、 人物行走、奔跑、跳跃、下蹲、音效
  • 五、 射击、射速、瞄准、弹痕、枪火、弹壳、文本
  • 六、 手臂摇摆
  • 七、 步枪切换到手枪
  • 八、 切枪效果
  • 九、 添加各种动画


一、 介绍

经典fps案例
行走、奔跑、跳跃、切枪、换弹、武器展示、弹壳效果、射击效果、全自动半自动效果、瞄准效果、弹痕效果

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

在这里插入图片描述


二、 知识点

lerp插值函数
地面监测
斜坡检测
动画器
枚举


三、 鼠标移动控制视角

声明一个公共浮点型变量 mouseSensitivity,用于控制鼠标灵敏度。
声明一个公共的 Transform 类型变量 playerBody,用于存储玩家的位置信息。
声明一个私有浮点型变量 xRotation,用于存储摄像机绕 x 轴旋转的角度。
在 Start 函数中,将光标锁定在该游戏窗口的中心,并隐藏光标。
在 Update 函数中,获取鼠标在 x 和 y 轴上的移动量,并乘以鼠标灵敏度和时间差,得到摄像机在 x 和 y 轴上的旋转角度。
将摄像机在 y 轴上的旋转角度累加到 xRotation 变量中。
使用 Mathf.Clamp 函数限制 xRotation 的值在 -90 度到 90 度之间,以防止摄像机在 y 轴上旋转过度。
使用 Quaternion.Euler 函数创建一个旋转角度为 xRotation、y 轴和 z 轴为 0 的四元数,并将其赋值给摄像机的本地旋转。
使用 playerBody.Rotate 函数将玩家的位置绕 y 轴旋转,以控制视角左右移动。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 摄像机的旋转
/// 玩家左右旋转控制视线左右移动
/// 摄像机上下旋转控制视线上下移动
/// </summary>
public class MouseLook : MonoBehaviour
{
    public float mouseSensitivity=100f;//鼠标灵敏度

    public Transform playerBody;//玩家的位置
    private float xRotation=0f;

    // Start is called before the first frame update
    void Start()
    {
        //将光标锁定在该游戏窗口的中心,并且隐藏光标
        Cursor.lockState = CursorLockMode.Locked;
    }

    // Update is called once per frame
    void Update()
    {
        float mouseX = Input.GetAxis("Mouse X") * mouseSensitivity * Time.deltaTime;
        float mouseY = Input.GetAxis("Mouse Y") * mouseSensitivity * Time.deltaTime;

        xRotation -= mouseY;//将上下旋转的轴值进行累计
        xRotation = Mathf.Clamp(xRotation,-90f,90f);//限制轴值的累计(这里就能发现上90度和下90度角正好相对于了90的轴值)
        transform.localRotation = Quaternion.Euler(xRotation, 0f,0f);
        playerBody.Rotate(Vector3.up * mouseX);
    }
}


四、 人物行走、奔跑、跳跃、下蹲、音效

定义变量和属性:

在代码中定义了一些变量和属性,包括玩家的移动速度、跳跃力度、重力、是否在地面上、是否在行走、是否在奔跑等等。

实现移动和跳跃:

通过 Moveing() 方法实现玩家的移动,包括使用Input.GetAxis方法获取玩家的移动方向、使用CharacterController.Move方法实现玩家的移动、使用重力来模拟跳跃,以及在空中时不断减小玩家的高度等。

实现下蹲:

通过Crouch()方法实现玩家的下蹲,主要是通过修改CharacterController的height属性来实现。

实现声音效果:

通过PlayFootStepSound()方法实现玩家的移动音效,主要是通过判断玩家是否在行走或奔跑,然后播放对应的音效。

其他功能:

在代码中还实现了判断玩家是否在斜坡上,并在斜坡上行走时施加额外的力量,以及通过地面检测判断玩家是否在地面上等。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 控制玩家移动
/// </summary>
public class PlayerMovement : MonoBehaviour
{
    private CharacterController characterController;
    public float walkSpeed = 10f;// 移动速度
    public float runSpeed=15f;//奔跑速度
    public float jumpForce = 3f;//跳跃力度
    private Vector3 velocity;//设置玩家Y轴的一个冲量变化
    private Vector3 moveDirction; //设置移动方向
    public float gravity = -9f; //设置重力

    public Transform groundCheck;//地面检测物体
    private float groundDistance = 0.4f;//与地面的距离
    public LayerMask groundMask;
    private bool isJump;//判断是否在跳跃
    private bool isGround;//判断是否在地面上
    public bool isWalk;//判断是否在行走
    public bool isRun;//判断是否在奔跑
    private bool isCrouch;//判断是否蹲下

    [SerializeField] private float slopeForce=6.0f; //走斜坡施加的力(是一个乘量)
    [SerializeField] private float slopeForceRayLength=2.0f; //斜坡射线长度(自定义量)

    [Header("键位设置")]
    [SerializeField] [Tooltip("跳跃按键")] private string jumpInputName = "Jump";
    [SerializeField] [Tooltip("奔跑按键")] private KeyCode runInputName;
    [SerializeField] [Tooltip("下蹲按键")] private KeyCode crouchInputName;

    private AudioSource audioSource;
    public AudioClip walkingSound;
    public AudioClip runingSound;

    void Start()
    {
        characterController = GetComponent<CharacterController>();
        audioSource = GetComponent<AudioSource>();
        audioSource.clip = walkingSound;
        audioSource.loop = true;
    }

    // Update is called once per frame
    void Update()
    {

        CheckGround();
        Moveing();
        Crouch();
    }

    /// <summary>
    /// 判断是否在斜坡上
    /// </summary>
    /// <returns></returns>
    public bool OnSlope()
    {
        if (isJump)
            return false;

        RaycastHit hit;
        //向下打出射线(检查是否在斜坡上)
        if (Physics.Raycast(transform.position, Vector3.down, out hit, characterController.height / 2 * slopeForceRayLength))
        {
            //如果触碰到的点的法线,不是在(0,1,0)这个方向上的,那么就人物处在斜坡上
            if (hit.normal != Vector3.up)
            {
                return true;
            }
        }
        return false;
    }


    /// <summary>
    /// 地面检测
    /// </summary>
    public void CheckGround() {
        //在 groundCheck 位置上做一个球体检测判断是否处在地面上
        isGround = Physics.CheckSphere(groundCheck.position, groundDistance, groundMask);
        //如果处在地面上,重力设置成一个固定值
        if (isGround && velocity.y < 0)
        {
            velocity.y = -2f;
        }
    }

    /// <summary>
    /// 跳跃
    /// </summary>
    public void Jump() {
        isJump = Input.GetButtonDown(jumpInputName);
        //施加跳跃的力 
        if (isJump && isGround)
        {
            velocity.y = Mathf.Sqrt(jumpForce * -2f * gravity);
            //velocity.y = 20f;
        }
    }

    /// <summary>
    /// 下蹲
    /// </summary>
    public void Crouch() {
        isCrouch = Input.GetKey(crouchInputName);
        if (isCrouch)
        {
            characterController.height = 1f;
        }
        else
        {
            characterController.height =1.8f;
        }   
    }

    /// <summary>
    /// 移动
    /// </summary>
    public void Moveing()
    {
        float speed;
        float h = Input.GetAxis("Horizontal");
        float v = Input.GetAxis("Vertical");
     
        isRun = Input.GetKey(runInputName);

        isWalk = (Mathf.Abs(h) > 0 || Mathf.Abs(v) > 0) ? true : false;
        speed = isRun ? runSpeed : walkSpeed; //设置行走或奔跑的速度

        moveDirction = (transform.right * h + transform.forward * v).normalized;//设置玩家移动方向(将移动速度进行规范化,防止斜向走速度变大)

        characterController.Move(moveDirction * speed * Time.deltaTime);//移动

        velocity.y += gravity * Time.deltaTime;//不在地面上(空中,累加重力值)
        characterController.Move(velocity * Time.deltaTime); //施加重力
        Jump();
        //如果处在斜面上移动
        if (OnSlope())
        {
            //向下增加力量
            characterController.Move(Vector3.down * characterController.height / 2 * slopeForce * Time.deltaTime);
        }
        PlayFootStepSound();



    }

    ///播放移动的音效
    public void PlayFootStepSound() {
        if (isGround && moveDirction.sqrMagnitude > 0.9f)
        {
            audioSource.clip = isRun ? runingSound : walkingSound;//设置行走或者奔跑的音效
            if (!audioSource.isPlaying)
            {
                audioSource.Play();
            }
        }
        else
        {
            if (audioSource.isPlaying)
            {
                audioSource.Pause();
            }
        }
    }

}


五、 射击、射速、瞄准、弹痕、枪火、弹壳、文本

武器射击:控制射击位置、射程、子弹数量、射速、射击精度等参数,并播放相关音效、动画和特效。

武器装弹:控制子弹数量、弹匣容量、备弹数量、自动装弹、手动装弹等功能,并播放相关音效、动画和UI界面的更新。

武器切换:控制游戏中玩家切换主武器、副武器、全自动和半自动射击模式的功能,并更新UI界面的状态。

武器精度:根据射击模式(全自动或半自动)控制射击精度,实现游戏中的真实射击体验。

界面更新:控制UI界面的更新,包括弹匣数量、备弹数量、射击模式、准星等界面元素的实时更新。

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

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
/// <summary>
/// 武器射击
/// </summary>
public class WeaponController : MonoBehaviour
{
    public PlayerMovement PM;
    public Transform shooterPoint;//射击的位置(枪口)
    public int range = 100;//武器射程
    public int bulletsMag = 31;//一个弹匣数量
    public int bulletLeft = 300;//备弹
    public int currentBullets;//当前子弹数

    public ParticleSystem muzzleFlash;//枪口火焰特效
    public GameObject hitParticles;//子弹击中粒子特效
    public GameObject bulletHole;//弹孔
    public Light muzzleFlashLight;//枪口火焰灯光

    public float fireRate = 0.1f;//射速
    private float fireTimer;//计时器
    private float SpreadFactor; //射击的一点偏移量

    [Header("键位设置")]
    [SerializeField] [Tooltip("填装子弹按键")] private KeyCode reloadInputName;
    [SerializeField] [Tooltip("查看武器按键")] private KeyCode inspectInputName;
    [SerializeField] [Tooltip("主武器按键")] private KeyCode AutoRifleKey;
    [SerializeField] [Tooltip("副武器按键")] private KeyCode HandGunKey;
    [SerializeField] [Tooltip("自动半自动切换按键")] private KeyCode GunShootModelInputName;

    private Animator anim;
    /*音效参数*/
    private AudioSource audioSource;    
    public AudioClip AK47ShoundClip;/*枪声音效片段*/
    public AudioClip reloadAmmoLeftClip;//换子弹1音效片段
    public AudioClip reloadOutOFAmmoClip;//换子弹2音效片段(拉枪栓)

    private bool isReloading;//判断是否在装弹
    private bool isAiming;//判断是否在瞄准
    
    public Transform casingSpawnPoint;//子弹壳抛出的位置
    public Transform casingPrefab; //子弹壳预制体

    private Camera mainCamera;
    public ChooseGunController CGC; //声明切换武器类的实例

    /*使用枚举区分全自动和半自动模式*/
    public enum ShootMode { AutoRifle, SemiGun };
    public ShootMode shootingMode;
    private bool GunShootInput; //根据全自动和半自动 射击的键位输入发生改变
    private int modeNum = 1; //模式切换的一个中间参数(1:全自动模式,2:半自动模式)
    private string shootModelName;

    /*UI的设置*/
    public Image crossHairUI;
    public Text ammoTextUI;
    public Text ShootModelTextUI;

    private void Start()
    {
        audioSource = GetComponent<AudioSource>();
        anim = GetComponent<Animator>();
        currentBullets = bulletsMag;
        mainCamera = Camera.main;
        shootingMode = ShootMode.AutoRifle; //AK47步枪默认是全自动模式
        shootModelName= "全自动";
        UpdateAmmoUI();

    }

    private void Update()
    {
        //切换模式(全自动和半自动)
        if (Input.GetKeyDown(GunShootModelInputName) && modeNum != 1)
        {
            modeNum = 1;
            shootModelName = "全自动";
            shootingMode = ShootMode.AutoRifle;
            ShootModelTextUI.text = shootModelName;
        }
        else if (Input.GetKeyDown(GunShootModelInputName) && modeNum != 0)
        {
            modeNum = 0;
            shootModelName = "半自动";
            shootingMode = ShootMode.SemiGun;
            ShootModelTextUI.text = shootModelName;
        }

        /*控制射击模式的转换  后面就要用代码去动态控制了*/
        switch (shootingMode)
        {
            case ShootMode.AutoRifle:
                GunShootInput = Input.GetMouseButton(0);
                fireRate = 0.1f;
                break;
            case ShootMode.SemiGun:
                GunShootInput = Input.GetMouseButtonDown(0);
                fireRate = 0.2f;
                break;
        }


        if (GunShootInput && currentBullets > 0)
        {
            GunFire();
        }
        else
        {
            muzzleFlashLight.enabled = false;
        }

        //计时器加时间
        if (fireTimer<fireRate)
        {
            fireTimer += Time.deltaTime;
        }

        anim.SetBool("Run",PM.isRun);//播放跑步动画
        anim.SetBool("Walk",PM.isWalk);
        //获取动画状态机第一层动画的状态
        AnimatorStateInfo info=anim.GetCurrentAnimatorStateInfo(0);
        //两种换子弹的东湖
        if (info.IsName("reload_ammo_left")||info.IsName("reload_out_of_ammo"))
        {
            isReloading = true;
        }
        else
        {
            isReloading = false;
        }

        if (Input.GetKeyDown(reloadInputName) && currentBullets < bulletsMag && bulletLeft > 0)
        {
            Reload();
        }
        SpreadFactor = (isAiming) ? 0f : 0.01f;
        DoingAim();

        if (Input.GetKeyDown(inspectInputName))
        {
            anim.SetTrigger("Inspect");
        }
        //切换主武器(自动步枪)
        if (Input.GetKeyDown(AutoRifleKey))
        {
            CGC.ChangeWeapon(0);
        }
        //切换副武器(手枪)
        if (Input.GetKeyDown(HandGunKey))
        {
            CGC.ChangeWeapon(1);
        }

    }

    //更新UI
    public void UpdateAmmoUI() {
        ammoTextUI.text = currentBullets + " / " + bulletLeft;
        ShootModelTextUI.text = shootModelName;
    }

    /// <summary>
    /// 瞄准的逻辑
    /// </summary>
    public void DoingAim() {
        if (Input.GetMouseButton(1)&&!isReloading&&!PM.isRun)
        {
            isAiming = true;
            anim.SetBool("Aim",isAiming);
            crossHairUI.gameObject.SetActive(false);
            mainCamera.fieldOfView = 25;//瞄准的时候摄像机视野变小
        }
        else
        {
            isAiming = false;
            anim.SetBool("Aim", isAiming);
            crossHairUI.gameObject.SetActive(true);
            mainCamera.fieldOfView = 60;//瞄准的时候摄像机视野恢复

        }


    }

    /// <summary>
    /// 射击逻辑
    /// </summary>
    public void GunFire() {
        //控制射速, 当前弹夹打光了,正在装子弹,正在奔跑  就不可以发射了
        if (fireTimer < fireRate||currentBullets<=0||isReloading||PM.isRun) return;


        RaycastHit hit;
        Vector3 shootDirection = shooterPoint.forward;
        //改成这个,shootDirection shooterPoint这个游戏物体进行小的偏移(TransformDirection 将local坐标转换为世界坐标)
        shootDirection = shootDirection + shooterPoint.TransformDirection(new Vector3(Random.Range(-SpreadFactor, SpreadFactor), Random.Range(-SpreadFactor, SpreadFactor)));

        if (Physics.Raycast(shooterPoint.position, shootDirection, out hit, range))
        {
            Debug.Log(hit.transform.name + "打到了");

            GameObject hitParticleEffect = Instantiate(hitParticles, hit.point, Quaternion.FromToRotation(Vector3.up, hit.normal));//实例出子弹击中的火光特效
            GameObject bulletHoleEffect = Instantiate(bulletHole, hit.point, Quaternion.FromToRotation(Vector3.up, hit.normal));//实例出弹孔特效
            //回收特效
            Destroy(hitParticleEffect,1f);
            Destroy(bulletHoleEffect, 3f);
        }

        if (!isAiming)
        {
            anim.CrossFadeInFixedTime("fire", 0.1f); //播放普通开火动画(使用动画的淡出淡入效果)
        }
        else
        {//瞄准状态下,播放瞄准的开火动画
            anim.Play("aim_fire", 0, 0f);
        }
        muzzleFlash.Play(); //播放火光特效
        muzzleFlashLight.enabled = true;
        PlayerShootSound();//播放射击音效
        //实例抛弹壳
        Instantiate(casingPrefab, casingSpawnPoint.transform.position,casingSpawnPoint.transform.rotation);
        currentBullets--;
        UpdateAmmoUI();
        fireTimer = 0f;//重置计时器
    }


    public void PlayerShootSound() {
        audioSource.clip = AK47ShoundClip;       
        audioSource.Play();
    }
    /// <summary>
    /// 填装弹药逻辑
    /// </summary>
    public void Reload()
    {
        if (bulletLeft <= 0) return;
        DoReloadAnimation();

        //计算需要填充的子弹
        int bulletToLoad = bulletsMag - currentBullets;
        //计算备弹扣除的子弹
        int bulletToReduce = (bulletLeft >= bulletToLoad) ? bulletToLoad : bulletLeft;
        bulletLeft -= bulletToReduce; //备弹减少
        currentBullets += bulletToReduce;//当前子弹增加
        UpdateAmmoUI();
    }

    //播放装弹动画
    public void DoReloadAnimation()
    {
        if (currentBullets > 0)
        {

            anim.Play("reload_ammo_left", 0, 0);
            audioSource.clip = reloadAmmoLeftClip;
            audioSource.Play();
        }

        if (currentBullets == 0)
        {
            anim.Play("reload_out_of_ammo", 0, 0);
            audioSource.clip = reloadOutOFAmmoClip;
            audioSource.Play();
        }
    }
  
}


六、 手臂摇摆

鼠标移动后,手臂滞后移动,会更有画面感

定义了摇摆的参数,包括摇摆幅度(amout)、平滑值(smoothAmout)和最大摇摆幅度(maxAmout)。

在 Start 方法中,获取了武器手臂模型的初始位置,以便后续摇摆时能够回到原始位置。

在 Update 方法中,根据鼠标输入获取相应的水平和垂直移动量,然后将其限制在最大摇摆幅度范围内。

创建一个新的位置向量,将水平和垂直移动量赋值给其 x 和 y 分量,将 z 分量设为 0。

使用 Vector3.Lerp 方法,将武器手臂模型的位置平滑移动到新的位置向量与原始位置向量相加后得到的位置向量之间,从而实现摇摆效果。平滑程度受 smoothAmout 参数控制,平滑程度越大,摇摆越缓慢。
在这里插入图片描述

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 武器摇摆
/// </summary>
public class WeaponSway : MonoBehaviour
{
    /*摇摆的参数*/
    public float amout; //摇摆幅度
    public float smoothAmout;//一个平滑值
    public float maxAmout;//最大摇摆幅度

    private Vector3 originPostion; //初始位置

    // Start is called before the first frame update
    void Start()
    {
        //自身位置(相对于父级物体变换得位置)
        originPostion = transform.localPosition;
    }

    // Update is called once per frame
    void Update()
    {
        //设置武器手臂模型位置得值,
        float movementX = -Input.GetAxis("Mouse X") * amout;
        float movementY = -Input.GetAxis("Mouse Y") * amout;
        //限制
        movementX = Mathf.Clamp(movementX, -maxAmout, maxAmout);
        movementY = Mathf.Clamp(movementY, -maxAmout, maxAmout);

        Vector3 finallyPostion = new Vector3(movementX, movementY, 0);
        //手柄位置变换
        transform.localPosition = Vector3.Lerp(transform.localPosition, finallyPostion + originPostion, Time.deltaTime * smoothAmout);
    }
}


七、 步枪切换到手枪

手枪功能和步枪功能一样

射击逻辑
射击需要获取玩家输入,通常是按下鼠标左键或空格键等触发器
射击会生成一个射线,用于检测命中目标
如果射线与目标相交,则触发目标的受伤逻辑
如果射线未与目标相交,则触发未命中逻辑

瞄准逻辑
瞄准通常需要获取玩家输入,通常是按下右键或其他瞄准键
瞄准会改变相机的角度或位置,以便更准确地瞄准目标
瞄准时通常会显示一个准星或其他标记,以便玩家知道自己的瞄准位置

弹药填装逻辑
弹药填装通常需要获取玩家输入,通常是按下R键或其他填装键
填装逻辑需要知道当前武器的弹药类型和弹夹容量
如果当前弹夹未满,则会填装弹药
填装弹药会消耗弹药库存中的弹药
如果弹药库存不足,则无法填装弹药

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
/// <summary>
/// 手枪(G18)武器射击
/// </summary>
public class HandGunController : MonoBehaviour
{
    public PlayerMovement PM;
    public Transform shooterPoint;//射击的位置(枪口)
    
    [Tooltip("武器射程")] public int range = 100;//武器射程
    [Tooltip("一个弹匣数量")] public int bulletsMag = 31;//一个弹匣数量
    [Tooltip("当前子弹数")] public int currentBullets;//当前子弹数
    [Tooltip("备弹")] public int bulletLeft = 300;//备弹
    [Header("枪口特效")]
    public ParticleSystem muzzleFlash;//枪口火焰特效
    public GameObject hitParticles;//子弹击中粒子特效
    public GameObject bulletHole;//弹孔
    public Light muzzleFlashLight;//枪口火焰灯光
    [Header("武器射速")]
    public float fireRate = 0.1f;//射速
    private float fireTimer;//计时器
    private float SpreadFactor; //射击的一点偏移量

    [Header("键位设置")]
    [SerializeField] [Tooltip("填装子弹按键")] private KeyCode reloadInputName;
    [SerializeField] [Tooltip("查看武器按键")] private KeyCode inspectInputName;
    [SerializeField] [Tooltip("主武器")] private KeyCode AutoRifleKey;
    [SerializeField] [Tooltip("副武器")] private KeyCode HandGunKey;
    [SerializeField] [Tooltip("自动半自动切换")] private KeyCode GunShootModelInputName;


    private Animator anim;
    private AudioSource audioSource;
    [Header("音效片段")]
    public AudioClip HandGunSound;/*枪声音效片段*/
    public AudioClip reloadAmmoLeftClip;//换子弹1音效片段
    public AudioClip reloadOutOFAmmoClip;//换子弹2音效片段(拉枪栓)

    private bool isReloading;//判断是否在装弹
    private bool isAiming;//判断是否在瞄准

    public Transform casingSpawnPoint;//子弹壳抛出的位置
    public Transform casingPrefab; //子弹壳预制体

    private Camera mainCamera;
    public ChooseGunController CGC; //声明切换武器类的实例

    /*使用枚举区分全自动和半自动模式*/
    public enum ShootMode { AutoRifle,SemiGun};
    public ShootMode shootingMode;
    private bool GunShootInput; //根据全自动和半自动 射击的键位输入发生改变
    private int modeNum = 0; //模式切换的一个中间参数(1:全自动模式,2:半自动模式)
    private string shootModelName;

    [Header("UI的设置")]
    public Image crossHairUI;
    public Text ammoTextUI;
    public Text ShootModelTextUI;

    private void Start()
    {
        audioSource = GetComponent<AudioSource>();
        anim = GetComponent<Animator>();
        currentBullets = bulletsMag;
        mainCamera = Camera.main;
        shootingMode = ShootMode.SemiGun; //G18手枪默认是半自动模式
        shootModelName = "半自动";
        UpdateAmmoUI();
    }

    private void Update()
    {
        //切换模式(全自动和半自动)
        if (Input.GetKeyDown(GunShootModelInputName) && modeNum != 1)
        {
            modeNum = 1;
            shootModelName = "全自动";
            shootingMode = ShootMode.AutoRifle;
            ShootModelTextUI.text = shootModelName;

        }
        else if (Input.GetKeyDown(GunShootModelInputName) && modeNum != 0)
        {
            modeNum = 0;
            shootModelName = "半自动";
            shootingMode = ShootMode.SemiGun;
            ShootModelTextUI.text = shootModelName;

        }

        /*控制射击模式的转换  后面就要用代码去动态控制了*/
        switch (shootingMode)
        {
            case ShootMode.AutoRifle:
                GunShootInput = Input.GetMouseButton(0);
                fireRate = 0.08f;
                break;
            case ShootMode.SemiGun:
                GunShootInput = Input.GetMouseButtonDown(0);
                fireRate = 0.2f;
                break;
        }



        if (GunShootInput && currentBullets>0)
        {
            GunFire();
        }
        else
        {
            muzzleFlashLight.enabled = false;
        }

        //计时器加时间
        if (fireTimer<fireRate)
        {
            fireTimer += Time.deltaTime;
        }

        anim.SetBool("Run",PM.isRun);//播放跑步动画
        anim.SetBool("Walk",PM.isWalk);
        //获取动画状态机第一层动画的状态
        AnimatorStateInfo info=anim.GetCurrentAnimatorStateInfo(0);
        //两种换子弹的动画
        if (info.IsName("reload_ammo_left")||info.IsName("reload_out_of_ammo"))
        {
            isReloading = true;
        }
        else
        {
            isReloading = false;
        }

        if (Input.GetKeyDown(reloadInputName) && currentBullets < bulletsMag && bulletLeft > 0)
        {
            Reload();
        }
        SpreadFactor = (isAiming) ? 0f : 0.01f; 
        DoingAim();

         
        if (Input.GetKeyDown(inspectInputName))
        {
            anim.SetTrigger("Inspect");
        }

        //切换主武器(自动步枪)
        if (Input.GetKeyDown(AutoRifleKey))
        {
            CGC.ChangeWeapon(0);
        }
        //切换副武器(手枪)
        if (Input.GetKeyDown(HandGunKey))
        {
            CGC.ChangeWeapon(1);
        }

    }

    //更新UI
    public void UpdateAmmoUI() {
        ammoTextUI.text = currentBullets + " / " + bulletLeft;
        ShootModelTextUI.text = shootModelName;
    }

    /// <summary>
    /// 瞄准的逻辑
    /// </summary>
    public void DoingAim()
    {
        if (Input.GetMouseButton(1) && !isReloading && !PM.isRun )
        {
            isAiming = true;
            anim.SetBool("Aim", isAiming);
            crossHairUI.gameObject.SetActive(false);
            mainCamera.fieldOfView = 40;//瞄准的时候摄像机视野变小
        }
        else
        {
            isAiming = false;
            anim.SetBool("Aim", isAiming);
            crossHairUI.gameObject.SetActive(true);
            mainCamera.fieldOfView = 60;//瞄准的时候摄像机视野恢复

        }


    }

    /// <summary>
    /// 射击逻辑
    /// </summary>
    public void GunFire() {
        //控制射速, 当前弹夹打光了,正在装子弹,正在奔跑  就不可以发射了
        if (fireTimer < fireRate||currentBullets<=0||isReloading||PM.isRun) return;


        RaycastHit hit;
        Vector3 shootDirection = shooterPoint.forward;
        //改成这个,shootDirection shooterPoint这个游戏物体进行小的偏移(TransformDirection 将local坐标转换为世界坐标)
        shootDirection = shootDirection + shooterPoint.TransformDirection(new Vector3(Random.Range(-SpreadFactor, SpreadFactor), Random.Range(-SpreadFactor, SpreadFactor)));

        if (Physics.Raycast(shooterPoint.position, shootDirection, out hit, range))
        {
            Debug.Log(hit.transform.name + "打到了");

            GameObject hitParticleEffect = Instantiate(hitParticles, hit.point, Quaternion.FromToRotation(Vector3.up, hit.normal));//实例出子弹击中的火光特效
            GameObject bulletHoleEffect = Instantiate(bulletHole, hit.point, Quaternion.FromToRotation(Vector3.up, hit.normal));//实例出弹孔特效
            //回收特效
            Destroy(hitParticleEffect,1f);
            Destroy(bulletHoleEffect, 3f);
        }

        if (!isAiming)
        {
            anim.CrossFadeInFixedTime("fire", 0.1f); //播放普通开火动画(使用动画的淡出淡入效果)
        }
        else
        {//瞄准状态下,播放瞄准的开火动画
            anim.Play("aim_fire", 0, 0f);
        }
        muzzleFlash.Play(); //播放火光特效
        muzzleFlashLight.enabled = true;
        PlayerShootSound();//播放射击音效
        //实例抛弹壳
        Instantiate(casingPrefab, casingSpawnPoint.transform.position,casingSpawnPoint.transform.rotation);
        currentBullets--;
        UpdateAmmoUI();
        fireTimer = 0f;//重置计时器
    }


    public void PlayerShootSound() {
        audioSource.clip = HandGunSound;       
        audioSource.Play();
    }
    /// <summary>
    /// 填装弹药逻辑
    /// </summary>
    public void Reload()
    {
        if (bulletLeft <= 0) return;
        DoReloadAnimation();

        //计算需要填充的子弹
        int bulletToLoad = bulletsMag - currentBullets;
        //计算备弹扣除的子弹
        int bulletToReduce = (bulletLeft >= bulletToLoad) ? bulletToLoad : bulletLeft;
        bulletLeft -= bulletToReduce; //备弹减少
        currentBullets += bulletToReduce;//当前子弹增加
        UpdateAmmoUI();
    }

    //播放装弹动画
    public void DoReloadAnimation()
    {
        if (currentBullets > 0)
        {

            anim.Play("reload_ammo_left", 0, 0);
            audioSource.clip = reloadAmmoLeftClip;
            audioSource.Play();
        }

        if (currentBullets == 0)
        {
            anim.Play("reload_out_of_ammo", 0, 0);
            audioSource.clip = reloadOutOFAmmoClip;
            audioSource.Play();
        }
    }
  
}

在这里插入图片描述


八、 切枪效果

定义一个 List 类型的 Weapons 变量,用于存储多个武器 GameObject 对象。

定义一个整型变量 index,用于记录当前选中的武器索引。

在 Start 方法中,将 index 设为 0。

实现一个 ChangeWeapon 方法,用于切换武器。该方法接收一个整型参数 WeaponIndex,用于指定要切换到的武器索引。

在该方法中,使用 for 循环遍历 Weapons 列表中的所有武器。

如果当前遍历到的武器索引与 WeaponIndex 相同,就将该武器对象设为激活状态。

如果当前遍历到的武器索引与 WeaponIndex 不同,就将该武器对象设为不激活状态。

在切换武器时,还会根据不同的武器类型来更新弹药 UI 界面。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 武器切换
/// </summary>
public class ChooseGunController : MonoBehaviour
{
    //武器设置为集合,存放多个武器
    public List<GameObject> Weapons=new List<GameObject>();
    private int index;

    // Start is called before the first frame update
    void Start()
    {
        index = 0;
    }

    //切换武器方法(全部武器挂载人物手上,默认隐藏,,谁需要就显示显示出来)
    public void ChangeWeapon(int WeaponIndex)
    {
        for (int i = 0; i < Weapons.Count; i++)
        {
            if (WeaponIndex == i)
            {
                Weapons[i].gameObject.SetActive(true);
                if (WeaponIndex == 0)
                {

                    Weapons[i].gameObject.GetComponentInChildren<WeaponController>().UpdateAmmoUI();      
                   // print(Weapons[i].gameObject.name);
                }
                else if (WeaponIndex == 1)
                {
                    Weapons[i].gameObject.GetComponentInChildren<HandGunController>().UpdateAmmoUI();
                    //print(Weapons[i].gameObject.name);
                }
            }
            else
            {
                Weapons[i].gameObject.SetActive(false);
            }
        }

    }

  
}


九、 添加各种动画

行走、奔跑、跳跃、开枪、换弹、切枪、瞄准

在这里插入图片描述






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

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

相关文章

gitlab搭建以及自动化部署

一、安装gitlab 首先下载gitlab的安装包&#xff0c;地址如下&#xff1a; https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/ubuntu/pool/bionic/main/g/gitlab-ce/ 然后安装下载的包即可&#xff0c;一般还需要安装openssh-server等依赖包&#xff0c;在安装gitlab包之前可以…

正则表达式-基本元字符和语法规则

© Ptw-cwl 文章目录 字符匹配元字符.元字符[]元字符[^]元字符*元字符元字符?元字符{}元字符|元字符()元字符^元字符$元字符\元字符\d元字符\w元字符\s元字符\b元字符\B元字符*?、?、??、{n,m}?元字符(?)、(?!)元字符(?:)元字符\1、\2等元字符^、$元字符&#x…

【刷题之路Ⅱ】LeetCode 274275. H指数ⅠⅡ

【刷题之路Ⅱ】LeetCode 274&275. H指数Ⅰ&Ⅱ 一、题目描述二、解题1、方法1——排序1.1、思路分析1.2、代码实现1.3、升级到275题的二分法1.3.1、思路分析1.3.2、代码实现 2、方法2——计数排序2.1、思路分析2.2、代码实现 一、题目描述 原题连接&#xff1a; 274. H…

C语言专升本编程题复习

1.求100以内的素数 int main( ) { int i,j,count0;for(i1;i<100;i){for(j2;j<i;j){if(i%j0)break;}if(ij){count; printf("%d ",i);if(count%50)printf("\n");}} }2.求1000以内的完数 * 完数&#xff1a;等于其因子&#xff08;不含本身&…

【五一创作】SAP入门到放弃系列之批次确定配置

概念&#xff1a; 在后勤涉及物料移动或消耗流程中&#xff0c;从采购到生产到销售&#xff0c;涉及启用批次的物料&#xff0c;需要一次又一次地为出入库业务中的库存指定批次。如果企业的出库库批次管理有一定的管理要求&#xff0c;比如先进先出&#xff0c;就可以针对货物…

基于Java语言开发B/S架构实现的云HIS

一、云HIS系统框架简介 1、技术框架 &#xff08;1&#xff09;总体框架&#xff1a; SaaS应用&#xff0c;全浏览器访问 前后端分离&#xff0c;多服务协同 服务可拆分&#xff0c;功能易扩展 &#xff08;2&#xff09;技术细节&#xff1a; 前端&#xff1a;AngularNg…

级数可视化

泰勒级数 数学家们普遍偏爱多项式&#xff0c;如果评选一下高等数学里面最重要的公式&#xff0c;泰勒公式一定榜上有名&#xff0c;泰勒公式的核心思想就是把一个给定的任意函数&#xff0c;展开成多项式的形式&#xff0c;如果是有限项&#xff0c;就像作泰勒多项式&#xf…

深入了解云计算:发展历程、服务模型、未来趋势

开篇博主 bluetata 的观点&#xff1a;PaaS 服务必将是未来10年云计算权重最高的趋势&#xff08;05/02/2023 15:32&#xff09; 文章目录 一、前言二、认识了解云计算2.1 什么是云计算2.1.1 维基百科上的云计算定义2.1.2 NIST 标准云计算定义2.1.3 如果被面试如何解释云计算 2…

MYSQL-数据库管理(下)

查看数据库信息 show database 查看数据库中的表信息 use 数据库名 #切换到书库中 show tables show tables in mysql 显示数据表的结构&#xff08;字段&#xff09; describe user; Field:字段名称 type:数据类型 Null :是否允许为空 Key :主键 Type:数据类型 Null :是否…

武忠祥老师每日一题||不定积分基础训练(五)

∫ x f ′ ( x ) d x \int xf{}(x)\,{\rm d}x ∫xf′(x)dx ∫ x d f ( x ) \int x\,{\rm d}{f(x)} ∫xdf(x) x f ( x ) − ∫ f ( x ) d x xf(x)-\int f(x)\,{\rm d}x xf(x)−∫f(x)dx 由题知&#xff1a; f ( x ) ( ln ⁡ 2 x ) ′ 2 ln ⁡ x 1 x 2 ln ⁡ x x f(x)({…

宋爽:利用大数据解码遗传学的秘密 | 提升之路系列(七)

导读 为了发挥清华大学多学科优势&#xff0c;搭建跨学科交叉融合平台&#xff0c;创新跨学科交叉培养模式&#xff0c;培养具有大数据思维和应用创新的“π”型人才&#xff0c;由清华大学研究生院、清华大学大数据研究中心及相关院系共同设计组织的“清华大学大数据能力提升项…

【Git】Gitee免密push(TencentCloudLinux)

前提&#xff1a; 我用的是腾讯云的Centos(Linux)服务器 我创建好了仓库 我配置过git 可以正常用密码push 以上自行解决 我们直接配置公钥解决免密push 1.在服务器上创建公钥 在用户根目录创建 公钥 邮箱写自己的 随意写 我写的是gitee绑定的邮箱 ssh-keygen -t ed25519 -C…

第四期 微信云开发之订阅消息以及定时发送(触发器)

前言 在我们开发过程中&#xff0c;很多场景都会使用到微信小程序订阅消息功能&#xff0c;例如打卡通知、订餐通知等等。但是在云开发过程中&#xff0c;没有后台的情况下&#xff0c;如何进行消息的定时通知呢&#xff1f;下面我将从小程序订阅消息到定时发送订阅消息进行一个…

改进YOLOv5:结合位置编码CoordConv,提升行人目标等预测能力 | 卷积加上坐标,从而使其具备了空间感知能力

CoordConv:给卷积加上坐标,从而使其具备了空间感知能力 核心代码CoordConv代码common代码:yolo注册yaml文件:测试众所周知,深度学习里的卷积运算是具有平移等变性的,这样可以在图像的不同位置共享统一的卷积核参数,但是这样卷积学习过程中是不能感知当前特征在图像中的坐标…

STM-32:SPI通信外设

目录 一、前言二、SPI功能框图三、SPI通信读写数据 一、前言 STM32的SPI外设可用作通讯的主机及从机&#xff0c;支持最高的SCK时钟频率为fpclk/2 (STM32F103型号的芯片默认 fpclk1为 36MHz&#xff0c;fpclk2为 72MHz)&#xff0c;完全支持 SPI协议的4种模式&#xff0c;数据…

RestTemplate 请求第三方网站数据 定时插入到数据库

目录 0 课程视频 1 RestTemplate-> 是Web Http 客户端请求 模板工具 1.1 使用RestTemplate -> 选择执行引擎 Http客户端请求工具 1.2 执行引擎 Http客户端请求工具 1.2.1 JDK HttpUrlConnection 1.2.2 Apache HttpClient 1.2.3 OkHttp 1.2 升级版 -> WebClien…

Android 12.0 Launcher3仿ios长按app图标实现抖动动画开始拖拽停止动画

1.概述 在12.0的系统rom定制化开发中,在对系统原生Launcher3的定制需求中,也有好多功能定制的,在ios等电子产品中 的一些好用的功能,也是可以被拿来借用的,所以在最近的产品开发需求中,需求要求模仿ios的 功能实现长按app图标实现抖动动画,接下来看如何分析该功能的实现…

Centos7快速安装Logstash 7.17.7并实现MySQL中数据导入Elasticsearch

可以通过以下命令在线安装 Logstash 7.17.7 sudo rpm --import https://artifacts.elastic.co/GPG-KEY-elasticsearch sudo rpm -ivh https://artifacts.elastic.co/downloads/logstash/logstash-7.17.7-x86_64.rpm安装完成后&#xff0c;需要添加环境变量 export PATH$PATH:…

ARM处理器的指令集(3)

ARM处理器的指令集 一、数据处理类指令 数据处理指令只能对寄存器的内容进行操作&#xff0c;而不能对内存中的数据进行操作&#xff0c;所以ARM数据处理指令均可以选择使用S后缀&#xff0c;以影响状态标志位。 影响状态标志位的命令&#xff1a; MOVS–>数据传送指令&am…

java servlet 农机租赁网站系统Myeclipse开发mysql数据库web结构jsp编程计算机网页项目

一、源码特点 java servlet 农机租赁网站系统是一套完善的java web信息管理系统&#xff0c;对理解JSP java编程开发语言有帮助 系统采用 serlvetdaobean 模式开发 &#xff0c;系统具有完整的源代码和数 据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,M…