こんにちは.traPAdventCalendar2017の11月14日担当のOsa_Pyonです.
はじめに
ゲームを制作する際にプレイヤーの状態をどうやって記述,管理するかという課題は常に発生します.この記事では状態の記述,管理の方法として状態ごとにクラスを定義するStateパターンという手法を紹介していきます.
ゲームにおけるプレイヤーの状態
例としてマリオを考えてみます.マリオが持つ状態を列挙してみると
- Idle:入力待ち(地上)
- Run:入力方向へ移動
- InAir:空中にいる
- HipDrop:ヒップドロップ
こんな感じになりますね.それぞれの状態から状態への遷移図はこの様になります.
switchで書いてみた
前述の状態遷移をswitch文を使って実装しました.言語はc#です.
Mario.cspublic class Mario {
//状態
enum State{Idle,Run,InAir,HipDrop};
State state = State.Idle;
//毎フレーム実行される
void Update() {
switch (state) {
case State.Idle:
if(/*ジャンプ入力*/) {
state = State.InAir;
//ジャンプ処理
Jump();
break;
}else if(/*移動入力*/) {
state = state.Run;
break;
}
break;
case State.Run:
if(/*ジャンプ入力*/) {
state = State.InAir;
//ジャンプ動作
Jump();
break;
}else if(/*移動入力*/) {
//走るという処理
Run();
break;
}else{
state = State.Idle;
break;
}
break;
case State.InAir:
if(/*ヒップドロップ入力*/) {
state = State.HipDrop;
break;
}else if(/*地上にいる*/) {
state = State.Idle;
break;
}
break;
case State.HipDrop:
if(/*地上にいる*/) {
state = State.Idle;
}else{
//ヒップドロップの処理
HipDrop();
break;
}
break;
}
}
}
}
このコードでは実感し難いですが状態数が10や20とかになって状態による分岐がUpdate以外の場所でも出現した上で,振る舞いを変更したり状態を増やしたりする時,全ての分岐をチェックし書き直さなくてはなりません.いやです.
Stateパターンを用いて書き直す
Stateパターンで上のコードを書き直すと次のようになります.
State.csinterface State {
State Execute();
}
StateProcessor.cspublic class StateProcessor {
State state;
public StateProcessor() {
state = new Idle();
}
//毎フレーム呼び出される
void Update() {
state = state.Execute();
}
}
Idle.cspublic class Idle : State {
public State Execute() {
if(/*ジャンプ入力*/) {
//ジャンプ処理
Jump();
return new InAir();
}else if(/*移動入力*/){
return new Run();
}else{
return this;
}
}
}
Run.cspublic class Run : State {
public State Execute() {
if(/*ジャンプ入力*/) {
//ジャンプ動作
Jump();
return new InAir();
}else if(/*移動入力*/) {
//走るという処理
Run();
return this;
}else{
return new Idle();
}
}
}
InAir.cspublic class InAir : State {
public State Execute() {
if(/*ヒップドロップ入力*/) {
return new HipDrop();
}else if(/*地上にいる*/) {
return new Idle();
}else{
return this;
}
}
}
HipDrop.cspublic class HipDrop : State {
public State Execute() {
if(/*地上にいる*/) {
return new Idle();
}else{
//ヒップドロップの処理
HipDrop();
return this;
}
}
}
この様にすることで,例えばHipDropからの遷移を追加する時やHipDropの挙動を変更する時にHipDropクラスのみをいじれば良くなりました(書き換え前だとコード全体をチェックしなくてはいけません).うれしい.ちなみに状態遷移の仕方は色々な派閥があるらしいです.
まとめ
この手法は僕が現在制作に関わっているゲームで管理が爆発した時に出会ったデザインパターンです.今回解説した手法以外にも状況に応じた様々な先人の知恵が存在するのでそれらをゴリゴリ使っていきましょう.
明日はPolyさんの記事です.