[Unity][포트폴리오] 카메라 회전
※ 해당 포스팅은 개인의 공부 정리용 글입니다. 틀린 내용이 있다면 추후 수정될 수 있습니다.
※ 해당 포스팅은 Unity 2021.3.15f1 버전을 기준으로 작성되었습니다.
※ 개발 중인 유니티 3D 포트폴리오의 소스 코드를 포함하고 있습니다.
0. 서문
이전에 만들었던 3D 프로젝트에서는 Input.GetKey()를 통해 마우스 입력을 체크하고, 우클릭 드래그 상태로 움직인 거리에 비례하여 카메라를 회전시키는 로직을 구현했었습니다. 이번 프로젝트에서는 플레이어의 움직임을 Player Input을 통해 구현한 만큼, 카메라 회전도 Player Input을 이용하여 구현해보기로 했습니다.
현재 CameraMove 클래스는 FixedUpdate()에서 카메라의 위치를 플레이어 위치 + 오프셋(플레이어와의 상대적인 거리)으로 유지하도록 하여 플레이어를 따라다니도록 구현한 상태입니다. 여기서 Offset을 고정된 벡터값이 아닌 오브젝트의 트랜스폼으로 바꾼 뒤, 카메라 회전 시 오프셋도 동일하게 회전하여 플레이어와의 상대적인 거리를 유지하도록 구현하려고 합니다.
1. 플레이어 인풋 세팅
카메라에 붙인 Player Input에서 사용할 Input Action은 다음과 같이 생성하였습니다.
마우스 위치를 체크하기 위한 Look 액션과 마우스 우클릭 드래그를 체크하기 위한 RightDown, RightUp을 생성하였습니다. RightDown은 상호작용으로 Press를 추가한 뒤 트리거를 Press Only로 선택하여 누르는 순간에만 반응하게 하였고, RightUp은 동일한 설정에서 트리거를 Release Only로 선택하여 떼는 순간에만 반응하게 하였습니다.
// 드래그 여부 체크용
bool rDown;
void OnRightDown()
{
rDown = true;
}
void OnRightUp()
{
rDown = false;
// 회전 종료 시 초기화
befLookVec = Vector2.zero;
}
CameraMove 클래스에 마우스 오른쪽 드래그 여부 체크용 bool 타입 변수를 선언하고, RightDown이 입력될 경우 해당 변수를 true로, RightUp이 입력될 경우 false로 바꾸도록 했습니다. 또한 OnRightUp()에서 이전 마우스 좌표값을 저장하는 벡터를 초기화하도록 했습니다.
2. 마우스 위치에 따른 카메라 회전
void OnLook(InputValue value)
{
// 타겟이 없거나 마우스 오른쪽 클릭중이 아니라면 패스
if (target == null || rDown == false) return;
curLookVec = value.Get<Vector2>();
// 저장된 이전 값이 없다면 저장하고 패스
if (befLookVec == Vector2.zero)
{
befLookVec = curLookVec;
return;
}
xOffset = (curLookVec.x - befLookVec.x) * vRotateSpeed * Time.deltaTime;
yOffset = (curLookVec.y - befLookVec.y) * hRotateSpeed * Time.deltaTime;
cameraRef.position = transform.position;
// x축 기준 회전
// 범위 체크를 위해 사전 테스트용 트랜스폼을 먼저 회전
cameraRef.RotateAround(target.position, Vector3.right, -yOffset);
// 범위 내일 경우에만 회전 적용
if (cameraRef.eulerAngles.x > rotateLimitMin && cameraRef.eulerAngles.x < rotateLimitMax)
{
transform.RotateAround(target.position, Vector3.right, -yOffset);
cameraOffset.RotateAround(Vector3.zero, Vector3.right, -yOffset);
}
cameraRef.rotation = transform.rotation;
// y축 기준 회전
transform.RotateAround(target.position, Vector3.up, xOffset);
cameraOffset.RotateAround(Vector3.zero, Vector3.up, xOffset);
// 현재 값을 이전 값으로 저장
befLookVec = curLookVec;
우선 스킵 사항으로 추적 대상(플레이어)이 설정되지 않았거나 마우스 우클릭 드래그 중이 아니라면 return하도록 했습니다. 또한 마우스 좌표의 이동량을 측정하여 회전시킬 예정이므로, 이전 좌표값이 Vector2.zero라면 현재값을 저장만 하고 return하도록 했습니다.
카메라의 회전은 타겟을 대상으로 회전하도록 Transform.RotateAround()를 통해 구현했습니다. 스킵 상황이 아니라면 현재 마우스 좌표값에서 이전 마우스 좌표값을 뺀 값을 기반으로 x축 회전량, y축 회전량을 계산하도록 했습니다(xOffset, yOffset). 그리고 x축 기준 회전은 카메라가 바닥 밑으로 들어가거나 하는 상황을 방지하기 위해 제한값을 설정하고(rotateLimitMin, rotateLimitMax), 회전 이후 각도를 예상하기 위해 별도의 객체의 트랜스폼(cameraRef)을 사용하게 했습니다.
x축 회전의 경우 카메라 회전 이전에 먼저 해당 트랜스폼을 회전시키고, 회전된 트랜스폼의 x축 회전값이 최소~최대 범위 안일 경우에만 카메라 및 오프셋도 동일하게 회전시키도록 했습니다. 이전 프로젝트에서는 범위를 벗어나면 강제로 최소값 또는 최대값으로 바꾸는 로직을 사용했었는데, 범위를 벗어나려고 할 때 카메라가 흔들리는 현상이 있어서 해당 방식으로 바꾸어 보았습니다. y축 회전은 별도의 제한을 두지 않았으므로 앞에서 계산한 값을 기반으로 바로 카메라 및 오프셋을 회전시키도록 했습니다.
// z축 회전 제한
if (transform.eulerAngles.z != 0)
{
transform.rotation =
Quaternion.Euler(transform.eulerAngles.x, transform.eulerAngles.y, 0);
}
}
마지막으로 x축,y축 회전만 이루어지도록 z축 회전 값이 0이 아닐 경우, 강제로 0으로 만드는 내용을 추가하였습니다.
다음은 실제 프로젝트에 적용한 모습입니다.
3. 결론
마우스를 이용한 카메라 회전은 구현되었고 x축 회전 범위의 경계에서 떨리는 현상은 해결하였으나, 다음과 같은 문제점들이 남아있습니다.
- 회전 비용이 많이 듦
- x축 회전은 각도 제한을 위해 별도의 트랜스폼을 먼저 회전시키고, 맞다면 카메라 회전 및 오프셋 회전
- y축까지 총 5번의 RoateAround()를 사용 - z축 회전 강제 제한
- x축, y축 회전에 따라 z축도 조금씩 변동하는 것을 제한하기 위해 강제로 0으로 바꿈
- Rigidbody라면 자연스럽게 제한할 수 있지만, RotateAround()와 같은 함수가 없음
=> Rigidbody로 RotateAround()와 같이 구현할 수 있다면 테스트 필요
해당 내용들은 개선하는 대로 포스팅에 내용 추가하도록 하겠습니다.