[Unity] 원활한 카메라 활용을 위한 CameraToken

2023. 12. 29. 02:55코드 전시관/Unity

카메라 효과를 활용할 때마다 항상 고민하던 것은,

두 카메라 효과가 충돌하면 어쩌나다.

 

예를 들어, 플레이어의 스킬이 카메라를 움직이고있는데 스테이지가 클리어 돼 여기에 해당하는 카메라 움직임이 나와야하는 경우다.

물론 매 케이스마다 우선순위를 두거나 해서 이런 케이스를 처리할 수 있겠지만,

코루틴이 카메라 토큰을 부여받아 토큰을 가진 사용자(코루틴)만 카메라를 활용할 수 있게 하는 방법을 떠올렸다.

토큰을 받기 위해 대기하는 사용자들은 큐에 들어가며, 우선순위에 따라 이전 카메라 효과가 끝나면 토큰을 받는다.

 

대략적인 사용 예시는 아래와 같다.

    IEnumerator LastConnectionCameraCoroutine(LanternRelation relation)
    {
        CameraControlToken token = new CameraControlToken(CameraPriority.SceneChange);
        yield return new WaitUntil(() => token.IsAvailable);

        token.Camera?.StartFollow(~~~);
        token.Camera?.StartConstantShake(_myshake);
        yield return new WaitForSecondsRealtime(0.5f);
        token.Camera?.StopConstantShake();
        yield return new WaitForSecondsRealtime(_1f);
        token.Release();
    }

 

CameraControllToken 객체를 생성하면 CameraTokenQueue에 들어가게 되고,

큐에 아무것도 없거나 CameraPriority.AlwaysOverride가 우선순위로 지정돼있다면 카메라 토큰을 획득한다.

이는 token.IsAvailable로 확인할 수 있고, 토큰을 가지고있다면 token.Camera를 사용할 수 있다. (토큰이 없으면 token.Camera가 null임)

 

이후 token.Camera?.효과() 를 이용해 카메라 효과를 활용한다.

여기서 token.Camera는 효과들을 정의한 임의의 카메라 컨트롤러다.

 

카메라 사용이 끝난 후엔 token.Release()를 활용해 토큰을 놓아준다.

이걸 c++의 RAII처럼 구현해보려했는데, C#은 가비지콜렉터가 객체 소멸을 담당하기 때문에 이런 패턴을 활용할 수 없었다.

 

카메라 토큰을 받으려고 하는 사용자가 많으면 우선순위 큐에 들어가게 되는데, 이 큐가 CameraControlToken의 내부 클래스로 정의된 CameraTokenQueue다.

class CameraTokenQueue
{
    List<CameraControlToken> _queue = new List<CameraControlToken>();

    public void Enqueue(CameraControlToken token)
    {
        int idx = _queue.FindLastIndex(x => x._priority < token._priority);
        if (idx == -1)
            _queue.Add(token);
        else
            _queue.Insert(idx, token);
    }
    public CameraControlToken Dequeue()
    {
        if (_queue.Count == 0)
            return null;
        var token = _queue[0];
        _queue.RemoveAt(0);
        return token;
    }
    public void Remove(CameraControlToken token)
    {
        _queue.Remove(token);
    }
    public CameraControlToken Peek()
    {
        if (_queue.Count == 0)
            return null;
        return _queue[0];
    }
    public bool IsEmpty { get { return _queue.Count == 0; } }
    public void Clear()
    {
        _queue.Clear();
    }
}

 

 

CameraControlToken은 토큰 취득과 취득 확인에 관한 기능이 들어있으며,

static 변수인 currentOwningToken과 tokenQueue를 이용해 누가 토큰을 가지고 있는지와 대기 중인 큐를 관리한다.

 

public enum CameraPriority
{
    Lowest, LightDoorOpen, Cutscene, SceneChange, AlwaysOverride
}

public class CameraControlToken
{
    static CameraControlToken _currentOwningToken = null;
    static CameraTokenQueue _tokenQueue = new CameraTokenQueue();
    static bool IsNothingQueued { get { return _tokenQueue.IsEmpty && _currentOwningToken == null; } }
    
    public CameraController Camera { get; private set; } = null;
    public bool IsAvailable { get { return _currentOwningToken == this; } }
    
    CameraPriority _priority;
    
    public CameraControlToken(CameraPriority priority)
    {
        _priority = CameraPriority.Lowest;
        if (priority == CameraPriority.AlwaysOverride)
        {
            ChangeCurrentOwningToken(this);
        }
        else
        {
            if (_currentOwningToken == null)
                ChangeCurrentOwningToken(this);
            else
                _tokenQueue.Enqueue(this);
        }
    }
    public void Release(bool resetCameraStateIfNothingQueued = true)
    {
        if (resetCameraStateIfNothingQueued && _tokenQueue.IsEmpty)
        {
            Camera?.ResetCameraSettings();
        }
        _tokenQueue.Remove(this);
        ChangeCurrentOwningToken(_tokenQueue.Dequeue());
    }
    void ChangeCurrentOwningToken(CameraControlToken token)
    {
        if (_currentOwningToken != null)
            _currentOwningToken.Camera = null;
        _currentOwningToken = token;
        if (_currentOwningToken != null)
            _currentOwningToken.Camera = UnityEngine.Camera.main.GetComponent<CameraController>();
    }
    public static void ClearQueue()
    {
        _currentOwningToken = null;
        _tokenQueue.Clear();
    }

 

 

아직 카메라 토큰을 사용하다가 우선순위에 의해 뺏겼을 때 진행하던 작업을 잠시 멈추거나 중단하는 기능은 없다.

사실 이건 사용자측에서 처리해야하는 것 같긴 하지만, 토큰이 뺏겼는지 체크하는 코루틴을 하나 더 돌려서 정상적으로 종료해줄 수 있는 방법이 있을 것 같다.

반응형