ThirdPersonController.cs Refactoring

devKyoun·2024년 10월 15일
0

Unity

목록 보기
5/27
post-thumbnail

Setting

Unity Package : [ Starter Assets ] Third Person Controller 사용
내가 구매한 Model 적용

내가 구현 할 게임은 3인칭 백 뷰 장르이다
WASD로 이동, 마우스를 고정시켜 마우스를 이용해 카메라를 회전하고
플레이어가 이동 시 카메라가 바라보는 방향에 따라 그 위치로 플레이어도 회전시키려고 한다

예) 다크소울

Refactoring

ThirdPesonController.cs

에셋 내에 ThirdPersonController.cs를 대폭 수정했다

  • 카멜 표기법이 누락돼서 가독성 떨어짐
    ➡️ 카멜 표기법 적용
  • ThirdPersonController.cs 하나에 모든 기능 다 들어가 있음
    ➡️ PlayerManager(main), PlayerMoveHandler, GravityGroundHandler 로 분류

StarterAssetsInput.cs

StarterAssetsInput.cs는 SendMessage 형태로 코드가 구현 돼 있었는데 개인적으로 나는 Invoke Unity Event가 커스터마이징하는데 더 편해서 수정함

Refactoring Scripts

StarterAssetsInput.cs

private Vector2 move;

{....}

public void OnMove(InputAction.CallbackContext context)
{
	MoveInput(context.ReadValue<Vector2>());
}


{....}

// 실질적인 작업을 하는 함수들 ( Joy Stick 에서도 불려지는 애들이다. )

public void MoveInput(Vector2 newMoveDirection)
{
	move = newMoveDirection;
} 

{....}

// PlayerManager에서부터 변수로 바로 접근 하는 대신 이 함수로 접근 
public Vector2 GetMove()
{
	return move;
}

GravityGroundHandler.cs


{....}

 public 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.SetBool(_animIDGrounded, grounded);

 }

 public void ApplyGravity()
 {
     if (grounded)
     {
         _fallTimeoutDelta = fallTimeout;
         animator.SetBool(_animIDFreeFall, false);

         if (_verticalVelocity < 0.0f)
         {
             _verticalVelocity = -2f;
         }
     }
     else
     {
         if (_fallTimeoutDelta >= 0.0f)
         {
             _fallTimeoutDelta -= Time.deltaTime;
         }
         else
         {
             animator.SetBool(_animIDFreeFall, true);
         }
     }

     if (_verticalVelocity < _terminalVelocity)
     {
         _verticalVelocity += gravity * Time.deltaTime;
     }
 }

개선 및 변경

  • Jump 삭제
    나중에 Jump 기능 대신 Rolling(Dodge)로 바꿀거임
  • if has animator 라는 조건부가 쓸데없이 붙은거 삭제함
    애시당초 로직 자체가 animator 없으면 처음부터 돌아가지도 않아서 필수 컴포넌트로 설정했음
    그래서 저 조건부는 굳이 할 필요없었음

PlayerMoveHandler.cs

 public void Move()
{
  // Target Speed 결정
  if(_input.GetMove() == Vector2.zero)
  {
      targetSpeed = 0.0f;
  }
  else if (!_input.IsSprinting())
  {
      targetSpeed = runSpeed;
  }
  else
  {
      targetSpeed = sprintSpeed;
  }


  if (targetSpeed > 0.0f)
  {

      // a reference to the players current horizontal velocity
      currentHorizontalSpeed = new Vector3(characterController.velocity.x, 0.0f, characterController.velocity.z).magnitude;

      speedOffset = 0.1f;
      inputMagnitude = _input.IsAnalogue() ? _input.GetMove().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
          speed = Mathf.Lerp(currentHorizontalSpeed, targetSpeed * inputMagnitude,
              Time.deltaTime * speedChangeRate);

          // round speed to 3 decimal places
          speed = Mathf.Round(speed * 1000f) / 1000f;
      }
      else
      {
          speed = targetSpeed;
      }


      animationBlend = Mathf.Lerp(animationBlend, targetSpeed, Time.deltaTime * speedChangeRate);
      if (animationBlend < 0.01f) animationBlend = 0f;
  }

  else
  {
      speed = 0;
      //2배 빠르게 멈추기
      animationBlend = Mathf.Lerp(animationBlend, targetSpeed, Time.deltaTime * speedChangeRate * 2);
      if (animationBlend < 0.01f) animationBlend = 0f;
  }


  // normalise input direction
  Vector3 inputDirection = new Vector3(_input.GetMove().x, 0.0f, _input.GetMove().y).normalized;

  if (_input.GetMove() != Vector2.zero)
  {
      // Rotation
      targetRotation = Mathf.Atan2(inputDirection.x, inputDirection.z) * Mathf.Rad2Deg +
                        mainCamera.transform.eulerAngles.y;
      float rotation = Mathf.SmoothDampAngle(transform.eulerAngles.y, targetRotation, ref rotationVelocity,
          rotationSmoothTime);

      if (!playerManager.LockActivate)
      {
          transform.rotation = Quaternion.Euler(0.0f, rotation, 0.0f);
      }
      // 나중에 락 온 기능 생성 시 활성화
      //else
      //{
      //    transform.rotation = Quaternion.Euler(0.0f, transform.eulerAngles.y, 0.0f);
      //}

  }


  // Move ( rotation 기반으로 무브 )
  Vector3 targetDirection = Quaternion.Euler(0.0f, targetRotation, 0.0f) * Vector3.forward;
  characterController.Move(targetDirection.normalized * (speed * Time.deltaTime) +
               new Vector3(0.0f, ggController.VerticalVelocity, 0.0f) * Time.deltaTime);


  animator.SetFloat(_animIDSpeed, animationBlend);
  animator.SetFloat(_animIDMotionSpeed, inputMagnitude);
}

개선 및 변경

  • input이 어떠냐에 따라 targetSpeed를 코드 시작부터 정의 시킴
    본래 코드의 가독성이 너무 떨어져서 수정

  • 움직임이 없으면 rotation 갱신하지 않기
    안하면 멈춰있을때 플레이어가 자꾸 고정된 방향으로 회전함
    그래서 input 입력이 없을때는 rotation 제한 걸어서 해결

  • LockActivate 기능 추가
    현재 포스팅에는 쓰지 않을 예정인데 Lock On 기능 구현 시 필요한 부분
    저 제약을 걸지 않으면 락 온 해도 Enemy 방향을 고정해서 바라보지않게 됨

  • 현재 코드엔 보이지 않으나 최적화를 진행함
    마지막 목차에서 설명하겠음

PlayerManager.cs

 // Update is called once per frame
 void Update()
 {
     //#region Gravity, Ground
     gravityGroundController.GroundedCheck();
     gravityGroundController.ApplyGravity();
     //#endregion

     if (canMove)
     {
         playerMovement.Move();
     }

 }

 private void LateUpdate()
 {
     CameraRotation();
 }

개선 및 변경

  • canMove
    공격이나 닷지 어떤 특정한 행위 시, 움직이면 안되는 경우에 사용하는 제약
    공격 기능 구현하는데 WASD를 누르니 움직이는 상황으로 인해 구현함

  • 카메라 관련 작업은 LateUpdate로
    Jittering 없음

Optimization

PlayerMoveHandler.cs

 public void Move()
{
    before = transform;
    // 변화가 없을때는 Move 탈출
    if(!System.Object.ReferenceEquals(before, null) && !System.Object.ReferenceEquals(after, null) && 
        _input.GetMove() == Vector2.zero && before == after)
    {
        if (animationBlend == 0)
        {
        	Debug.Log("NotMoving");
            return;
        }
    }
    

    // Target Speed 결정
    if(_input.GetMove() == Vector2.zero)
    {
    	...
    }
    
  { .... }
      
  after = transform;

무엇이 최적화 인가?

  • 단순 == 비교의 성능 비용
    초반에 null 확인은 before, after transform 둘 다 null 이라서 처리 해준 것
    ==, != 연산자를 통해 null 을 비교하는 성능비용 보다 ReferenceEquals를 통해 비교하는 성능 비용이 미세하나 2배 가까이 빠르기 때문에 채택
    FakeNull 현상으로 인해서 리소스를 잡아먹음

  • 입력이 없고 before transform과 after transform이 같다면 Move 기능 하지 않음
    입력이 없는데도 Update에서 Move를 호출 해 Move에서 일어나는 많은 연산을 하는 것을 방지 하기 위함
    근데 그러면 PlayerManager에서 입력이 없을때 처음부터 Move호출을 안하면 되는거 아닌가?
    그렇게 하면 Animation Blend 갱신이 안돼서 Animation이 Move->Idle로 전환되지않음
    따로 PlayerManager에서 갱신 해줘야하는데 그렇게하면 독립성이 저하 되고 가독성도 저하 됨

결과

에러 두개는 FootStep이라고 Audio Source를 지정안해줘서 일어나는 에러

움직이지 않을때 움직이고 다시 멈췄을때 Console에 Not Moving이 찍힘

profile
Game Developer

0개의 댓글