Quick Flow
command는 “키를 눌렀다”가 아니라 “어떤 상태 변경을 어떤 입력으로 실행한다”를 값으로 남깁니다. replay·AI·undo가 필요하면 input source가 command를 만들고, simulation이 정해진 순서로 적용하며, UI·audio·network 같은 부가 효과는 결과 event로 분리합니다.
public interface ICommand
{
void Execute(GameState state);
}
public readonly record struct MoveCommand(int ActorId, int Dx, int Dz) : ICommand
{
public void Execute(GameState state) => state.Move(ActorId, Dx, Dz);
}
// Update에서 만든 command를 simulation tick 순서로 적용합니다.
commandQueue.Enqueue(new MoveCommand(playerId, 1, 0));| 요구 | command에 남길 것 | 별도 처리할 것 |
|---|---|---|
| replay·network debug | tick, actor, input, deterministic state 변경 | 화면 연출·audio·toast |
| undo | 이전 값 또는 역연산 가능 조건 | 이미 외부로 전송된 부작용 |
| AI와 player 입력 통일 | 같은 command type과 validation | input device 차이 |
| 단순 버튼 토글 | 보통 직접 호출이 더 단순 | 불필요한 history 객체 |
실행과 기록의 경계
직접 player.MoveLeft()를 호출하면 즉시 행동은 일어나지만 무엇이 언제 실행됐는지를 값으로 보관하기 어렵습니다. command에는 intent와 실행에 필요한 최소 input을 넣고, invoker 또는 simulation loop가 실행 순서를 책임집니다. 여러 command가 같은 frame에 들어오면 queue 순서, validation 실패, 취소·재시도 정책을 명시하지 않으면 replay가 달라질 수 있습니다.
turn-based game에서는 command history가 undo/replay에 바로 쓰일 수 있습니다. real-time game에서도 Update에서 input을 sample하고 fixed simulation tick에서 command를 소비하면 frame timing과 simulation order를 분리할 수 있습니다. 고정 tick만으로 network determinism이 보장되지는 않으므로 random seed, state snapshot, platform/physics 차이도 별도 계약입니다.
undo와 부가 효과
undo는 Undo() 메서드를 붙이는 일만으로 끝나지 않습니다. command가 바꾸기 전 state를 저장할지, 역연산이 가능한지, 뒤에 실행된 command와 충돌하면 무엇을 할지 결정해야 합니다. 체력 10을 빼는 command는 이전 체력을 저장해 되돌릴 수 있지만, 외부 결제·서버 전송·다른 사용자의 행동처럼 이미 관측된 부작용은 같은 방식으로 되돌릴 수 없습니다.
상태 command: 월드 state를 변경하고 replay·undo 대상이 될 수 있음
결과 event: animation, sound, UI를 구동하지만 replay 때 중복 재생 정책이 필요함
외부 action: 승인·idempotency·보상 처리까지 별도 소유권이 필요함도입 여부 판단
command가 유용한 기준은 객체지향 취향이 아니라 행동을 값처럼 저장·지연·재생·검증해야 하는가입니다. 한 번 실행하고 잊는 UI 토글에는 직접 함수가 읽기 쉽습니다. 반면 actor, tick, payload, validation result가 남아야 하는 action에는 command 경계가 test와 debug를 단순하게 만듭니다.
command 하나에 damage 계산, network 송신, animation, audio, UI를 모두 넣으면 replay와 undo의 범위가 무너집니다. 먼저 state mutation의 owner를 고정하고, 관측용 효과와 외부 부작용은 결과 event·별도 workflow로 분리하세요.
참고 링크
2 sources