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();
}
아직 카메라 토큰을 사용하다가 우선순위에 의해 뺏겼을 때 진행하던 작업을 잠시 멈추거나 중단하는 기능은 없다.
사실 이건 사용자측에서 처리해야하는 것 같긴 하지만, 토큰이 뺏겼는지 체크하는 코루틴을 하나 더 돌려서 정상적으로 종료해줄 수 있는 방법이 있을 것 같다.
'코드 전시관 > Unity' 카테고리의 다른 글
[Unity] NestedClassMirror - Nested Class를 인스펙터에서 사용하기 (1) | 2024.10.12 |
---|---|
[HappyKit] C# Extension Method를 활용한 Vector 변환 기능 (0) | 2024.05.04 |
[HappyTools] GameBootstrapper, SingletonBehaviour (0) | 2023.03.21 |
[HappyTools] StateMachineBase (0) | 2023.03.13 |
[Unity] Lerp Coroutine 만들기 (0) | 2022.03.24 |