はじめに
様々な言語で「デザインパターン」の本が世の中にありますが、筆者個人の経験では
いまいちピンとこない例
いまいちピンとこないコード
で説明されてることが多く、
結局これっていつ使うの?
という疑問に答えるには仕事仲間等との議論をしないと
辿り着けないことが多々ありました。
そこで特に「ゲーム開発ではどう使うか?」にフォーカスを当てて、実践的な例を交えて
デザインパターンの説明の需要があると思い記事を作りました。
デザインパターンを学ぶ理由
デザインパターンを学ぶ理由としては
- 車輪の再発明の防止
- 長文で読みにくいコード(可読性の低いコード)を減らす
- コードを疎結合にして変更に強くなる(変更時のコスト・変更箇所を減らす)
- モジュールとして使いまわせるように、コードの再利用性を高める
といった効果を期待できます。
対象読者
Unity 全くの初心者(インストールしただけで触ったことがないような方)はお断りです。
最低限以下のことは理解・経験を積んでおくことが必須になります。
- MonoBehaviour 継承クラスでコードを書いたことがある
- C# のピュアクラスを用いた自作クラスを作ったことがある
- クラスの継承という概念は知っている
そのため、脱・初心者
中級者へのステップアップ
として デザインパターンを学ぶ
のが良いと思います。
デザパタ記事リンク
生成系
構造系
様態・ふるまい系
- Chain of Responsibility パターン
- Command パターン(本記事)
- Interpreter パターン
- Iterator パターン
- Mediator パターン
- Memento パターン
- Observer パターン
- State パターン
- Strategy パターン
- TemplateMethod パターン
- Visitor パターン
Command パターンについて
Commandパターンとはその名の通り、コマンド(命令・処理)をObjectとしてカプセル化するための方法です。
Commandパターンを用いることで処理の遅延実行や、敵AIの行動制御など様々なことが実装可能です。
ゲームにおける Commandパターン
オンラインの格闘ゲームはキー・ボタン入力が大事になるため、キー入力自体を同期することが多いですが、他のゲーム、例えばRTSではゲーム内の指示であるコマンドを入力する方法で実装されていたりします。
ここのコマンドは例えば 味方ユニットを生成する
とか 指定座標に新しく建築物の建築を開始する
といったものです。
当然パラメータも必要になるため、このCommandパターンは 指示コマンド(関数ポインタ or コマンドID)
と コマンドに必要なパラメータ
をセットで管理します。
以下に、ユニットの生成・移動のサンプルコマンドを記載しておきます。
public interface ICommand
{
bool Do();
bool Undo();
}
public abstract class BaseCommand : ICommand
{
protected abstract bool OnDoCommand();
protected abstract bool OnRedoCommand();
#region ===== ICommand =====
bool ICommand.Do() { return OnDoCommand();}
bool ICommand.Undo(){ return OnRedoCommand();}
#endregion //) ===== ICommand =====
}
public class CreateUnitCommand : BaseCommand
{
private GameObject m_target = null;
private Vector3 m_pos;
private GameObject m_instance = null;
public CreateUnitCommand(GameObject target, Vector3 position)
{
m_target = target;
m_pos = position;
}
protected override bool OnDoCommand()
{
if (m_target == null) return false;
m_instance = (GameObject)Object.Instantiate(m_target, m_pos, Quaternion.identity);
return true;
}
protected override bool OnRedoCommand()
{
if (m_instance == null) return true;
Object.Destroy(m_instance);
return true;
}
}
public class MoveUnitCommand : BaseCommand
{
private GameObject m_target = null;
private Vector3 m_prevPosition;
private Vector3 m_nextPosition;
public MoveUnitCommand(GameObject target, Vector3 position)
{
m_target = target;
m_nextPosition = position;
}
protected override bool OnDoCommand()
{
if (m_target == null) return false;
m_prevPosition = m_target.transform.position;
m_target.transform.position = m_nextPosition;
return true;
}
protected override bool OnRedoCommand()
{
if (m_target == null) return false;
m_target.transform.position = m_prevPosition;
return true;
}
}
このように組んでおけば、敵AIの行動などは
public class EnemyBrain : MonoBehaviour
{
private Queue<ICommand> commands = new Queue<ICommand>();
void Update()
{
if(commands.Count > 0)
{
var cmd = command.Dequeue();
cmd.Do();
}
}
}
のように書けば、毎フレームコマンドを実行してくれるようになります。
まとめ
Commandパターンは行動とパラメータをまとめて管理します。
これにより入力の抽象化や他プレイヤーやAIの代理実行などで非常に役に立ちます。