본문 바로가기

유니티 스파르타 캠프 7주차

250520 3D 회전 Quaternion 문제 해결

오늘 이 주제를 선정한 것은 이번 과제에서 회전과 관련된 것을 하는동안 ChatGPT를 활용해서 너무 힘들었고 오히려 검색을 했으면 더 빨리 끝냈을 것이라는 생각도 들고, 또 스스로 어떻게 할 것인지 사고방식이 있었다면 이리 오랜 시간을 투자하진 않았을 거 같아 스스로 복습하는 겸, ChatGPT의 의존하던 성향을 스스로 줄이고자 적어봅니다. 


💫 그럼 뭐를 만들려했는가?

player.GetComponent<PlayerKey>().isControllStop  = true;
direction = Quaternion.AngleAxis(angleY, Vector3.right) * Vector3.up;

player.AddForce(direction.normalized * distance *player.mass, ForceMode.Impulse);

다시 생각해봐도 그리 어려운 내용은 아니었습니다. 그냥 캐릭터가 어떤 발판에 올라가면 일정 시간이 지나고 원하는 방향의 각도로 Force를 활용하여 점프를 하는 것이었습니다. 이렇게만 보면 쉽지만, 한가지 문제가 있었습니다. 

 Vector3 dir = transform.forward * curMoveInput.y+ transform.right * curMoveInput.x;
 dir *= moveSpeed;
 dir.y = rb.velocity.y;
 rb.velocity = dir;

위 내용은 캐릭터를 움직일 때, 키 값을 기반으로 캐릭터의 움직임을 강제하는 것입니다. 눈치채신 분도 있을 수 있습니다. Force를 사용할 경우, 위 코드는 velocity를 고정시키기에 정상적으로 날아가지 않습니다. ㅎㅎ 그래서 방법을 생각했습니다.

1. 캐릭터가 플랫폼에서 점프했을 때 일정시간동안 키 값을 받지 않는다.

2. 캐릭터의 키값이 0이라면 velocity를 고정시키지 않는다.

2가지 다 큰 문제가 있었습니다. 1번째 방식으로 할려고 시도했으나, AddForce는 시작시간과 걸리는 시간을 특정할 수가 없습니다. ㅋㅋ. 다음 2번째 방식으로는 키 값이 0일 경우, velocity는 기존 값을 배경으로 움직이기에 비눗물을 밟은 것처럼 계속 움직입니다. ㅋㅋ 그래서 결론으로 선택하게된 방법이 있었습니다. 


💦 해결 방법?

바로 점프했을 때는 움직이지 않게 하고 떨어질 때, 키를 다시 입력받을 수 있도록 만든다.

네, 약간의 설명을 덫붙이자면, Rigidbody를 넣었기에 중력으로 인해 자동으로 velocity 값이 -로 떨어집니다. +일 때는 올라간다는 의미이게 이 값이 -일 때, 움직일 수 있도록 했습니다. 

if(isControllStop)
	if (rb.velocity.y <= -0.01f)
		isControllStop = false;
    else
        return;

Vector3 dir = transform.forward * curMoveInput.y+ transform.right * curMoveInput.x;
dir *= moveSpeed;
dir.y = rb.velocity.y;
rb.velocity = dir;

근데 사실 여기까지는 저도 그리 오랜 시간이 걸리진 않았습니다.

그래서 다음 문제를 살펴보겠습니다.


💢 다음으로 생긴 문제?

player.GetComponent<PlayerKey>().isControllStop  = true;
direction = Quaternion.AngleAxis(angleY, Vector3.right) * Vector3.up;

player.AddForce(direction.normalized * distance *player.mass, ForceMode.Impulse);

다시 위 코드로 넘어와서 약간 요약 해석을 하자면, Vector.up 좌표를 right Z축으로 Angle만큼 돌린다는 뜻입니다. 위 내용을 이해 못했을 때, 별의별 값을 다 넣어서 위 내용을 해결하고나서는 이것은 정상작동이 되었습니다.그러나, 제 의도와는 약간 달랐습니다. 플레이어의 보는 방향으로 점프가 활성화되면 좋겠는데 위 내용은 Vector3.up 그니까 공용좌표계를 썼기에 언제나 특정방향으로만 날라가도록 설정되었습니다. 이를 수정하기 위해서 player의 회전값을 받아오고 그를 돌리는 시도를 했습니다. 그러나 이 때, 챗지피티가 생각지도 못한 비수를 꼽습니다. 

1.
Vector3 forwardXZ = new Vector3(forward.x, 0f, forward.z).normalized;

// 플레이어가 위나 아래를 보는 각도 (degree)
float angleVertical = Vector3.SignedAngle(forwardXZ, forward, Vector3.right);

// angleVertical 이 너무 크거나 작으면 clamp 하자
float clampedAngle = Mathf.Clamp(angleVertical, minAngle, maxAngle);


2.
float angleVertical = Vector3.SignedAngle(forwardXZ, forward, Vector3.right);
float clampedAngle = Mathf.Clamp(angleVertical, minAngle, maxAngle);
Vector3 clampedDirection = Quaternion.AngleAxis(clampedAngle, Vector3.right) * forwardXZ;

3.
✅ 보너스: 아래로 점프 방지하고 싶다면?

float maxDownAngle = -10f;
float minUpAngle = 80f;

float clampedAngle = Mathf.Clamp(angleY, maxDownAngle, minUpAngle);
Quaternion angleTilt = Quaternion.AngleAxis(clampedAngle, Vector3.right);

Vector3 rotatedDirection = Quaternion.Euler(-angle, 0f, 0f) * baseDirection;

4.
// 2. 플레이어가 보고 있는 Y축 회전만 가져옴 (회전 각도만 필요)
float yaw = player.transform.eulerAngles.y;

// 3. Y축 회전만 반영한 방향 구함
Vector3 forwardWithYaw = Quaternion.Euler(0, yaw, 0) * baseDirection;

// 4. 위로 튕겨주는 각도 적용 (예: 위로 45도 기울기)
float upwardAngle = 45f;
Vector3 jumpDirection = Quaternion.AngleAxis(-upwardAngle, Vector3.right) * forwardWithYaw;

5.
float viewY = player.transform.eulerAngles.y;
Vector3 viewYVector = Quaternion.Euler(0, viewY, 0) * Vector3.forward;

// 2. viewYVector 기준으로 위로 angleY만큼 회전된 방향 만들기
Vector3 rightAxis = Vector3.Cross(Vector3.up, viewYVector); // 오른쪽 축 구하기
Vector3 direction = Quaternion.AngleAxis(-angleY, rightAxis) * viewYVector;

6.
1. Quaternion.Euler(0, angleY, 0)

float angleY = 45f;
Vector3 baseDir = Vector3.forward;

Vector3 rotatedDir = Quaternion.Euler(0, angleY, 0) * baseDir;

2. Quaternion.AngleAxis(angle, Vector3.up)

float angleY = 45f;
Vector3 baseDir = Vector3.forward;

Vector3 rotatedDir = Quaternion.AngleAxis(angleY, Vector3.up) * baseDir;

위 내용 중에 6번째를 제외한 나머지는 이제 플레이어의 카메라 각도를 전부 가져오면 플레이어가 바닥을 보고 뛴다거나 하는 문제가 있어서 그거에 관해서 질문했더니, clamp 조절이라던가.. 근본적인 해결이 되지 않는 해결책을 알려줬습니다. 역시 질문하는 사람의 역량이 어느정도 필요한 거 같습니다. 일단 저는 Quaternion.AngleAxis라는 기능을 활용하여

player.GetComponent<PlayerKey>().isControllStop  = true;
direction = Quaternion.AngleAxis(angleY, Vector3.right) * Vector3.up;
direction = Quaternion.AngleAxis(player.transform.eulerAngles.y, Vector3.up) * direction;

player.AddForce(direction.normalized * distance *player.mass, ForceMode.Impulse);

그냥 문장 하나 더 넣어서 회전을 시켜줬습니다. 사실 이거 보고서 되게 허무한 느낌이 있었지만.. 일단 알게된 정보를 하나 공유하자면 플레이어의 rotation값 그니까 player.transform.rotation.y 와 같은 값은 -1~ 1이기에 가져와서 사용해봤자 방향으로서의 역할을 하지 못합니다. 그렇기에 rotation 값을 방향으로 쓰고 싶다면 저와 같이 player.transform.eulerAngles를 가져와야합니다.


🎓결론

1. 챗지피티를 사용할 때도 어느정도 생각을 언어화 잘해야한다.

2. 회전값을 받아올 때는, transform.eulerAngles를 사용한다.

3. Quaternion은 AngleAxis가 제일 활용하기 좋은 거 같다. (Euler도 비슷한 역할입니다.)

아무튼 잘 해결되서 다행이고 각도는 이후로 잘 해결할 수 있을 듯 합니다..