当前位置:   article > 正文

fps枪械案例_public void characterwalk() { anim.setbool("walk",

public void characterwalk() { anim.setbool("walk",true); }


一、 介绍

经典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 轴旋转,以控制视角左右移动。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
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);
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

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

定义变量和属性:
  • 1

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

实现移动和跳跃:
  • 1

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

实现下蹲:
  • 1

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

实现声音效果:
  • 1

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

其他功能:
  • 1

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

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();
            }
        }
    }

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172

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

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

武器装弹:控制子弹数量、弹匣容量、备弹数量、自动装弹、手动装弹等功能,并播放相关音效、动画和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();
        }
    }
  
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271

六、 手臂摇摆

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

定义了摇摆的参数,包括摇摆幅度(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);
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

七、 步枪切换到手枪

手枪功能和步枪功能一样

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

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

弹药填装逻辑
弹药填装通常需要获取玩家输入,通常是按下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();
        }
    }
  
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278

在这里插入图片描述


八、 切枪效果

定义一个 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);
            }
        }

    }

  
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

九、 添加各种动画

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

在这里插入图片描述






声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/运维做开发/article/detail/922164
推荐阅读
相关标签
  

闽ICP备14008679号

        
cppcmd=keepalive&