내일이 부트캠프 유니티 입문 주차 과제 제출일인데, 문득 지난주에 프로젝트를 완료하고 Cinemachine과 비동기 처리에 대한 트러블 슈팅만 정리했지, 전체 프로젝트 리뷰를 작성하지 않았다는 걸 깨달았다. 3일이나 늦었지만 이제라도 정리해보려 한다.
Cinemachine을 처음 사용해보면서 카메라 시스템에 대한 새로운 시각을 얻었다. 특히 매니저 클래스 하나를 거의 대체할 수 있다는 점이 인상적이었다. 지난 주차에 Input System을 배웠을 때만큼 기뻤던 순간이었다.
다만 카메라 사이즈를 완벽하게 자유롭게 조절할 수 없다는 제약이 약간 아쉬웠다. 하지만 코드 관리 측면에서의 이점이 이 단점을 상쇄하고도 남는다고 생각한다.
비동기 처리에 대한 이해가 한층 깊어진 주간이었다. 특히 CancellationToken과 CancellationTokenSource의 관계를 명확히 이해하게 되었다.
CreateLinkedTokenSource(): 두 개의 토큰을 결합하여 AND 조건 구현GetCancellationTokenOnDestroy(): 오브젝트가 파괴될 때 함께 취소되는 Source 생성token.ThrowIfCancellationRequested(): 전달받은 토큰의 취소 여부 확인UniTask 기반 정적 메서드는 token을 인자로 받을 수 있음async void는 해당 메서드의 결과를 확인할 방법이 전혀 없고, 예외 발생 시 처리할 수 없어 애플리케이션이 크래시될 수 있다. 이를 대체하기 위해 UniTaskVoid가 등장했다.
처음에는 someAsyncTask().Forget()과 async UniTaskVoid someAsyncTask()가 같은 것 아닌가 생각했다. 결과적으로는 동일하게 "비동기 처리 후 결과를 기다리지 않는다"는 의미지만, 중요한 차이점이 있다.
UniTaskVoid의 특징:
UniTaskScheduler.UnobservedTaskException으로 전역 핸들러에 전달됨Start() 같은 void 시그니처를 요구하는 메서드에서 사용하기 위해 존재try-catch로 예외를 처리하거나, 전역 핸들러를 설정해야 함Forget()의 특징:
UniTask인 메서드에 체이닝해서 사용핵심은 사용 목적의 차이다. UniTaskVoid는 Unity 생명주기 메서드처럼 void 반환 타입이 필수인 곳에서 비동기 작업을 수행하기 위해 사용하고, Forget()은 이미 UniTask를 반환하는 메서드의 결과를 의도적으로 무시할 때 사용한다.
처음에는 꽤 깔끔한 구조로 시작했다:
기존프로젝트에서
RuntimeInitializerRuntimeSceneLoaderBootstrapSceneLoaderSoEditorCLoggerSceneTransitionManagerSceneTransitionViewVContainer를 사용하기엔 과할 것 같기도 하고, 이번 기회에 ServiceLocator 패턴을 시도해보기로 했다. TitleManager와 TitleView도 빠르게 작성하고 개발을 진행했다.중반을 넘어가면서 "이게 맞나?" 라는 의구심이 들기 시작했다.
초기 설계 없이 그때그때 필요한 기능을 만들다 보니, 확장이 필요한 순간마다 "리팩토링" 과 "설계 무시" 사이에서 고민해야 했다. 일주일짜리 과제에서 매번 리팩토링하는 것은 비효율적이라 판단해 대부분 후자를 선택했고, 이는 프로젝트 구조를 점점 망가뜨렸다.
이 경험이 개인 프로젝트를 처음부터 다시 시작하게 된 계기가 되었다.
게임 개발 공부를 시작하기 전에는 고전적인 MVC 패턴 정도만 알고 있었다. 학습을 위해 이것저것 좋다는 디자인 패턴을 도입해서 사용해봤지만, 디테일한 이해가 부족했다.
깨달은 점:
나는 내 프로젝트에서 코드의 일관성을 전혀 고려하지 않았다.
개인적으로 가장 아쉬운 부분이다.
Action의 범주를 구체화하지 않았으며 CutScene을 단순히 Action을 실행하는 컨테이너로만 설정했던것이다.
그 결과 Action은 원래 의도했던 "플레이어와 오브젝트 간 상호작용" 이 아닌, 게임 내 모든 기능을 실행하는 만능 인터페이스로 변질되었으며,
CutScene은 컷씬 연출이 아닌, 게임의 모든 기능을 실행하는 만능 컨테이너로 변질되어버렸다.
뒤늦게 기존 프로젝트의 ActionSequencer를 가져오고 두 클래스의 범주를 한정할까 생각했지만, 때는 이미 너무 늦은후였다. 결국 또다시 설계 부재로 인한 문제였다.
Manager 네이밍 사용리팩토링하면 어렵지 않게 고칠 수 있었지만, 개인 프로젝트를 빨리 시작하고 싶은 마음에 그냥 마무리했다. 이것 역시 설계 부재에서 비롯된 문제다.
배움은 끝이 없다. 팀 프로젝트보다 개인 프로젝트를 진행할 때 하고 싶은 것을 마음껏 시도할 수 있어서, 지식적으로는 더 많이 성장하는 것 같다.
이번 프로젝트를 통해 초기 설계의 중요성과 일관성 있는 코드 작성의 가치를 뼈저리게 느꼈다. 다음 프로젝트에서는 이 교훈을 꼭 살려야겠다.