플레이어 회전 및 카메라 기준 시점 제어 구조

목차

1. 시스템 요구 사항

3D RPG에서 플레이어 조작감의 핵심은 이동 방향, 카메라 시점, 플레이어 회전이 하나의 기준으로 정렬되어 있는가다.

카메라가 플레이어를 따라오지 않거나, 플레이어가 카메라와 다른 방향을 바라본 상태로 이동하게 되면 입력과 결과가 어긋나 조작감이 급격히 불안정해진다.

특히 시점 제어 로직과 이동 로직이 하나의 스크립트에 섞여 있을 경우, 카메라 회전 수정이 플레이어 이동에 영향을 주거나 플레이어 회전 처리로 인해 카메라가 튀는 문제가 발생하기 쉽다.

따라서 이 시스템에서는 카메라 위치 동기화, 카메라 회전, 플레이어 회전 반영을 명확히 분리하고, 플레이어는 항상 카메라가 바라보는 방향을 기준으로 행동하도록 만드는 구조가 필요했다.

2. 설계  목표

- 카메라 위치는 플레이어를 기준으로 동기화할 것

- 카메라 회전 로직은 플레이어 로직과 분리할 것

- 플레이어 회전은 카메라 회전 결과를 그대로 반영할 것

- 물리/이동 연산 이후 시점이 결정되도록 LateUpdate를 사용할 것

- 시점 제어 로직을 독립적으로 확장 가능하게 만들 것

3. 흐름도

이 흐름에서 중요한 점은카메라는 플레이어를 따라오지만, 회전의 주도권은 카메라가 가진다 는 구조다.

플레이어는 카메라를 조작하지 않고,카메라가 만든 시점을 플레이어가 그대로 따르는 형태다.

4. 구현

4.1. LateUpdate 기반 카메라 위치 및 플레이어 회전 동기화
// PlayerController.cs
private void LateUpdate()
{
    if (mainCam == null || !mainCam)
        mainCam = Camera.main;

    // 카메라 위치 및 방향 동기화
    if (mainCam && camPos)
    {
        mainCam.transform.position = camPos.position;
        transform.rotation = mainCam.transform.rotation;
    }
}

카메라 위치 동기화와 플레이어 회전 반영은 LateUpdate에서 처리한다.

이벤트 함수 실행 순서(Execution Order of Event Functions) - Unity 매뉴얼

Unity의 실행 순서에서 Update는 입력·이동·물리 계산이 이루어지는 단계이고, LateUpdate는 해당 프레임의 모든 이동 연산이 끝난 이후 호출된다.

이 구조를 활용해, 플레이어 이동과 점프, 중력 계산이 모두 끝난 뒤 카메라 위치와 플레이어 회전을 동기화하도록 설계했다.

이를 통해 이동·점프·중력 등 모든 위치 연산이 끝난 뒤 최종 시점을 결정할 수 있고,카메라가 이동 결과를 따라오면서 발생하는 떨림이나 프레임 단위 어긋남을 방지할 수 있다.

만약 이 로직을 Update에서 처리하면, 같은 프레임 안에서 '이동 → 회전 → 이동 보정' 같은 흐름이 섞이며 카메라 떨림이나 미세한 어긋남이 발생할 수 있다.

이 구조에서는 카메라는 camPos라는 플레이어 기준 트랜스폼 위치로 이동하고, 플레이어는 카메라의 회전을 그대로 복사한다.

즉, 플레이어가 회전해서 카메라가 따라오는 구조가 아니라, 카메라가 먼저 회전하고, 플레이어는 그 결과를 따른다.

이 방식은 '카메라가 보는 방향 = 플레이어가 바라보는 방향' 이라는 기준을 항상 유지하게 만든다.

4.2. 카메라 회전 전담
//Camera_.cs

void Update()
{
    rotationSpeed = settingManager.MouseSensitivity(); // 마우스 감도 설정 가져오기

    CameraRotation(); 
}

void CameraRotation()
{
    // 마우스 오른쪽 버튼을 누르고 있는지 확인
    if (Input.GetMouseButton(1))
    {
        float mouseX = Input.GetAxis("Mouse X");
        float mouseY = Input.GetAxis("Mouse Y");

        rotationX += mouseX * rotationSpeed * Time.deltaTime;
        rotationY += mouseY * rotationSpeed * Time.deltaTime;

        if (rotationY < -15) rotationY = -15;
        else if (rotationY > 15) rotationY = 15;

        transform.localEulerAngles = new Vector3(-rotationY, rotationX, 0);
    }
}

카메라의 실제 회전 처리는 PlayerController가 아닌 Camera_ 클래스에서 담당한다.

시점 제어는 이동과 전혀 다른 책임을 가지는 시스템이기 때문에 분리하였다.

마우스 입력은 우클릭을 누르고 있을 때만 처리하도록 제한했다.

이를 통해 평상시에는 시점이 고정되고, 의도적으로 시점을 조작할 때만 카메라가 회전하도록 만들었다.

수직 회전은 -15° ~ 15°로 제한했다.

이는 카메라가 과도하게 위나 아래를 바라보며 캐릭터가 화면에서 사라지거나, 조작감이 불안정해지는 상황을 방지하기 위함이다.

회전은 localEulerAngles 기준으로 적용되며, 이 회전 결과는 다음 LateUpdate에서 플레이어의 회전에 그대로 반영된다.

이로 인해 이동 로직은 시점을 고려하지 않고도 항상 동일하게 유지되며, 카메라 감도 · 제한 각도 · 타겟팅 로직을 독립적으로 확장할 수 있다.

4.3. 시점 기준 이동과의 연결

이전 게시글에서 설명한 이동 로직에서는 입력 방향을 플레이어 로컬 기준에서 월드 기준으로 변환했다.

Vector3 planarDir = transform.TransformDirection(inputDir).normalized;

여기서 transform은 항상 카메라 회전과 일치한다.

즉, 카메라가 바라보는 방향이 곧 플레이어의 로컬 기준이 되고, 입력은 항상 시점 기준 이동으로 해석된다.

이 구조 덕분에 카메라 방향과 입력 해석 기준이 어긋나지 않으며, 플레이어는 항상 “보는 방향으로 이동한다”는 일관된 조작감을 유지할 수 있다.

5. 개발 의도

이 게시글의 핵심 의도는 시점 제어를 이동 시스템으로부터 완전히 분리하는 것이었다.

플레이어는 이동과 상태를 책임지고, 카메라는 시점과 회전을 책임지며, 두 시스템은 LateUpdate 단계에서만 연결된다.

이 구조를 통해 카메라 감도 조절, 타겟 락온, 컷신 카메라, 자동 회전 같은 기능을 플레이어 이동 로직을 건드리지 않고 확장할 수 있다.

결과적으로 이 시스템은 카메라 기준 플레이어 조작이라는 3D RPG의 핵심 조작 패턴을 단순하고 안정적인 구조로 구현하는 데 목적이 있다.