
먼저 Orbit Camera에 대해 간략하게 설명하자면 특정 오브젝트를 기준으로 회전하며 바라보는 카메라 방식이라고 생각하면 됩니다. 당연히 Unity 게임을 만드는 상황이니 플레이어 기준으로 카메라를 회전시켜 바라보는걸로 구현하게 됩니다.
카메라 시점 또한 플레이어 이동과 같이 Input.GetAxisRaw로 마우스의 X와 Y값을 받겠습니다.
mosX += Input.GetAxisRaw("Mouse X") * MouseSensitivity; //오른쪽 + 왼쪽 - //오른쪽으로 돌리면 + 우측으로 돌아감
mosY -= Input.GetAxisRaw("Mouse Y") * MouseSensitivity; //위 + 아래 - //-=를 통해 방향 반대로 전환해주기
입력을 받는 과정에서 입력값을 계속 가중시켜 더해줌으로써 카메라가 회전이 되게 됩니다.
즉, = 로 값을 받는 상태가 된다면 마우스를 움직여 값을 줘도 가중치로 더해지는게 아닌 순간 움직이는 값이 들어가게 되어 카메라가 고정되어 있는 것 처럼 됩니다.
회전각도의 한계를 두지 않는다면 시점이 뒤바뀌거나 플레이 하는 경험이 나빠질 수 있기에 상하 회전 제한을 두겠습니다.
mosY = Mathf.Clamp(mosY, minYAngle, maxYAngle); //상하 회전 제한
Quaternion rotation = Quaternion.Euler(mosY, mosX, 0); //회전 계산 Pitch, Yaw, Roll
추가로 이상적 위치를 계산하기 위해 변수를 만들어주어 카메라를 플레이어의 등 뒤쪽으로 위치 시키겠습니다.
//회전 기준으로 플레이어로부터 떨어진 이상적인 위치 계산
Vector3 idealOffset = rotation * Vector3.forward;
Vector3 idealPosition = Target.position - (idealOffset * Distance) + Vector3.up * Height;
위에 나오는 Distance와 Height는 Public으로 선언해주어 실제 게임내에서 조정할 수 있게 해주었습니다.
추가로 카메라의 벽 충돌까지 구현해주겠습니다. 흔히 많은 3인칭 게임에서 사용하는 방식입니다.
구현 내용은 이렇습니다.
먼저 충돌 감지를 위해 레이케스트를 사용해 주겠습니다.
// 충돌 감지를 위한 레이캐스트
Vector3 targetCenter = Target.position + Vector3.up * TargetVerticalOffset; //플레이어의 눈높이
Vector3 rayDirection = idealPosition - targetCenter;
float rayDistance = rayDirection.magnitude;
rayDirection.Normalize();
RaycastHit hit;
Vector3 finalCameraPosition;
float currentSmoothTime = smoothTime;
if (Physics.Raycast(targetCenter, rayDirection, out hit, rayDistance, collisionLayer, QueryTriggerInteraction.Ignore))
{
finalCameraPosition = hit.point + hit.normal * collisionBuffer; //충돌 시: 충돌 지점(hit.point)에서 뒤로 살짝 물러난 위치를 최종 위치로 설정
currentSmoothTime = 0.01f;
}
else
{
finalCameraPosition = idealPosition; //충돌 없을 시: 이상적인 위치를 최종 위치로 설정
}
//카메라 위치 부드럽게 이동 (SmoothDamp) // 충돌 여부에 따라 동적으로 변경된 currentSmoothTime 사용
transform.position = Vector3.SmoothDamp(transform.position, finalCameraPosition, ref currentVelocity, currentSmoothTime);
transform.LookAt(Target.position + Vector3.up * TargetVerticalOffset); //대상을 바라보게 하기
길게 느껴질 수 있지만 사실 if 조건문 하나만 봐도 괜찮습니다.
그 값을 hit 이라는 변수에 넣게 됩니다.
collisionLayer는 해당 레이어만 인식할지에 대한 범위를 나타내는 것 이며,
QueryTriggerInteraction.Ignore를 사용하면 트리거로 설정된 콜라이더는 무시하고 오직 물리적 충돌을 유발하는 일반 콜라이더 하고만 충돌을 감지 하게 됩니다.
카메라 시점에 맞게 캐릭터가 이동을 하게끔 구현해 주겠습니다.
W만 누른 상태에서 화면 회전을 시키면 회전하는 방향으로 캐릭터가 회전과 이동을 동시에 하게 되는 구조로 만들어 주겠습니다.
처음 캐릭터를 이동 입력을 받게 해준 코드입니다.
float moveX = Input.GetAxisRaw("Horizontal"); //앞뒤 w일 경우 +1 s 일 경우 -1
float moveZ = Input.GetAxisRaw("Vertical"); //좌우 a일 경우 -1 d 일 경우 +1
moveX와 moveZ를 이용해 실시간 카메라 시점에 맞게 이용해 주었습니다.

캐릭터 이동 관련을 수정하기 전 카메라는 정상작동을 하였습니다. 하지만 캐릭터의 이동 방식에 따라 카메라가 벽을 뚫는 버그가 있었는데요
- 카메라는 플레이어의 이동에 따라 플레이어를 항상 비춘다.
- 플레이어는 벽을 뚫을 수 없지만 벽을 향해 W,A,S,D등 이동 버튼을 눌러 벽에 비빈다(?)
- 실제 플레이어는 이동하지 않지만 이동 버튼 키에 따라 카메라는 벽을 뚫고 들어간다.
해당 버그를 수정하고 원활하게 카메라 이동을 하기 위해 캐릭터 컨트롤러 컴포넌트를 추가 해 주었습니다.
플레이어의 최종이동 변수를 만들어 실제로 이동하게 된 거리를 계산해 카메라가 움직일 수 있게 수정해 주었습니다.
Vector3 horizontalMove = currentMoveDirection * runSpeed; //수평이동 계산
PlayerVelocity.y += gravityValue * Time.deltaTime; //중력 가속도 적용
Vector3 finalMove = horizontalMove; //최종 이동 백터 결합
finalMove.y = PlayerVelocity.y;
//캐릭터 컴포넌트 이동 함수
controller.Move(finalMove * Time.deltaTime); //캐릭터 이동 실행