개요
Wild Bowl : Zooports에서 구현한 튜토리얼 시스템에 대해 설명하는 글이다.
내용
배경
게임 출시를 앞두고 다양한 유저들을 대상으로 테스트를 진행한 결과, 조작법이 어렵다는 피드백이 다수 확인되었다. 이를 해결하기 위해 튜토리얼을 개발하게 되었다.
효율적인 튜토리얼 컨텐츠 추가를 위해, 우선적으로 튜토리얼 시스템을 설계하고 구축하였다.
튜토리얼 시스템 개발 시, 특히 중요하게 고려한 부분은 다음과 같다:
- 튜토리얼 순서의 보장: 컨텐츠의 순서를 유지하여 자연스럽고 체계적인 학습 과정을 제공한다.
- 단계별 작업의 안전성: 각 진행 단계에서 오류 없이 안정적이고 신뢰성 있게 작동할 수 있는 구조를 구축한다.
이 두 가지 원칙을 바탕으로 튜토리얼 개발에 집중하였다.
설계 전략
1. 데이터를 Queue에 저장하여 컨텐츠 순서를 보장
첫 번째 아이디어는 튜토리얼 컨텐츠를 큐 형태로 관리하는 것이다. 이를 통해 컨텐츠의 순서를 자연스럽게 보장할 수 있으며, Queue를 사용함으로써 순서를 유지해야 한다는 설계 의도를 표현하고자 했다.

위의 시퀀스 다이어그램과 같이 시스템을 구축하였고 아래와 같이 구현하였다.
// TutorialPhaseManager 클래스 내부
public void ServerOnInit()
{
List<BasicTutorialPhase> tutorialStates = ResourceHelper.GetTutorialContents();
foreach (var phase in System.Enum.GetValues(typeof(BasicTutorialPhase)))
{
if (!tutorialStates.TryGetValue((BasicTutorialPhase)phase, out TutorialStateBase state)) continue;
_tutorialPhaseQueue.Enqueue(state);
}
ServerContentSetupComplete();
}
public async UniTaskVoid ServerOnStartNextPhaseAsync()
{
var currentPhase = _tutorialPhaseQueue.Dequeue();
_currentPhase = currentPhase.MyPhase;
const int NEXT_PHASE_DELAY_MS = 1000;
await UniTask.Delay(NEXT_PHASE_DELAY_MS);
currentPhase.ServerStartTutorialPhase().Forget();
ClientRpcSetUpGameBallBottomUI();
}
위 코드는 튜토리얼 시스템의 주요 흐름을 처리하는 TutorialPhaseManager 클래스의 일부이다.
- ServerOnInit: 컨텐츠 데이터를 로드하여 큐에 저장하고 서버 셋업 완료를 알림
- ServerOnStartNextPhaseAsync: 다음 튜토리얼 단계를 시작하며, 딜레이 후 단계별 초기화를 진행
이 구조를 통해 튜토리얼 단계를 명확히 구분하고 순차적으로 실행할 수 있도록 설계하였다.
컨텐츠 순서를 수동으로 관리하면 귀찮기도 하며 잘못된 순서로 실행되는 오류가 발생할 수 있다. Queue를 이용함으로써 문제를 해결하였다.
또한, 개발자는 튜토리얼 컨텐츠를 구현한 뒤, 페이즈 열거형만 설정하면 된다. 이를 통해 추가적인 순서 관리 없이도 컨텐츠를 올바른 순서로 실행할 수 있다.
2. 컨텐츠의 진행 상태를 나누고, 게임 진행에 필요한 데이터를 초기화 및 정리하도록 설계
구현에는 State 패턴을 참고하였다.
State 패턴의 핵심은 상태별 로직을 개별 클래스로 분리하고, 상태 전환 시 각 상태가 필요한 작업을 스스로 처리하도록 만드는 것이다. 이 점에서 영감을 받아, 튜토리얼 단계를 Enter, Playing, Exit 상태로 나누고, 각 상태가 자신의 책임을 명확히 수행하도록 설계하였다.
예를 들어, 첫 번째 튜토리얼에서는 장애물이 배치되어 있으며, 유저는 무빙만 가능하도록 제한된다.
- Enter 상태에서는 장애물을 셋업하고, 플레이어 UI 컨트롤러의 Canvas.enabled를 조정하는 등의 초기화 작업이 이루어진다.
- Playing 상태에서는 유저가 튜토리얼 도중 맵을 벗어난 경우 시작 지점으로 되돌리거나, 골인을 했는지 여부를 체크한다.
- Exit 상태에서는 Enter 단계에서 생성된 장애물을 정리하고, 다음 단계 진행을 위한 준비 작업을 수행한다.
이와 같은 구조를 통해 단계별로 필요한 작업을 명확히 정의하고 관리할 수 있었다.
// TutorialStateBase 클래스 내부
protected virtual void ServerOnSequenceEnter()
{
var tutorialGameManager = GameManagerBase.Instance as BasicTutorialGameManager;
Debug.Assert(tutorialGameManager);
var userCharRoot = tutorialGameManager.UserCharacterRoot;
Debug.Assert(userCharRoot);
userCharRoot.CharacterStateController.AddState(CharacterActionState.MoveLock);
}
protected virtual void ServerOnSequencePlaying()
{
var tutorialGameManager = GameManagerBase.Instance as BasicTutorialGameManager;
Debug.Assert(tutorialGameManager);
var userCharRoot = tutorialGameManager.UserCharacterRoot;
Debug.Assert(userCharRoot);
userCharRoot.CharacterStateController.DeleteState(CharacterActionState.MoveLock);
}
protected virtual void ServerOnSequenceExit()
{
ServerOnClear();
ServerOnNextSequence().Forget();
}
// TutorialStateExecutor 클래스 내부
private void ServerOnGoalAreaEntered()
{
var characterRoot = BasicTutorialGameManager.Instance.UserCharacterRoot;
characterRoot.CharacterStateController.AddState(CharacterActionState.MoveLock);
characterRoot.AnimationController.ServerSetAnimation(PlayerAnimState.Idle);
ClientSendTutorialStateToServer(TutorialState.EXIT);
}
private void ServerOnRetry()
{
// 초기화 작업들...
}
상태를 명확히 구분함으로써 복잡한 로직으로 인한 혼란과 수정 시 발생할 수 있는 오류를 방지하고, 튜토리얼 진행 과정을 보다 체계적으로 분리할 수 있었다.
마무리
Queue를 활용한 컨텐츠 순서 보장과 상태별 작업 분리를 통해, 유지보수와 확장성을 모두 고려한 시스템을 완성하였다.
이번 프로젝트를 통해 명확한 구조와 책임 분리의 중요성을 깊이 이해하게 되었다. 시스템을 체계적으로 설계하고 구현하니 이후에는 튜토리얼 컨텐츠만 추가하면 되는 단순한 작업으로 전환되어, 업무를 빠르고 효율적으로 마칠 수 있었던 경험이 있다.
이 경험을 토대로, 앞으로 더 복잡한 시스템에서도 효율적인 설계를 적용하고, 문제 해결 능력을 더욱 발전시킬 수 있을 것이라 확신한다.
'프로젝트 > Wild Bowl : Zooports' 카테고리의 다른 글
Xcode iOS 빌드 이슈 해결 (0) | 2025.01.06 |
---|---|
인앱 결제 복구 이슈 해결 (0) | 2025.01.05 |
상점 데이터 갱신 방식 개선 (0) | 2025.01.04 |