무엇을 만들었나
UNET의 권한(Authority) 제어와 제네릭 FSM 패턴을 결합하여, 확장 가능하고 안정적인 2D 네트워크 캐릭터 컨트롤러를 설계하는 것을 목표로 했다.
| 항목 | 내용 |
|---|---|
| 엔진 | Unity 2018.3.12f1 |
| 기여도 | 100% (1인 개발) |
| 네트워크 | UNET (isLocalPlayer 권한 분리 · [Command] · [ClientRpc]) |
| 상태 관리 | 제네릭 StateMachine<T> 직접 구현 (Animator 미사용) |
| 상태 | 완성 |

- 제네릭 FSM: 타입 안정성(
Type-Safe)과 소유권(Owner) 접근이 용이한 커스텀 상태 머신 시스템 구현. - 2D 애니메이션 네트워크 동기화: Animator 컴포넌트 의존 없이, 각 상태(Idle · Run · Jump)에 맞는
SpriteRenderer를[Command]와[ClientRpc]로 동기화. - 입력 및 물리 동기화: 로컬 플레이어의 키 입력에 따른
Rigidbody2D기반 이동·점프 물리 연산을 서버를 거쳐 브로드캐스트.
상태를 클래스로: PlayerObject 와 State
PlayerObject 는 내부에 FSM 인스턴스를 하나 들고 있다. IdleState, RunState, JumpState 각각이 자기 클래스이고, 입력 처리 결과에 따라 Invoke<STATE>() 로 다른 상태로 넘어간다.
private StateMachine<PlayerObject> _stateMachine = null;
void Start ()
{
ShowSprite( eSprite.Idle );
if( !isLocalPlayer ) return;
_stateMachine = new StateMachine<PlayerObject>( this );
StartCoroutine( _stateMachine.Coroutine<IdleState>() );
}
// RunState의 Update 예시
protected override void Update()
{
if( Input.GetKey( KeyCode.LeftArrow ) )
{
Owner.CmdMove( true );
Owner.CmdLookAt( true );
}
else if( Input.GetKey( KeyCode.RightArrow ) )
{
Owner.CmdMove( false );
Owner.CmdLookAt( false );
}
else
{
Invoke<IdleState>();
}
if( Input.GetKey( KeyCode.Space ) ) Invoke<JumpState>();
}
Start() 에서 !isLocalPlayer 면 상태 머신을 아예 만들지 않는다. 남의 캐릭터는 입력을 읽을 필요가 없고, 화면에 보이는 스프라이트만 서버가 알려 주는 대로 따라가면 되기 때문이다. 대신 스프라이트 초기화(ShowSprite)는 그 검사 앞에 있어서 모든 클라이언트에서 실행된다.
RunState 의 Update 를 보면 상태 안에서 Owner.CmdMove() 를 바로 호출한다. 상태 클래스가 자기를 소유한 객체를 알고 있어서 가능한 형태다.
Animator 를 쓰지 않고 스프라이트를 바꾼다
[Command]
private void CmdShowSprite(eSprite type)
{
RpcShowSprite( type );
}
[ClientRpc]
private void RpcShowSprite( eSprite type )
{
if( !isClient ) return;
ShowSprite( type );
}
private void ShowSprite( eSprite type )
{
HideAllSprite();
switch( type )
{
case eSprite.Idle: _spriteIdle.enabled = true; break;
case eSprite.Run: _spriteRun.enabled = true; break;
case eSprite.Jump: _spriteJump.enabled = true; break;
}
}
상태가 바뀌면 클라이언트가 CmdShowSprite 로 서버에 알리고, 서버는 RpcShowSprite 로 모든 클라이언트에게 그 유저의 스프라이트를 바꾸라고 명령한다. ShowSprite 는 전부 끈 뒤 해당 상태의 SpriteRenderer 하나만 켠다.
[Command] 로 보낼 수 있다. 동기화 대상이 '애니메이션'이 아니라 '상태'가 되는 셈이다.제네릭 상태 머신의 뼈대
구조체나 enum 방식의 단순 조건문 분기를 탈피하려고 만든 제네릭 기반 상태 머신이다. Owner 접근성을 확보해서 각 상태 내부에서 대상 객체의 메서드를 직접 부를 수 있도록 설계했다.
// BaseStateMachine 클래스의 주요 상태 전환 메서드
virtual protected STATE ChangeState<STATE>() where STATE : BASE_STATE_TYPE, new()
{
StopState( _currentStateType );
SetCurrentState<STATE>();
return (STATE)_currentState;
}
// BaseState 클래스의 코루틴 및 Update 루프
private IEnumerator Coroutine()
{
Begin();
yield return Enter();
while( _bActive )
{
yield return null;
Update();
}
End();
}
where STATE : BASE_STATE_TYPE, new() 제약 덕분에 잘못된 타입을 넘기면 컴파일 단계에서 걸린다. 상태 하나의 수명은 코루틴 하나가 통째로 들고 있다. Begin → Enter → (Update 반복) → End 순서가 코드에 그대로 드러나서, 상태를 벗어날 때 정리해야 할 것을 End 한곳에 모을 수 있다.
정리
- 유니티 내장 Animator 를 쓰지 않고 상태 머신 코드를 직접 구현해 봄으로써, 디자인 패턴의 강력함과 확장성을 깊이 체감할 수 있었다.
- 이를 UNET RPC 로직과 매끄럽게 연결시키면서, 캐릭터 애니메이션과 행동 상태를 멀티플레이어 환경에서 어떻게 통제하고 동기화하는지에 대한 기술적 기반을 다질 수 있었다.
- 상태를 클래스로 분리하고
Owner를 들려 주는 구조는, 상태 안에서 네트워크 호출을 바로 하게 해 준다는 점에서 네트워크 게임과 궁합이 좋았다.