[유니티] 3인칭 컨트롤러 구현 시도(넷째, 모바일 컨트롤 ②)

[유니티] 3인칭 컨트롤러 구현 시도(넷째, 모바일 컨트롤 ②)

[설명] 이 3인칭 컨트롤러는 유니티스타 에셋의 3인칭 컨트롤러를 재현한 것으로, 필요한 경우 에셋을 직접 다운받아 학습할 수 있습니다. 저는 개인적으로 바퀴를 재발명하는 행위를 좋아하지 않는데, 공식 컨트롤러는 제가 사용했을 때 만족스럽지 못한 버그가 있습니다. 그래도 컨트롤러는 나에게 꽤 좋아 보이며 매끄러운 애니메이션은 불가능하지만 개인 프로젝트는 먼저 컨트롤러를 움직이는 방법을 알아내야 합니다.
[버전] 이 프로젝트는 Unity [2021.3lts] 버전을 기반으로 합니다.

방향 수정

이동 방법에서 변위 각도를 제어했던 것을 기억하십니까?
이제 카메라를 얻었으니 이제 카메라의 각도만 계산하면 됩니다. 우리의 방향과 카메라는 연결 효과를 얻었습니다.

_targetRotation = Mathf.Atan2(currentInput.x, currentInput.z) * Mathf.Rad2Deg 
		+ _mainCamera.transform.eulerAngles.y;

애니메이션 컨트롤의 일부를 내 자신의 코드에 추가했는데 나중에 설명하겠습니다. 이는 값의 컨트롤일 뿐입니다.

속도 확장

다음으로 달리기와 같은 속도를 만들 수 있습니다. 다음 두 가지 방법으로 만들 수 있습니다:
Ⅰ. 다른 상태에 대한 bool 값을 만듭니다. 상태가 활성화되면 bool 값을 직접 활성화합니다.
Ⅱ.idle, walk, run과 같이 상호 배타적인 상태의 그룹에 대한 열거형을 생성하고 이에 대한 상호 배타적인 상태의 그룹을 생성합니다.
솔직히 두 방법의 차이는 크게 없다고 생각하는데 열거형에 익숙하지 않다면 첫 번째 방법으로도 충분합니다. 두 번째 유형은 주로 조금 더 선명하게 보는 것입니다. 여전히 같은 문장, 익숙해 졌는지 여부에 따라 장단점이 없습니다.

public enum PlayerActionState
{
    
    
    Idle,
    Walk,
    Run
}

public PlayerActionState playerActionState = PlayerActionState.Idle;

이제 이 상태를 제어할 위치를 고려합니다. 사실 이전 입력컨트롤 부분에 넣으면 되는데 결국 우리 캐릭터의 상태는 대부분 입력값에 의해 결정되는데 거기에 변경을 한 후에는 캐릭터가 Move 에서 넘어지는 정보만 합치면 판단이 됩니다 캐릭터의 상태. 물론 입력 컨트롤에서 외부 상태 데이터도 수행했으므로 Move에서도 직접 볼 수 있습니다. 우리는 먼저 각 이동 실행이 시작될 때 캐릭터의 현재 상태를 판단합니다.

private void PlayerStateJudge()
{
    
    
	playerActionState = PlayerActionState.Idle;
	if (_inputsMassage.move!=Vector2.zero)
	{
    
    
		playerActionState = PlayerActionState.Walk;
		if (_inputsMassage.run)
			playerActionState = PlayerActionState.Run;
	}
}

판단이 완료된 후 상태에 따라 _currentSpeed에 값을 할당합니다.

_currentSpeed = playerActionState switch
{
    
    
	PlayerActionState.Idle => 0f,
	PlayerActionState.Walk => walkSpeed,
	PlayerActionState.Run => _runSpeed,
	_ => 0f
};

실행을 위한 것입니다.

새로운 상태

우리는 캐릭터에 웅크린 상태를 추가할 것입니다.물론 이 효과는
우리가 bool을 통해 제어하는 ​​상대적으로 독립적인 상태이기 때문에 애니메이션 변경이 포함됩니다.
우리는 Move 초기에 판단(모든 상태를 판단할 때 종속성 문제를 고려하십시오. 종속성 판단을 우선시할 필요는 없습니다)한
다음 실행 상태와 스쿼팅 상태의 우선 순위를 고려합니다. 우선 순위가 높을수록 쪼그리고
앉은 후 다음 상태로 들어가면 달릴 수 없기 때문에 고정 속도는 쪼그려 걷는 속도라고 생각합니다. 스쿼트 상태를 종료할 때는 수행되지 않습니다.

_currentSpeed = playerActionState switch
{
    
    
	PlayerActionState.Idle => 0f,
	PlayerActionState.Walk => walkSpeed,
	PlayerActionState.Run => _runSpeed,
	_ => 0f
};
if (_isCrouch) 
	_currentSpeed = _crouchSpeed;

속도의 점진적인 변화

현재 우리의 속도는 직접적으로 변화하고 있습니다. 친구가 테스트를 위해 애니메이션에 연결하면 속도가 직접 변경되고 애니메이션 사이에 "점프"가 있음을 알 수 있습니다. 이 속도를 윤활하기 위해서는 점차 속도를 바꿔야 하는데
, 수치해석을 공부했다면 이것이 보간이라는 것을 알게 될 것이다. 그런 다음 보간할 때 보간 함수를 만들기 위해 두 점을 가져와야 합니다. 그렇다면 이 두 점은 무엇일까요? 현재 속도와 변경할 속도입니다.
먼저 현재 속도를 기록합니다.

//玩家当前水平速度的参考
float currentHorizontalSpeed = new Vector3(_characterController.velocity.x, 
        0.0f, _characterController.velocity.z).magnitude;//偏离度,保证目标速度与目前速度相差大才可以插值,避免小幅度的抽搐
float speedOffset = 0.1f;

여기서 크기는 정확한 길이입니다.(이전에 속도와 벡터의 문제에 대해 이야기했는데 여기서는 크기만 필요합니다.)
그러면 의미론적 반복이 발생하므로 위에서 판단한 속도는 targetSpeed가 됩니다.
우리는 편차의 정도를 판단하기 시작합니다. 목표 값과 현재 값의 차이가 크면 원활하게 보간 변경 프로세스가 필요하며 편차 정도 미만인 경우에는 그렇지 않다고 생각합니다. 직접 변경 사항을 볼 수 있으므로 직접 변경됩니다.

//判断偏离度
if (currentHorizontalSpeed < targetSpeed - speedOffset ||
	currentHorizontalSpeed > targetSpeed + speedOffset)
{
    
    
	_currentSpeed = Mathf.Lerp(currentHorizontalSpeed, targetSpeed ,Time.deltaTime * SpeedChangeRate);
	//四舍五入到小数点后3位
	_currentSpeed = Mathf.Round(_currentSpeed * 1000f) / 1000f;
}
else
{
    
    
	_currentSpeed = targetSpeed;
}

이 시점에서 모바일 제어 부분은 기본적으로 완료됩니다. 애니메이션 시스템에 연결 후 애니메이션 흔들림 문제가 발생합니다.이 문제가 있거나이 문제에 대한 해결책이 있으면 아래 메시지를 남겨주세요.

코드

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

public class ThirdPlayerMoveController : MonoBehaviour
{
    
    
    CharacterController _characterController;
    PlayerInput _playerInput;
    PlayerInputsMassage _inputsMassage;
    GameObject _mainCamera;
    Animator _anim;

    public PlayerActionState playerActionState = PlayerActionState.Idle;

    [Header("相机设置")]
    public GameObject _cinemachineFollowTarget;
    float _cinemachineTagertX;
    float _cinemachineTagertY;
    [Tooltip("相机仰角")]
    public float TopClamp = 70.0f;
    [Tooltip("相机俯角")]
    public float BottomClamp = -30.0f;
    [Tooltip("额外的度数覆盖摄像头。有用的微调相机位置时,锁定")]
    public float CameraAngleOverride = 0.0f;

    [Header("玩家设置")]
    [Tooltip("这将决定普通行走时的速度")]
    public float walkSpeed = 1.5f;
    [Tooltip("这将决定跑步时的速度")]
    public float _runSpeed = 5.0f;
    private bool _isCrouch = false;
    [Tooltip("这将决定蹲下行走的速度")]
    public float _crouchSpeed = 1.0f;
    [Tooltip("这将决定不同状态速度间切换的速度")]
    public float SpeedChangeRate = 10.0f;

    private float _currentSpeed;
    private float _targetRotation = 0.0f;
    [Tooltip("角色光滑旋转时间")]
    private float RotationSmoothTime = 0.12f;
    [Tooltip("在角色光滑旋转过程中的速度")]
    private float _rotationVelocity;

    private float _threshold = 0.01f;

    private bool IsCurrentDeviceMouse
    {
    
    
        get {
    
     return _playerInput.currentControlScheme == "KeyboardMouse"; }
    }

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

    // Start is called before the first frame update
    void Start()
    {
    
    
        _characterController = GetComponent<CharacterController>();
        _inputsMassage = GetComponent<PlayerInputsMassage>();
        _playerInput = GetComponent<PlayerInput>();
        _anim = GetComponentInChildren<Animator>();
    }

    private void FixedUpdate()
    {
    
    
        Move();
    }

    private void LateUpdate()
    {
    
    
        CameraRotation();
    }

    /// <summary>
    /// 相机追踪点的控制
    /// </summary>
    private void CameraRotation()
    {
    
    
        if(_inputsMassage.look.sqrMagnitude>_threshold)//look值大于误差代表有输入
        {
    
    
            float deltaTimeMultiplier = IsCurrentDeviceMouse ? 1f : Time.deltaTime;

            _cinemachineTagertX += _inputsMassage.look.x * deltaTimeMultiplier;
            _cinemachineTagertY += _inputsMassage.look.y * deltaTimeMultiplier;
        }
        _cinemachineTagertX = ClampAngle(_cinemachineTagertX, float.MinValue, float.MaxValue);
        _cinemachineTagertY = ClampAngle(_cinemachineTagertY, BottomClamp, TopClamp);

        _cinemachineFollowTarget.transform.rotation = Quaternion.Euler((-_cinemachineTagertY - CameraAngleOverride) * Settings.mouseYmoveTimes,
                _cinemachineTagertX * Settings.mouseXmoveTimes, 0.0f);
    }

    private void Move()
    {
    
    
        _isCrouch = _inputsMassage.crouch;
        //在这里进行状态的判断
        PlayerStateJudge();

        //首先将移动速度赋予临时变量,考虑到有可能在其他地方使用,我们将其存储起来
        //_currentSpeed = walkSpeed;(转换为更加完善的速度控制)
        float targetSpeed = playerActionState switch
        {
    
    
            PlayerActionState.Idle => 0f,
            PlayerActionState.Walk => walkSpeed,
            PlayerActionState.Run => _runSpeed,
            _ => 0f
        };
        if (_isCrouch) targetSpeed = _crouchSpeed;

        //玩家当前水平速度的参考
        float currentHorizontalSpeed = new Vector3(_characterController.velocity.x, 0.0f, _characterController.velocity.z).magnitude;
        //偏离度,保证目标速度与目前速度相差大才可以插值,避免小幅度的抽搐
        float speedOffset = 0.1f;
        //判断偏离度
        if (currentHorizontalSpeed < targetSpeed - speedOffset ||
                currentHorizontalSpeed > targetSpeed + speedOffset)
        {
    
    
            _currentSpeed = Mathf.Lerp(currentHorizontalSpeed, targetSpeed ,Time.deltaTime * SpeedChangeRate);
            //四舍五入到小数点后3位
            _currentSpeed = Mathf.Round(_currentSpeed * 1000f) / 1000f;
        }
        else
        {
    
    
            _currentSpeed = targetSpeed;
        }

        //判断是否进行移动输入
        if (_inputsMassage.move == Vector2.zero) _currentSpeed = 0;

        var currentInput = new Vector3(_inputsMassage.move.x, 0, _inputsMassage.move.y).normalized;

        //单位向量的方向,或者说位移方向
        if (_inputsMassage.move!=Vector2.zero)
        {
    
    
            _targetRotation = Mathf.Atan2(currentInput.x, currentInput.z) * Mathf.Rad2Deg + _mainCamera.transform.eulerAngles.y;


            #region 在位移过程中的转向
            float rotation = Mathf.SmoothDampAngle(transform.eulerAngles.y, _targetRotation, ref _rotationVelocity,
                RotationSmoothTime);
            transform.rotation = Quaternion.Euler(0.0f, rotation, 0.0f);
            #endregion

        }

        Vector3 targetDir = Quaternion.Euler(0.0f, _targetRotation, 0.0f) * Vector3.forward;

        _characterController.Move(targetDir.normalized * _currentSpeed * Time.deltaTime);
        //TODO:这里的Move可以执行垂直方向的速度,直接加上垂直的Vector就可以

        _anim.SetFloat("Speed", _currentSpeed);
        _anim.SetBool("Crouch", _isCrouch);
    }

    /// <summary>
    /// 限制角度
    /// </summary>
    /// <param name="lfAngle"></param>
    /// <param name="lfMin"></param>
    /// <param name="lfMax"></param>
    /// <returns></returns>
    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);
    }

    /// <summary>
    /// 对玩家状态进行判断
    /// </summary>
    private void PlayerStateJudge()
    {
    
    
        playerActionState = PlayerActionState.Idle;
        if (_inputsMassage.move!=Vector2.zero)
        {
    
    
            playerActionState = PlayerActionState.Walk;
            if (_inputsMassage.run)
                playerActionState = PlayerActionState.Run;
        }

    }

}

Supongo que te gusta

Origin blog.csdn.net/weixin_52540105/article/details/127714469
Recomendado
Clasificación