一个简简单单的基于CharacterController的第一人称射击FPS角色控制器的实现——Unity随手记(2021.1.19)

由于之前期末了一直在装备考试,随手记已经很久没有写了。今天开始用Unity整一个第一人称射击游戏出来。


今天实现的内容:

  • 基于CharacterController的第一人称移动功能,了解了一下CharacterController.Move和CharacterController.SimpleMove的区别CharacterController中Move和SimpleMove的区别

  • 重力效果(不拟真)

  • 跳跃效果跳跃受当前运动状态影响

  • 冲刺效果,更快的移动速度

  • 下蹲效果,通过改变CharacterController.Height来实现,拥有独立的下蹲行走速度和下蹲冲刺速度。


BUG以及缺陷:

我这个写法标准嘛?我也不知道,但是能用了。


值得注意的:

冲刺判断时考虑到了移动情况,避免在原地不动时按下冲刺键也能跳到冲刺高度。

要注意将得到的movementDirection的模长设置为1,可以使用normalize。这样做是为了避免在Horizontal和Vertical都有输入时导致的模长大于1位移变大,也就是斜着走时速度变快的情况。注意不能在模长小于0时设置。


代码:

基于CharacterController

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

public class FPCharacterControllerMovement : MonoBehaviour
{
    
    
    //-----------------------------------------------------运动-----------------------------------------------------------------//
    // 走路的速度
    public float walkSpeed = 10f;
    // 奔跑的速度
    public float sprintingSpeed = 20f;
    // 重力
    public float grivaty = 9.8f;
    // 跳跃高度
    public float jumpHeight = 2f; 
    // 下蹲之后的高度
    public float crouchHeight = 1f;
    // 站立时的高度
    [HideInInspector]
    public float originHeight;
    // 下蹲时的冲刺的速度
    public float crouchWalkSpeed = 5f;
    public float crouchSprintSpeed = 5f;
    // 当前在冲刺
    [HideInInspector]
    public bool m_isSprinting;
    // 当前是否蹲下了
    [HideInInspector]
    public bool m_isCrouched;
    // 当前是否正在蹲下/起立 用于协程控制
    [HideInInspector]
    public bool m_isDoCrouching;

    // 对CharacterController的引用
    private CharacterController m_characterController;
    // 对Transform的引用
    private Transform m_characterTransform;
    // 移动的方向
    private Vector3 m_movementDirection;
    // 当前使用哪个速度(Walk or Sprint)
    private float m_currentSpeed;

    //------------------------------------------------------动画----------------------------------------------------------------//
    private Animator m_characterAnimator;
    private float m_velocity;

    private void Start() 
    {
    
    
        // 运动
        m_currentSpeed = walkSpeed;
        m_characterController = GetComponent<CharacterController>();
        m_characterTransform = transform;
        m_isCrouched = false;
        m_isDoCrouching = false;
        originHeight = m_characterController.height;

        // 动画
        m_characterAnimator = GetComponentInChildren<Animator>();
    }

    private void Update()
    {
    
    
        
        //触地判断
        if (m_characterController.isGrounded) 
        {
    
    
            //------------------------------------------运动--------------------------------------------------------//
            // 获得输入
            float temp_Horizontal = Input.GetAxis("Horizontal");
            float temp_Vertical = Input.GetAxis("Vertical");

            // 将方向从对象的自身坐标系转换为世界坐标系
            m_movementDirection = m_characterTransform.TransformDirection(
                new Vector3(temp_Horizontal, 0, temp_Vertical));
            
            // 这是为了防止Horizontal和Vertical都有输入时
            // 出现movementDirection模长大于1的情况
            // 保证速度的恒定
            if(m_movementDirection.sqrMagnitude > 1)
            {
    
    
                m_movementDirection = m_movementDirection.normalized;
            }

            // SimpleMove是带有重力效果的 目前不使用
            //characterController.SimpleMove(temp_MovementDirection * Time.deltaTime * speed); 

            // 是否按着冲刺键并且拥有运动
            // 运动不考虑y轴的跳跃
            if (Input.GetKey(KeyCode.LeftShift) && 
                m_movementDirection.x != 0 &&
                m_movementDirection.z != 0)
            {
    
    
                // 如果条件符合 m_isSprinting设置为true 状态为冲刺
                m_isSprinting = true; 
            }
            else
            {
    
    
                m_isSprinting = false;
            }

            // 是否按下跳跃键
            if (Input.GetButtonDown("Jump"))
            {
    
    
                m_movementDirection.y = jumpHeight;
            }

            // 是否按下下蹲键
            if (Input.GetKeyDown(KeyCode.C))
            {
    
    
                // 正在下蹲或者起立时 不能更新状态 防止BUG
                if(!m_isDoCrouching)
                {
    
    
                    //当前是否已经蹲下了
                    float temp_currentHeight =
                        (m_isCrouched) ? originHeight : crouchHeight;
                    // 正在下蹲/起立
                    m_isDoCrouching = true;
                    // 因为下蹲(起立)是一个过程 所以用协程
                    StartCoroutine(DoCrouch(temp_currentHeight));
                    // 将下蹲状态设置为反状态
                    m_isCrouched = !m_isCrouched;
                }
            }

            // 是否是下蹲状态
            if(m_isCrouched)
            {
    
    
                m_currentSpeed = (m_isSprinting) ? crouchSprintSpeed : crouchWalkSpeed;
            }
            else
            {
    
    
                m_currentSpeed = (m_isSprinting) ? sprintingSpeed : walkSpeed;
            }
            //------------------------------------------------------------------------------------------------------//

            //------------------------------------------动画--------------------------------------------------------//

            Vector3 temp_velocity = m_characterController.velocity;
            temp_velocity.y = 0; //过滤掉跳跃带来的影响
            m_velocity = temp_velocity.magnitude; 
            m_characterAnimator.SetFloat("Velocity", m_velocity, 0.1f, Time.deltaTime);
            //------------------------------------------------------------------------------------------------------//
        }

        // 重力效果
        m_movementDirection.y -= grivaty * Time.deltaTime;

        // 实际的移动
        // 这样写有一些问题
        // 比如重力受移动速度的影响
        m_characterController.Move(m_movementDirection * Time.deltaTime * m_currentSpeed);
    }

    // 下蹲/站立的协程
    private IEnumerator DoCrouch(float target)
    {
    
    
        while(Mathf.Abs(m_characterController.height - target) > 0.0001f)
        {
    
    
            float temp_currentHeight = 0;
            yield return null;
            m_characterController.height = Mathf.SmoothDamp(
                m_characterController.height,
                target, 
                ref temp_currentHeight, 
                Time.deltaTime * 3f);
        }
        m_isDoCrouching = false; //对吗?目前看来没问题
    }
}

基于Rigidbody

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

public class FPMovement_Rigidbody : MonoBehaviour
{
    
    
    public float speed;
    public float gravity;
    public float jumpHeight;

    private Rigidbody m_rigidbody;
    private bool m_isGrounded;
    private bool isJumping;

    void Start()
    {
    
    
        m_rigidbody = this.GetComponent<Rigidbody>();
    }

    private void Update()
    {
    
    
        // 只有在地面上时才能起跳
        if(m_isGrounded)
        {
    
    
            // 跳跃逻辑判断
            if (Input.GetButtonDown("Jump"))
            {
    
    
                Debug.Log("Jump!");
                isJumping = true;
            }
        }
        else
        {
    
    
            // 在空中时,要将isJumping设置为false
            isJumping = false;
        }
    }

    void FixedUpdate()
    {
    
    
        // 玩家只有触地时才能移动和跳跃
        if (m_isGrounded)
        {
    
    
            float horizontal = Input.GetAxis("Horizontal");
            float vertical = Input.GetAxis("Vertical");

            Vector3 currentDirection = new Vector3(horizontal, 0, vertical); //这个坐标系是局部的,需要转换到世界坐标后再使用
            currentDirection = this.transform.TransformDirection(currentDirection.normalized); //normalized是为了保证速度大小的一致
            currentDirection *= speed;

            Vector3 currentVelocity = m_rigidbody.velocity; //获取当前的速度
            Vector3 velocityChange = currentDirection - currentVelocity; //用户输入的速度减去当前速度来获取实际需要的速度
            velocityChange.y = 0;  //暂时不计算这个值

            m_rigidbody.AddForce(velocityChange, ForceMode.VelocityChange);

            // 跳跃
            if(isJumping)
            {
    
    
                m_rigidbody.velocity = new Vector3(velocityChange.x, CalculateJumpHeightSpeed(), velocityChange.z);
            }
        }

        // 重力
        m_rigidbody.AddForce(Vector3.down * gravity);
    }

    // 计算要达到跳跃高度需要的y轴速度
    private float CalculateJumpHeightSpeed()
    {
    
    
        return Mathf.Sqrt(2 * gravity * jumpHeight);
    }

    // 这只是一个相当简单的方法
    private void OnCollisionEnter(Collision collision)
    {
    
    
        m_isGrounded = true;
    }

    //private void OnCollisionStay(Collision collision)
    //{
    
    
    //    m_isGrounded = true;
    //}

    private void OnCollisionExit(Collision collision)
    {
    
    
        m_isGrounded = false;
    }
}

猜你喜欢

转载自blog.csdn.net/qq_37856544/article/details/112813129