【Unity】基于Cinemachine实现的第三人称摄像机

2022-04-18  本文已影响0人  木心Sepith

实现了人物基本的走,跑,跳,滞空状态,并有物理模拟
相机通过设置Cinemachine,实现了旋转,锁定,缩放,并且自带摄像机碰撞,不会穿墙

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

public class PlayerController : MonoBehaviour
{
    [Header("Player")]
    [Tooltip("移动速度")]
    public float MoveSpeed = 2.0f;
    [Tooltip("加速移动速度")]
    public float SprintSpeed = 5.335f;
    [Tooltip("旋转速度")]
    [Range(0.0f, 0.3f)]
    public float RotationSmoothTime = 0.12f;
    [Tooltip("加速度")]
    public float SpeedChangeRate = 10.0f;

    [Space(10)]
    [Tooltip("跳跃高度")]
    public float JumpHeight = 1.2f;
    [Tooltip("重力,默认为 -9.81f")]
    public float Gravity = -15.0f;

    [Space(10)]
    [Tooltip("跳跃间隔时间")]
    public float JumpTimeout = 0.50f;
    [Tooltip("Time required to pass before entering the fall state. Useful for walking down stairs")]
    public float FallTimeout = 0.15f;

    [Header("Player Grounded")]
    [Tooltip("当前是否在地面")]
    public bool Grounded = true;
    [Tooltip("粗糙地面偏移量")]
    public float GroundedOffset = -0.14f;
    [Tooltip("地面检测的半径,应该和CharacterController的半径匹配")]
    public float GroundedRadius = 0.28f;
    [Tooltip("地面有哪些层")]
    public LayerMask GroundLayers;

    [Header("CinemachineTarget")]
    [Tooltip("虚拟相机目标")]
    public GameObject CinemachineCameraTarget;
    [Tooltip("最大仰角")]
    public float TopClamp = 80;
    [Tooltip("最小俯角")]
    public float BottomClamp = -80;
    [Tooltip("额外的角度来调整摄像机,用与当相机锁住的时候")]
    public float CameraAngleOverride = 0.0f;
    [Tooltip("相机灵敏度")]
    public float MouseSensitivity = 200;

    [Tooltip("虚拟相机")]
    public Cinemachine3rdPersonFollow CinemachineVirtualCamera;
    [Tooltip("相机缩放")]
    public float CameraDistance = 3;
    [Tooltip("相机缩放")]
    public float CameraDistanceRatio = 5;
    [Tooltip("相机缩放最小距离")]
    public float CameraDistanceMin = 2;
    [Tooltip("相机缩放最大距离")]
    public float CameraDistanceMax = 8;

    [Tooltip("相机锁")]
    public bool LockCameraPosition = false;

    // cinemachine
    private float _cinemachineTargetYaw;
    private float _cinemachineTargetPitch;
    private float _cinemachineTargetDistance;

    // player
    private float _speed;
    private float _animationBlend;
    private float _targetRotation = 0.0f;
    private float _rotationVelocity;
    private float _verticalVelocity;
    private float _terminalVelocity = 53.0f;

    // state
    private bool isRun = false;
    private bool isJump = false;

    // timeout deltatime
    private float _jumpTimeoutDelta;
    private float _fallTimeoutDelta;

    // animation IDs
    private int _animIDSpeed;
    private int _animIDGrounded;
    private int _animIDJump;
    private int _animIDFreeFall;
    private int _animIDMotionSpeed;

    private Animator _animator;
    private CharacterController _controller;
    private GameObject _mainCamera;

    private bool _hasAnimator;

    private void Awake()
    {
        // get a reference to our main camera
        if (_mainCamera == null)
        {
            _mainCamera = GameObject.FindGameObjectWithTag("MainCamera");
        }
    }

    private void Start()
    {
        _hasAnimator = TryGetComponent(out _animator);
        _controller = GetComponent<CharacterController>();
        var cinemachine = FindObjectOfType<CinemachineVirtualCamera>();
        CinemachineVirtualCamera = cinemachine.GetCinemachineComponent<Cinemachine3rdPersonFollow>();

        AssignAnimationIDs();

        // reset our timeouts on start
        _jumpTimeoutDelta = JumpTimeout;
        _fallTimeoutDelta = FallTimeout;

        Cursor.lockState = CursorLockMode.Locked;
    }

    private void Update()
    {
        if (Input.GetKey(KeyCode.LeftShift))
        {
            isRun = true;
        }

        if (Input.GetKeyUp(KeyCode.LeftShift))
        {
            isRun = false;
        }

        if (Input.GetKeyDown(KeyCode.Space))
        {
            isJump = true;
        }

        if (Input.GetKeyDown(KeyCode.L))
        {
            LockCameraPosition = !LockCameraPosition;
        }

        _cinemachineTargetDistance -= Input.GetAxis("Mouse ScrollWheel") * CameraDistanceRatio;
        _cinemachineTargetDistance = Mathf.Clamp(_cinemachineTargetDistance, CameraDistanceMin, CameraDistanceMax);
    }

    private void FixedUpdate()
    {
        _hasAnimator = TryGetComponent(out _animator);

        JumpAndGravity();
        GroundedCheck();
        Move();
    }
    public float cameraDistanceLerpSpeed = 1;
    private void LateUpdate()
    {
        CameraPosition();
        CameraRotation();
    }

    private void AssignAnimationIDs()
    {
        _animIDSpeed = Animator.StringToHash("Speed");
        _animIDGrounded = Animator.StringToHash("Grounded");
        _animIDJump = Animator.StringToHash("Jump");
        _animIDFreeFall = Animator.StringToHash("FreeFall");
        _animIDMotionSpeed = Animator.StringToHash("MotionSpeed");
    }

    /// <summary>
    /// 地面检测
    /// </summary>
    private void GroundedCheck()
    {
        // 得到检测点位置
        Vector3 spherePosition = new Vector3(transform.position.x, transform.position.y - GroundedOffset, transform.position.z);

        // 检测结果
        Grounded = Physics.CheckSphere(spherePosition, GroundedRadius, GroundLayers, QueryTriggerInteraction.Ignore);

        // 设置Animator
        if (_hasAnimator)
        {
            _animator.SetBool(_animIDGrounded, Grounded);
        }
    }

    private void CameraPosition()
    {
        CameraDistance = Mathf.Lerp(CameraDistance, _cinemachineTargetDistance, Time.deltaTime * cameraDistanceLerpSpeed);
        CinemachineVirtualCamera.CameraDistance = CameraDistance;
    }

    private void CameraRotation()
    {
        var mouseX = Input.GetAxis("Mouse X") * MouseSensitivity;
        var mouseY = Input.GetAxis("Mouse Y") * MouseSensitivity;

        _cinemachineTargetYaw += mouseX * Time.deltaTime;
        _cinemachineTargetPitch -= mouseY * Time.deltaTime;

        _cinemachineTargetYaw = ClampAngle(_cinemachineTargetYaw, float.MinValue, float.MaxValue);
        _cinemachineTargetPitch = ClampAngle(_cinemachineTargetPitch, BottomClamp, TopClamp);
        var targetRot = Quaternion.Euler(_cinemachineTargetPitch, _cinemachineTargetYaw, 0);
        CinemachineCameraTarget.transform.rotation = targetRot;
    }

    /// <summary>
    /// 角色移动
    /// </summary>
    private void Move()
    {
        // 获取到当前的输入向量
        Vector3 curInput = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));

        // 根据是否按下加速键,获得最终速度值
        float targetSpeed = isRun ? SprintSpeed : MoveSpeed;

        // 如果输入的数值太小则不计算
        if (curInput == Vector3.zero) targetSpeed = 0.0f;

        // 获取玩家当前在水平面上的单位速度
        float currentHorizontalSpeed = new Vector3(_controller.velocity.x, 0.0f, _controller.velocity.z).magnitude;

        float speedOffset = 0.1f;
        //float inputMagnitude = _input.analogMovement ? _input.move.magnitude : 1f;

        // 模拟加速过程
        if (currentHorizontalSpeed < targetSpeed - speedOffset || currentHorizontalSpeed > targetSpeed + speedOffset)
        {
            // 模拟一个非线性的加速过程
            _speed = Mathf.Lerp(currentHorizontalSpeed, targetSpeed * curInput.magnitude, Time.fixedDeltaTime * SpeedChangeRate);

            // 精确到小数点后3位
            _speed = Mathf.Round(_speed * 1000f) / 1000f;
        }
        else
        {
            _speed = targetSpeed;
        }

        // 设置动画的速度
        _animationBlend = Mathf.Lerp(_animationBlend, targetSpeed, Time.fixedDeltaTime * SpeedChangeRate);

        // 获取玩家的输入单位水平向量
        Vector3 inputDirection = new Vector3(curInput.x, 0.0f, curInput.z).normalized;

        // Vector2's != 更节省性能
        if (curInput != Vector3.zero)
        {
            _targetRotation = Mathf.Atan2(inputDirection.x, inputDirection.z) * Mathf.Rad2Deg + _mainCamera.transform.eulerAngles.y;
            float rotation = Mathf.SmoothDampAngle(transform.eulerAngles.y, _targetRotation, ref _rotationVelocity, RotationSmoothTime);

            // 旋转到相对于摄像机的方向
            transform.rotation = Quaternion.Euler(0.0f, rotation, 0.0f);
        }

        // 获取移动的目标方向
        Vector3 targetDirection = Quaternion.Euler(0.0f, _targetRotation, 0.0f) * Vector3.forward;

        // 移动玩家,水平面的移动 + 垂直方向的移动
        _controller.Move(targetDirection.normalized * (_speed * Time.fixedDeltaTime) + new Vector3(0.0f, _verticalVelocity, 0.0f) * Time.deltaTime);

        // 设置Animator
        if (_hasAnimator)
        {
            _animator.SetFloat(_animIDSpeed, _animationBlend);
            _animator.SetFloat(_animIDMotionSpeed, curInput.magnitude);
        }
    }

    /// <summary>
    /// 角色跳跃
    /// </summary>
    private void JumpAndGravity()
    {
        // 在地面上
        if (Grounded)
        {
            // 重置下落时间
            _fallTimeoutDelta = FallTimeout;

            // 设置Animator
            if (_hasAnimator)
            {
                _animator.SetBool(_animIDJump, false);
                _animator.SetBool(_animIDFreeFall, false);
            }

            // 快速下落
            if (_verticalVelocity < 0.0f)
            {
                _verticalVelocity = -2f;
            }

            // 如果还在跳跃
            if (isJump && _jumpTimeoutDelta <= 0.0f)
            {
                // 求出垂直速度
                // the square root of H * -2 * G = how much velocity needed to reach desired height
                _verticalVelocity = Mathf.Sqrt(JumpHeight * -2f * Gravity);

                // 设置Animator
                if (_hasAnimator)
                {
                    _animator.SetBool(_animIDJump, true);
                }
            }

            // 跟新跳跃的状态数值
            if (_jumpTimeoutDelta >= 0.0f)
            {
                _jumpTimeoutDelta -= Time.fixedDeltaTime;
            }
        }
        //在半空中
        else
        {
            // 重置跳跃时间
            _jumpTimeoutDelta = JumpTimeout;

            // 如果还在下落
            if (_fallTimeoutDelta >= 0.0f)
            {
                _fallTimeoutDelta -= Time.fixedDeltaTime;
            }
            else
            {
                // 设置Animator
                if (_hasAnimator)
                {
                    _animator.SetBool(_animIDFreeFall, true);
                }
            }

            // 在半空中不能跳跃
            isJump = false;
        }

        // 跟新速度为 Vt = V0 + a * t
        if (_verticalVelocity < _terminalVelocity)
        {
            _verticalVelocity += Gravity * Time.fixedDeltaTime;
        }
    }

    private static float ClampAngle(float lfAngle, float lfMin, float lfMax)
    {
        if (lfAngle < -360f) lfAngle += 360f;
        if (lfAngle > 360f) lfAngle -= 360f;
        return Mathf.Clamp(lfAngle, lfMin, lfMax);
    }

    private void OnDrawGizmosSelected()
    {
        Color transparentGreen = new Color(0.0f, 1.0f, 0.0f, 0.35f);
        Color transparentRed = new Color(1.0f, 0.0f, 0.0f, 0.35f);

        if (Grounded) Gizmos.color = transparentGreen;
        else Gizmos.color = transparentRed;

        // when selected, draw a gizmo in the position of, and matching radius of, the grounded collider
        Gizmos.DrawSphere(new Vector3(transform.position.x, transform.position.y - GroundedOffset, transform.position.z), GroundedRadius);
    }

}

改编自Unity商城的官方第三人称视角
https://assetstore.unity.com/packages/essentials/starter-assets-third-person-character-controller-196526

上一篇下一篇

猜你喜欢

热点阅读