본문 바로가기
스파르타 내배캠

스파르타 내배캠 Unity 3기 23일차

by LemongO 2024. 1. 25.

아~ 무것도 안 하고싶다~

 

 

 

 

오늘부터 다시 팀 프로젝트에 돌입이구나...

'게임'을 만드는 것과 '구현'을 코드적으로 세련되게 하는것은 아직 둘 다 잡기엔 무리로구나 ㅠ

 


조준(Aim)

 

어제의 이어 오늘은 조준시스템을 보고자 한다.

조준도 이동과 마찬가지로 InputSystem을 활용해서 구현한다.

 

 

Look 이라는 Action과 Mouse의 Position을 바인딩 해줬다. 받아오는 값은 이미지엔 안 나왔지만 Vector2 값을 받는다.

 

그리고 Action을 사용해 주기 위해 PlayerInputController 스크립트에 OnLook 함수를 만들어주자.

 

public class PlayerInputController : TopDownCharacterController
{
    private Camera _camera;

    protected override void Awake()
    {
        base.Awake();
        _camera = Camera.main;        
    }
    
    // 마우스포인터가 움직일 때 호출 됨.
    public void OnLook(InputValue value)
    {
        Vector2 aimTo = value.Get<Vector2>();        
        Vector2 worldPos = _camera.ScreenToWorldPoint(aimTo);
        aimTo = (worldPos - (Vector2)transform.position).normalized;        

        if (worldPos.magnitude >= 0.9f)
            CallLookEvent(aimTo);
    }
}

현재 메인카메라를 이용해 마우스 포인터의 World 좌표를 알아낸다.

그리고 주석에서 써 놨듯 움직임을 담당했던 OnMove는 KeyDown & KeyUp 때 호출됐지만,

OnLook은 마우스포인터의 움직임이 있을 때 호출된다.

 

변수 aimTo 는 현재 마우스 포인터의 Vector2 값이지만 이대로 쓰기엔 적절하지 않다.

말도 안 되게 높은 값이다.

aimTo의 좌표를 로그로 찍어봤다. 현재 카메라에 찍히는 부분 좌측하단이 0,0부터 시작해 좌표가 엄청나게 크다.

그리고 방향을 알아야하는데 카메라 좌측하단이 0이면 방향을 알 수 없다.

 

그래서 쓰는것이 Camera.ScreenToWorldPoint(aimTo)

이걸 쓰면 현재 마우스 포인터의 위치가 월드 좌표로 찍히게 된다. 그러면 방향을 알 수 있겠지!

 

마우스 포인터의 월드좌표 - 캐릭터의 좌표 를 normalized 한 값을 aimTo에 담는다.

그리고 포인터가 캐릭터와 너무 가까워 0.9이하면 조준하지 않도록 한다.

 

 

다음은 실제로 조준을 적용할 TopDownAimRotation 스크립트이다.

public class TopDownAimRotation : MonoBehaviour
{
    [SerializeField] SpriteRenderer _armRenderer;
    [SerializeField] Transform _weaponPivot;

    [SerializeField] SpriteRenderer _characterRenderer;

    private TopDownCharacterController _controller;    

    private void Awake()
    {
        _controller = GetComponent<TopDownCharacterController>();        
    }

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

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

    private void RotateArm(Vector2 direction)
    {
        // Mathf.Atan2 에 y, x 좌표를 넣어주면 각도(라디안)가 나오고,
        // 이걸 360도(degree)로 쓰기위해서 Mathf.Rad2Deg(상수) 를 곱해준다.
        float rotZ = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;

        //_armRenderer.flipY = Mathf.Abs(rotZ) > 90f;
        _characterRenderer.flipX = Mathf.Abs(rotZ) > 90f;
        _weaponPivot.eulerAngles = Vector3.forward * rotZ;
    }
}

 

로직이 전반적으로 이동 담당 TopDownMovement 스크립트와 유사하다.

컨트롤러를 받아오고, 실제 움직임을 구현한 함수를 만들고, 구독시킨다.

 

다만 이동의 경우엔 계속해서 이동이 돼야 했기에 FixedUpdate를 썻지만,

조준의 경우엔 OnLook 메서드 자체가 포인터가 움직일때마다 호출이 되고,

호출이 될 때만 그 방향으로 회전이 일어나면 되므로 따로 Update 함수는 써주지 않는다.

 

해당 구현은 RotateArm 에서 해준다.

 

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

        armRenderer.flipY = Mathf.Abs(rotZ) > 90f;
        characterRenderer.flipX = armRenderer.flipY;
        armPivot.rotation = Quaternion.Euler(0, 0, rotZ);
    }

 

이건 강의에서 쓴 코드다.

위에 내가 적은 코드와는 살짝 다른다.

강의에선 armRenderer.flipY를 true false 해주는데, (armRenderer는 활 스프라이트다)

현재 쓰는 활 스프라이트 특성상 flipY는 해줄 이유가 없어서 나는 그냥 뺐다.

 

2D에서 flipX는 rotation Y축, flipY는 rotation X축을 담당하는데

현재 스프라이트는 X축을 180도 돌려도 0도와 보이는게 같다.

 

중요한건 스프라이트의 flip보단 Pivot의 rotation 인데,

해당 기능은 rotation = Quaternion.Euler로 해도되고 eulerAngles = Vector3.forward * 로 해도 된다.

3D면 짐벌락 현상 때문에 Quaternion으로 하는게 좀더 맞는 방법이라고 한다.

https://youtu.be/zc8b2Jo7mno?si=sMLJogMRuXhjsoos

짐벌락 관련 영상이다.

 

아무튼 2D에서 회전은 사실상 Z축을 돌리면서 회전을 표현하므로 Z축을 알아내야 하는데 이것은

Mathf.Atan2(y, x) 로 알아낼 수 있다. 해당 함수를 쓰면 라디안을 알아낼 수 있고

라디안 = 원의 반지름에 대한 호의 길이의 비로 정의

 

라고 하는데 아무튼 우린 각도 Degree만 알면 되니까 Mathf.Rad2Deg (상수) 를 곱해줘

간단하게 Z축 Degree를 뽑아내도록 한다.

 

그리고 그것을 조준에 쓰일 활의 Pivot인 armPivot.rotation에 적용하면

이렇게 된다.

 

화질이.. 왜이리 안 좋지?