Posted in: Unity, 메서드 & 팁 정리

Unity tip | 카메라에게 추적 기능과 자동 줌 기능 넣기

메인 카메라에게 추적 기능을 넣기 전에


메인 카메라가 게임 오브젝트를 바로 따라다니면 해당 게임 오브젝트는 보이지 않는 문제점이 발생합니다.

그런 현상을 방지하기 위해서 빈 게임 오브젝트가 게임 오브젝트를 추적하게 만들고 카메라는 자식으로 설정합니다. 그리고 카메라를 일정 거리 띄어놓으면 문제없겠죠.

위 사진을 보면 닻을 의미하는 Anchor가 빈 게임 오브젝트이며 메인 카메라는 닻과 연결되어 있는 배라고 보시면 됩니다.

그리고 Anchor와 카메라의 Transform 을 보기 좋게 바꿔주면 좋은데 제작하시는 게임에 맞추어 적절하게 바꿔줍시다.

스크립트로 실질적인 기능을 만들자


위 사진을 보면 Anchor는 이미 스크립트를 가지고 있습니다. 실질적인 기능은 스크립트로 만들어야 합니다. 우리가 원하는 기능은 카메라가 원하는 오브젝트를 자동 추적하고 상황에 따라 줌인 줌아웃을 하게 해주는 스크립트입니다.

카메라에 게임 상황에 따른 시각적 효과를 줄려면 어떤 상황을 줄 것인지 생각을 해 봐야 합니다. 예를 들어 3가지 상황이 있다 치고 각 상황마다 적절하게 카메라 움직임을 주면 더욱 역동적인 전투를 보여주겠죠.

public enum State
{
    // 3가지 상황을 줌
    Idle, Ready, Tracking
}

// 프로퍼티 사용
private State state
{
    set
    {
        switch(value)
        {
            case State.Idle:
                break;
            case State.Ready:
                break;
            case State.Tracking:
                break;
        }
    }
}

void Awake()
{
    state = State.Idle;
}

위 코드에서는 3가지 상황을 생각하고 작성한 것입니다. Idel(평상시 상태), Ready(준비 상태) 그리고 Tracking(추적 상태)입니다. 스위치 문으로 3가지 상황에 따른 기능을 작동하는 함수를 실행해 주면 편하겠지요.

스위치문에서 정수로 작동하게 하지 말고 enum 을 활용하여 3가지 상황에 따른 정수를 설정한 뒤 스위치문과 함께 사용하면 더욱 보기 좋은 코드가 됩니다.

그리고 프로퍼티라는 C# 문법을 사용합니다. 프로퍼티는 바깥에서 보면 변수이지만 안에서 보면 함수처럼 동작합니다. 프로퍼티를 이용하면 은닉성과 편의성 두 마리 토끼를 다 잡을 수 있습니다.

코드를 해석하자면 게임이 시작되면 Awake 함수를 통해 state는 =(이퀄)을 통해 State.Idle이라는 값이 들어오는데 그렇게 되면 set 접근자 안에있는 value 키워드로 들어옵니다. 그러면 State.Idle에 해당하는 case가 작동합니다. C# 프로퍼티를 공부하면 알 수 있는 내용입니다.

이제 아래 코드처럼 몇 가지 변수들을 추가해줍시다.

private Transform target;

public float smoothTime = 0.2f;

private Vector3 lastMovingVelocity;
private Vector3 targetPosition;

private Camera cam;
private float targetZoomSize = 5f;

private const float roundReadyZoomSize = 14.5f;
private const float readyShotZoomSize = 5f;
private const float trackingZoomSize = 10f;

private float lastZoomSpeed;

private Transform target; 추적할 대상을 이 변수가 받아들입니다.

public float smoothTime = 0.2f; 대상을 추적할 때 바로 이동하는 게 아니라 스무스하게 부드럽게 꺾어서 추적하게 하기 위해 지연시간 역할을 합니다. 유니티는 두 Vector3 좌표를 알고 있을 때 부드럽게 이동하는 기능을 가지고 있습니다.

private Vector3 lastMovingVelocity; 사용하려는 유니티 기능이 마지막 프레임에 어느 속도만큼 해당 위치로 이동하고 있었는지 알고 있어야 동작합니다. 그때 사용하는 변수입니다.

private Vector3 targetPosition; 위와 같은 이유로 사용하려는 유니티 기능에서 필요하기 때문에 선언합니다.

private Camera cam; 카메라에 줌 기능을 넣기 위해선 당연히 카메라를 가져와야 합니다. 카메라를 받기 위한 변수입니다.

private float targetZoomSize = 5f; 위 사진에서 보이는 것처럼 Camera 컴포넌트의 Size를 통해서 줌인 줌아웃 기능을 구현할 수 있습니다. 해당 변수는 원하는 줌 사이즈를 구현하기 위한 변수입니다.

private const float; 으로 선언한 변수들은 앞서 작성했던 switch문에서 사용되었던 각각의 게임 상황에 따라 사용할 변수입니다. const로 선언한 이유는 해당 값은 변경할 이유가 없기 때문입니다.

private float lastZoomSpeed; 유니티 기능 중 두 Vector3 좌표가 있다면 부드럽게 이동하는 기능이 있다고 하였는데요 사용하기 위해선 마지막 프레임에 속도를 알아야 한다고 했습니다. 그때 사용하는 변수입니다.

이제 필요한 변수를 선언했으니 미리 작성했었던 코드도 수정합시다.

private State state
{
    set
    {
        switch(value)
        {
            case State.Idle:
                targetZoomSize = roundReadyZoomSize; //추가
                break;
            case State.Ready:
                targetZoomSize = readyShotZoomSize; //추가
                break;
            case State.Tracking:
                targetZoomSize = trackingZoomSize; //추가
                break;
        }
    }
}

void Awake()
{
    cam = GetComponentInChildren<Camera>(); //추가
    state = State.Idle;
}

void Start()
{
    target = GameObject.Find("Player").GetComponent<Transform>();
}

//카메라가 추적 대상으로 이동
private void Move()
{
    targetPosition = target.transform.position;
    
    Vector3 smoothPosition = Vector3.SmoothDamp(transform.position, targetPosition,
                                        ref lastMovingVelocity, smoothTime);
                                        
    transform.position = smoothPosition;
    
    state = State.Tracking;
}

switch문을 보면 각 case마다 적절한 카메라 줌 사이즈를 설정하였습니다.

또한 Awake 함수에서 게임이 시작하였을 때 카메라 컴포넌트를 변수에 대입하였습니다. GetComponentInChildren 은 자식들을 전부 뒤져서 원하는 컴포넌트를 가져오는 것입니다. 현재 메인 카메라는 Anchor 오브젝트의 자식으로 있기 때문에 해당 함수를 사용합니다.

Move 함수는 카메라가 추적 대상으로 이동하는 함수입니다. 이 함수에서 중요한 것은 SmoothDamp입니다.

Vector3.SmoothDamp(현재 위치, 가고 싶은 위치, 마지막 순간의 변화량, 지연시간); 제대로 작성만 해주면 알아서 부드럽게 이동시켜 줍니다.

ref(레퍼런스)의 의미는 변수가 함수 내부로 들어갔을 때 변경된 값을 챙겨서 나온다는 의미입니다. SmoothDamp 는 얼마나 값을 변경했는지 ‘마지막 순간의 변화량’에 갱신해서 욱여넣습니다. 더 자세한 내용은 유니티 문서를 참고하시길 바랍니다.

마지막에 Anchor의 위치를 smoothPosition으로 지정해주면 됩니다.

카메라 이동 함수가 만들어졌으니 카메라 줌을 부드럽게 바꿔주는 함수를 만들어줍시다.

private void Zoom()
{
    float smoothZoomSize = Mathf.SmoothDamp(cam.orthographicSize, targetZoomSize,
                                        ref lastZoomSpeed, smoothTime);
                                        
    cam.orthographicSize = smoothZoomSize;
}

카메라 이동과 비슷하지만 함수 제작 원리는 원하는 줌 사이즈를 바로 지정하는 게 아니라 현재 줌 사이즈에서 원하는 줌 사이즈까지 지연시간을 줘서 부드럽게 바꿔주는 기능을 합니다.

카메라 이동과 마찬가지로 SmoothDamp 가 또 사용됩니다. 카메라의 줌 사이즈를 변경하는 게 목적이므로 카메라 컴포넌트를 가지고 있는 변수에서 orthographicSize의 값을 변경시키면 됩니다. 참고로 카메라 컴포넌트는 orthographic 상태여야 위와 같은 코드가 제대로 작동합니다.

private void FixedUpdate()
{
    if(target != null)
    {
        Move();
        Zoom();
    }
}

Move와 Zoom 함수는 카메라에 관련된 함수이기 때문에 매번 사용되는 함수입니다.

Update 함수가 아니라 FixedUpdate의 기능은 게임이 렉 걸려서 프레임이 떨어져도 FixedUpdate는 렉 걸리는 거 무시하고 정해진 처리를 합니다. 즉, FixedUpdate는 정확한 처리를 요구할 때 사용됩니다. 반면 Update 문은 매 프레임마다 실행되기 때문에 렉 걸려서 프레임이 떨어진다면 개발자가 유도한 만큼 함수가 실행되지 않게 됩니다.

위 함수는 target(추적할 대상)이 존재할 경우라면 Move Zoom 함수를 실행되게 작성하였습니다.

저는 위에서 Start 함수에 target을 Player 변수에 지정해 놓았습니다. 이건 여러분들이 추적할 오브젝트를 유동적으로 바꾸시면 되겠죠.

그리고 지금은 Move 함수에서 카메라의 상태를 바꾸어 주었지만 카메라 상태도 여러분들이 개발하시는 게임에 맞게 바꾸어 주시는 게 좋습니다. 예를 들면 캐릭터가 가만히 있다면 Idle 상태로 전환하고 다시 움직이면 Tracking 상태로 전환하는 형식으로요.

긴 글 봐주셔서 감사합니다.


참고문헌 – retr0의 유니티 게임 프로그래밍 에센스

이미지 출처 – https://littledeep.com/camera-illustration-free-download/

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 항목은 *(으)로 표시합니다