졸업까지 100턴의 학생은 전투에서는 스스로 싸우고, 직접 조작할 때는 패드를 따르고, 보드에서는 말이 되고, 교실에서는 산책합니다. 이 모든 모습이 같은 캐릭터 클래스입니다. 상태 머신과 행동 객체를 나눠 둔 구조를 보겠습니다.
상황마다 캐릭터 클래스를 만들 수는 없습니다
같은 학생이 화면에 따라 전혀 다르게 움직여야 합니다. 상황마다 캐릭터를 따로 만들면 능력치, 버프, 스킬, 애니메이션 코드가 모두 복사됩니다.
그래서 캐릭터가 무엇인지와 지금 어떻게 움직이는지를 나눴습니다.
세 층 구조
상태 머신
상태가 바뀌면 들어가는 처리, 애니메이션 전환, 나오는 처리가 차례로 불립니다. 매 프레임에는 지금 상태로 Do 가 불립니다.
public void SetState(ElStateTypes st)
{
if (currentType == st) return;
ElStateTypes prevType = currentType;
currentType = st;
if (attackDelayRoutine != null)
{
StopCoroutine(attackDelayRoutine);
}
Entry(prevType, currentType);
SetAnimation(StateToAnimState(st));
Exit(prevType, currentType);
}이 구현은 새 상태의 Entry 를 먼저, 앞 상태의 Exit 를 나중에 부릅니다. 흔히 쓰는 Exit 다음 Entry 와 순서가 반대라서, 새 상태에 들어가는 코드가 앞 상태의 정리가 끝났다고 가정하면 안 됩니다.
행동은 Strategy 에 맡깁니다
TDCharacter 는 상태 처리를 자신이 들고 있는 Strategy 에 그대로 넘깁니다. 스킬 자동 사용 여부도 Strategy 가 정합니다.
protected override void Do(ElStateTypes currentType)
{
if (warmUpTime > 0f)
warmUpTime -= Time.deltaTime;
else
warmUpTime = 0f;
strategy.Do(currentType);
if (strategy.AutoPlaySkill && activeSkill1 != null)
{
activeSkill1.CheckCondition(currentType);
}
if (strategy.AutoPlaySkill && activeSkill2 != null)
{
activeSkill2.CheckCondition(currentType);
}
if (strategy.AutoPlaySkill && activeSkill3 != null)
{
activeSkill3.CheckCondition(currentType);
}| Strategy | 쓰이는 장면 |
|---|---|
| Offensive · Defensive | 전투 AI. 적을 찾고 따라가 공격합니다 |
| Playable | 직접 조작. 가상 패드와 길게 눌러 쓰는 스킬 |
| Board | 보드의 말. 칸 단위로 이동하고 도착을 알립니다 |
| Spline | 교실과 도서관의 산책 NPC |
| Hammer | 대장간 미니게임 |
| Idle | 가만히 서 있기 |
실행 중에 행동을 갈아 끼웁니다

전투 중 조작할 캐릭터를 바꾸면, 새 캐릭터에는 조작용 Strategy 를 씌우고 앞 캐릭터에게는 원래의 AI Strategy 를 돌려줍니다.
lastStrategy = chr?.GetStrategy();
TDStrategy_Playable stg = new TDStrategy_Playable(chr, field);
stg.SetVirtualPad(virtualPad);if (lastStrategy != null && lastController != null)
{
var owner = lastController.GetFollowingCharacter();
owner.SetStrategy(lastStrategy);
lastStrategy.StopMove();
owner.SetState(ElStateTypes.WAITING);
}Strategy 는 만들어지는 순간 스스로 캐릭터에 등록됩니다. 그래서 new 가 곧 교체입니다.
public TDStrategy(TDCharacter owner, TDField field)
{
this.owner = owner;
this.field = field;
this.owner.SetStrategy(this);
}디자이너가 그린 길이 이동 그래프가 됩니다

산책 NPC 의 길은 코드로 만들지 않습니다. 디자이너가 에디터에서 스플라인을 그리고 양 끝에 노드 번호를 달면, 코드가 그 번호를 읽어 이웃 관계를 만듭니다.
public SplineGraph(SplineContainer container)
{
splineContainer = container;
foreach (var spline in container.Splines)
{
if (!spline.TryGetIntData(NodeKey, out var intDatas) || intDatas.Count < 2) continue;
var nodeIds = intDatas.Select(p => p.Value).ToList(); // 노드 연결
for (var i = 0; i < splineInNodes.Count; i++)
{
var node = splineInNodes[i];
var neighborId = nodeIds.First(p => p != node.Id);
node.AddNeighbor(neighborId, spline, node.Id != nodeIds[0]);
}
}NPC 는 노드에서 잠시 쉬다가 이웃 가운데 하나를 골라 걸어갑니다. 동선을 바꾸고 싶으면 스플라인만 다시 그리면 됩니다.
돌아보며
잘된 점
- 새 행동은 Strategy 클래스를 만들어 추가합니다.
- 보드의 말, 산책 NPC, 미니게임 캐릭터를 새 캐릭터 클래스 없이 만들었습니다.
- 조작 전환은 Strategy 를 바꿔 끼우면 끝납니다.
아쉬운 점
- 기본 공격 로직이 부모 Strategy 에 들어 있어, Board · Spline · Hammer 는 부모의 Do 를 부르지 않는 방식으로 피해 갑니다.
- TDCharacter 가 1,768줄로, 능력치부터 피해 처리까지 이 클래스가 맡고 있습니다.
- Entry 와 Exit 의 순서가 일반적인 상태 머신과 반대입니다.
정리
- 무엇인지와 어떻게 움직이는지를 나눕니다. 캐릭터는 하나, 행동은 갈아 끼웁니다.
- 상태는 프레임워크, 행동은 게임. 상태 머신은 사내 프레임워크가, 상태별 행동은 게임 코드가 맡습니다.
- 데이터로 그린 길. 동선은 코드가 아니라 디자이너가 그린 스플라인입니다.
다음 편에서는 같은 프로젝트를 앱, PC, 웹, 게임 포털, 토스 미니앱으로 나눠 출시한 방법을 보겠습니다.