ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • TopDown Shooting Game / 조준 시스템 구현하기
    스파르타 게임 개발 2024. 5. 21. 01:02

    무기가 바라보는 방향이 마우스 위치를 따라가게 해보자.

     

    먼저 저번에 액션인풋을 만들었고 거기에 Look이라는 Action을 만들어 주었다. 그러니 저번 Move와 같이 PlayerInputController에서 OnLook 함수를 만들어 주고 그걸 실행 하게 하는 클래스도 만들어 준다.

     

      public void OnLook(InputValue value)
      {
          Vector2 newAim = value.Get<Vector2>();
          Vector2 worldPos = camera.ScreenToWorldPoint(newAim);
          newAim = (worldPos - (Vector2)transform.position).normalized;

          CallLookEvent(newAim);
      }

     

    이렇게 해주면 마우스의 위치가 화면상의 좌표로 Vector2 형식의 newAim변수에 저장을 할 수 있게 되고 이걸 월드좌표계로 변경을 해주어야 한다. 화면좌표는 화면 픽셀 단위로 측정, 월드좌표계는 월드내의 위치로 측정,

    camera.ScreenToWorldPoint(newAim);를 통해서 월드좌표계로 변경을 하게 해준다. 그러면 우리는 마우스의 위치를 월드좌표기준으로 x,y 값을 얻을 수 있게 되고 이것을 통해 무기의 각도를 조절 하게 한다. 

     

     newAim = (worldPos - (Vector2)transform.position).normalized; 코드로 두 Vector간에 거리차이를 계산 해서 특정 위치로의 방향을 얻게 해준다. 그리고 그걸 정규화 해서 newAim에 저장을 하고 다 계산이 된 x,y의 값으로 CallLookEvent를 호출 한다. 

     

    지금 Event에 등록이 된 게 없으므로 실행을 하는 클래스도 만들어 준다. 

     

    TopDownAimRotation 클래스를 만들어 준다. MonoBehaviour 상속

     

    먼저 마우스에 위치에 따라서 무기의 각도를 변경 해줄 것이고 캐릭터의 좌우방향도 바꾸어 줄것이니 Renderer 컴포넌트를 다 가져와 준다. 

     

    [SerializeField] private SpriteRenderer armRenderer;
    [SerializeField] private Transform armPivot;

    [SerializeField] private SpriteRenderer characterRenderer;

     

    여기서 SerializeField로 가져온 것은 아무래도 보안? 이다. public으로 하면 바로 inspector에 넣을 순 있지만 SerializeField를 쓰면 private로 해도 넣을 수 있다.

     

    그리고 TopDownController에 있는 Event에 접근을 할 것이니 이것도 가져와 준다. 

     

    private TopDownController controller;

     

     private void Awake()
     {
         controller = GetComponent<TopDownController>();
     }

     

    그 다음에 Move와 같이 게임이 시작이 되면 Event에 함수를 추가 시켜 주는 코드를 작성 해준다. 

     

    private void Start()
    {
        controller.OnLookEvent += OnAim;
    }

     

    이제 OnAim이라는 함수를 작성 해준다. 

     

     private void OnAim(Vector2 direction)
     {
         RotateArm(direction);
     }

     

    이렇게 하면 OnAim을 실행하나 바로 RotateArm을 실행 하나 무슨 차이가 있냐라고 생각 할 수 있는데 이유가 무엇인가.

    아마 솔리드 원칙 때문이지 않나. 한 개의 함수는 하나의 역할만 해야 하는데 흠. 애매한데

    =================

    OnAim은 입력처리를 하는 함수, RatateArm은 실제 팔을 돌리게 처리 하는 함수다. 

    이렇게 나누게 된다면 유지보수성, 재사용성, 가독성을 좋게 해준다.

     

    예를 들어서 지금은 마우스의 위치를 입력 받아서 RotateArm을 실행 하게 해주는 건데 이 입력처리 함수를 마우스 위치가 아닌 다른 입력처리로도 RotateArm을 호출 할 수 있으니까 코드를 재사용 할 수 있게 되는 것이다.

     

    그래서 이제 RotatArm 함수를 작성 해준다. 입력처리를 받고 그 값에 따라 무기를 회전 시키게 한다. 

     

       private void RotateArm(Vector2 direction)
       {
           float rotZ = Mathf.Atan2(direction.y,direction.x) * Mathf.Rad2Deg;

           characterRenderer.flipX = Mathf.Abs(rotZ) > 90f;
           armRenderer.flipY = characterRenderer.flipX;

           armPivot.rotation = Quaternion.Euler(0,0, rotZ);
       }

     

    여기서 MathF는 뭐 무섭게 생긴 수학함수들이 담긴 Class다. 이 클래스에 있는 Atan2와 Rad2Deg라는 함수를 사용 해서 무기의 Rotation을 변경 해줄것이다.

     

    Atan2와 Rad2Deg가 무엇이냐. 우리는 좀 전에 OnLook함수를 이용해 계산이 끝난 x,y의 값을 얻어왔다. 그 x,y의 값으로 각도를 구할 수가 있는데 어떻게 구하냐. 그게 Atan2와 Rad2Deg다. 

     

    이걸 만든 사람은 삼각형의 밑변과 높이를 통해서 각도를 알 수 있는 것을 생각해내서 x는 밑변 y는 높이인 걸 활용해서 각도를 구하게 만들었다. 개발자들은 못 참는다고 한다. 뭘 못 참는다는 건진 모르겠다. 투터님은 동공이 커지고 침을 흘렸다.

     

    그래서 아까 계산 한 x,y를 매개변수로 받아와서 그거를 가지고 각도를 구하는 계산을 끝내고 그 각도로 rotation의 값을 변경 해주면 된다. 

     

    원래는 각도를 통해 밑변과 높이를 구할 수 있는데 Atan2는 그 반대이다. 밑변과 높이로 각도를 구할 수 있게 해준다. 라디안으로 반환 해준다.  -3.14~3.14의 값으로 표시를 하는 게 라디안, 그걸 다시 펼쳐 주는 게 디그리다. 

     

    그렇게 하면 우리는 x,y의 값으로 각도를 구할 수 있게 되고 그 각도로 Rotation을 변경 시켜주면 되는데 여기서 주의 할 점이 있다. 

    우리는 프로그래밍을 할 때 x,y,z의 세 가지 값으로 하는 게 편안한 것으로 돼있다. 하지만 회전을 할 때 게임엔진 내부적으로는 4가지의 수 x,y,z,w Quaternion을 사용 하는 경우가 많다. 

     

    유니티도 내부적으로 Quaternion을 사용해 회전을 해서 x,y,z만 있는 Euler의 값으로만 회전을 하면 짐벌락현상. 제대로 회전이 일어나지 않는 문제가 생긴다. 

     

    그래서 우리는 알게 된 Euler각도. x,y,z 3가지의 값으로 Quaternion의 값이 나오게 계산 해주는 Quaternion 함수를 사용해서 Rotation을 변경 해주어야 한다. 

     

        armPivot.rotation = Quaternion.Euler(0,0, rotZ);

     

    이 코드가 그 코드다. Euler의 값을 Quaternion으로 변경 해준다. 이러면 문제 없이 회전이 잘 될 것이다. 

    Atan2와 디그리를 통해서 rotZ에(Rotation Z 줄인거) 각도를 저장 하고 Quaternion에 함수를 사용해서 회전을 하게 한다. 

     

    그리고 각도에 따라서 캐릭터와 무기의 좌우반전도 하게 해준다. 

     

    characterRenderer.flipX = Mathf.Abs(rotZ) > 90f;
    armRenderer.flipY = characterRenderer.flipX;

     

    이렇게 하면 캐릭터기준 마우스가 왼 쪽으로 이동 하면 캐릭터가 왼 쪽을 바라볼 것이다. 마우스의 좌표에 따라 각도를 계산 하게 했으니까..., 마우스의 좌표가 캐릭터 왼 쪽으로 찍히면 그 좌표로 계산을 하고 각도 방향으로 캐릭터 반전.. 이해완? 90도보다 커지면 flip이 체크 되니까 반전 된다. Abs 함수를 사용해서 절대값으로 반환 해서 계산을 하니까 양수로만 계산.

    사진참고

    이제 코드는 끝났고 아까 SerializeField로 선언한 변수들만 값 넣어주면 된다. 무기의 각도를 변경 해줄 것이니 무기 Object를 넣어주고 등등 알아서 잘 넣으면 된다.

Designed by Tistory.