C#/Unity 3D

[Unity 3D] [Starter Assets] (3) ThirdPersonController : 이동(Move) 함수 코드 분석

hizzi 2024. 4. 3. 18:30
더보기

목차

  • Move() 역할
  • Move() 변수 설명
  • Move() 함수 코드 분석

 

1. Move() 역할

  Move() 함수는 Update()에서 호출되며, Player의 이동 관련 함수이다.

 

2. Move() 변수 설명

  • Move Speed : Player의 기본 이동 속도
  • Sprint Speed : Player의 달리기(질주) 속도
  • RotationSmoothTime : Player가 이동 방향을 향해 회전하는 속도 (0.0f ~ 0.3f)
  • SpeedChangeRate : 가속/감속으로 '걷기>달리기' or '달리기>걷기'에서 부드러운 모션을 제공

 

3. Move() 함수 코드 분석

(1) 걷기/달리기 속도 설정

// 질주 상태에 따른 질주 속도/이동 속도 조절
float targetSpeed = _input.sprint ? SprintSpeed : MoveSpeed;
            
// 이동하고 있지 않은 경우, 목표 속도는 0으로 설정함
if (_input.move == Vector2.zero) targetSpeed = 0.0f;
  • _input.sprint : StartAssetsInputs에서 달리기(질주) 상태 여부를 check하며 bool 타입이다.
                           달리는 중이면 true, 걷는 중이면 false를 반환한다.
  • _input.sprint에 따라 targetSpeed를 설정한다. 달리는 중이면 SprintSpeed, 걷는 중이면 MoveSpeed이다.
  • _input.move : StartAssetsInputs에서 이동 상태 여부를 check하며 Vector2 타입이다.
                           멈춰있으면 Vector2.zero, 이동 중이면 Vector2.zero가 아닌 값을 반환한다.
  • 멈춘 상태라면 targetSpeed를 0으로 설정한다.

(2) 가속/감속

// Player의 현재 수평 속도
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 * inputMagnitude,
        Time.deltaTime * SpeedChangeRate);

    // 속도를 소수점 이하 3자리까지 반올림
    _speed = Mathf.Round(_speed * 1000f) / 1000f;
}
// 속도 변화가 없는 경우 : 목표 속도 유지
else
{
    _speed = targetSpeed;
}

_animationBlend = Mathf.Lerp(_animationBlend, targetSpeed, Time.deltaTime * SpeedChangeRate);
if (_animationBlend < 0.01f) _animationBlend = 0f;
  • Player의 속도 변화가 있다면, 이동이 자연스럽도록 가속/감속을 준다.
  • Player의 속도 변화가 없다면 목표 속도를 유지한다.
  • Mathf.Lerp : 선형 보간(a: 일차 함수 시작 값/b: 일차 함수 종료 값/t: 얻고 싶은 값의 위치[비율])
                         (ex: a=(0,0), b=(100,100), t=0.5라면 (50,50)을 반환한다.)
  • t는 고정으로 설정되어 있으므로 속도 고정을 따로 설정할 필요가 없다.

(3) 회전(입력 방향)

// 입력 방향 정규화
Vector3 inputDirection = new Vector3(_input.move.x, 0.0f, _input.move.y).normalized;

// 이동 중인 경우, 회전
if (_input.move != Vector2.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;
  • Player가 이동중인 경우, 카메라 회전 시킴
  • Mathf.Atan2 : 탄젠트를 적용하여 각도(Radian) 반환
  • Mathf.Rad2Deg : Radian 값을 Degree로 변환
  • Mathf.SmoothDampAngle : 부드러운 각도 변화
  • Quaternion.Euler : Euler 각을 Quaternion(방향/회전 둘 다 표현)으로 변환
                                  (Euler는 x → y   z 순으로 계산되어 축이 겹치는 현상[짐벌락]이 발생할 수 있음)

(4) Player 이동 및 애니메이션 적용

// Player 이동
_controller.Move(targetDirection.normalized * (_speed * Time.deltaTime) +
                 new Vector3(0.0f, _verticalVelocity, 0.0f) * Time.deltaTime);

// 애니메이션 적용
if (_hasAnimator)
{
    _animator.SetFloat(_animIDSpeed, _animationBlend);
    _animator.SetFloat(_animIDMotionSpeed, inputMagnitude);
}

 

 

(5) 전체 코드

private void Move()
{
    // set target speed based on move speed, sprint speed and if sprint is pressed
    // 질주 상태에 따른 질주 속도/이동 속도 조절
    float targetSpeed = _input.sprint ? SprintSpeed : MoveSpeed;

    // a simplistic acceleration and deceleration designed to be easy to remove, replace, or iterate upon
    // 제거/교체/반복이 자연스럽도록 설계된 가속/감속

    // note: Vector2's == operator uses approximation so is not floating point error prone, and is cheaper than magnitude
    // 참고 : Vector2의 == 연산자는 근사치를 사용하므로 부동 소수점 오류가 발생하지 않음
    // if there is no input, set the target speed to 0
    // 이동하고 있지 않은 경우, 목표 속도는 0으로 설정함
    if (_input.move == Vector2.zero) targetSpeed = 0.0f;

    // a reference to the players current horizontal velocity
    // Player의 현재 수평 속도
    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;

    // accelerate or decelerate to target speed
    // 목표 속도 가속/감속
    if (currentHorizontalSpeed < targetSpeed - speedOffset ||
        currentHorizontalSpeed > targetSpeed + speedOffset)
    {
        // creates curved result rather than a linear one giving a more organic speed change
        // 이동이 자연스럽도록 설계된 가속/감속
        // note T in Lerp is clamped, so we don't need to clamp our speed
        // Mathf.Lerp : 선형 보간(a: 일차 함수 시작 값/b: 일차 함수 종료 값/t: 얻고 싶은 값의 위치[비율])
        // t는 고정으로 설정되어 있으므로 속도 고정 필요 X
        _speed = Mathf.Lerp(currentHorizontalSpeed, targetSpeed * inputMagnitude,
            Time.deltaTime * SpeedChangeRate);

        // round speed to 3 decimal places
        // 속도를 소수점 이하 3자리까지 반올림
        _speed = Mathf.Round(_speed * 1000f) / 1000f;
    }
    // 속도 변화가 없는 경우 : 목표 속도 유지
    else
    {
        _speed = targetSpeed;
    }

    _animationBlend = Mathf.Lerp(_animationBlend, targetSpeed, Time.deltaTime * SpeedChangeRate);
    if (_animationBlend < 0.01f) _animationBlend = 0f;

    // normalise input direction
    // 입력 방향 정규화
    Vector3 inputDirection = new Vector3(_input.move.x, 0.0f, _input.move.y).normalized;

    // note: Vector2's != operator uses approximation so is not floating point error prone, and is cheaper than magnitude
    // 참고 : Vector2의 != 연산자는 근사치를 사용하므로 부동 소수점 오류가 발생하지 않음
    // if there is a move input rotate player when the player is moving
    // 이동 중인 경우, Player가 움직일 때 회전시킴
    if (_input.move != Vector2.zero)
    {
        // Mathf.Atan2 : 탄젠트 적용 -> 각도(Radian) 반환
        // Mathf.Rad2Deg : Radian -> Degree
        // Mathf.SmoothDampAngle : 부드러운 각도 변화
        _targetRotation = Mathf.Atan2(inputDirection.x, inputDirection.z) * Mathf.Rad2Deg +
                          _mainCamera.transform.eulerAngles.y;
        float rotation = Mathf.SmoothDampAngle(transform.eulerAngles.y, _targetRotation, ref _rotationVelocity,
            RotationSmoothTime);

        // rotate to face input direction relative to camera position
        // 카메라 회전 : 입력방향을 향하도록 회전
        transform.rotation = Quaternion.Euler(0.0f, rotation, 0.0f);
    }


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

    // move the player
    _controller.Move(targetDirection.normalized * (_speed * Time.deltaTime) +
                     new Vector3(0.0f, _verticalVelocity, 0.0f) * Time.deltaTime);

    // update animator if using character
    if (_hasAnimator)
    {
        _animator.SetFloat(_animIDSpeed, _animationBlend);
        _animator.SetFloat(_animIDMotionSpeed, inputMagnitude);
    }
}

 

 

 

사담

   순서대로 차례차례 올리려고 노력 중이지만, 이미 개발 후에 글을 작성하려니 순서가 뒤죽박죽이 되고 말았다😅

   Move() 함수 분석 다음 글은 Grounded()와 JumpAndGravity()를 작성해 볼 예정이다!