Unity プロジェクトに一般的なゲームプログラミングデザインパターンを実装すると、クリーンで整理された読みやすいコードベースを効率的に構築し、維持するのに役立ちます。デザインパターンは、リファクタリングとテストの時間を短縮し、開発プロセスをスピードアップし、ゲーム、チーム、ビジネスを成長させるための強固な基盤に貢献します。
デザインパターンは、コピーしてコードに貼り付けることができる完成したソリューションではなく、大規模でスケーラブルなアプリケーションの構築に役立つ追加のツールと考えてください。
このページでは、コマンドデザインパターンについて説明します。
こちらのコンテンツは、無料の電子書籍「ゲームプログラミングパターンでコードをレベルアップ」に基づいています。
Unity のゲームプログラミングデザインパターンシリーズのその他の記事は、Unity ベストプラクティスハブまたは以下のリンクからご覧ください。
コマンドプログラミングのデザインパターンは Gang of Four のオリジナルの 1 つで、特定の一連のアクションを追跡したい場合に便利です。元に戻す/やり直し機能を使用するゲームや、入力履歴をリストに保存しているゲームをプレイしたことがある場合は、コマンドパターンが機能しているのを目にしたことがあるでしょう。ユーザーが実際にターンを実行する前に複数のターンを計画できるストラテジーゲームを想像してみてください。それがコマンドパターンです。
コマンドパターンでは、アクションをオブジェクトとして表現できます。アクションをオブジェクトとしてカプセル化することで、ユーザーの入力に応じてゲームオブジェクトの動作を制御するための柔軟で拡張可能なシステムを作成できます。これは、メソッドを直接呼び出すのではなく、1つまたは複数のメソッド呼び出しを「コマンドオブジェクト」としてカプセル化することで機能します。次に、これらのコマンドオブジェクトを、小さなバッファとして機能するキューやスタックなどのコレクションに格納できます。
このようにコマンドオブジェクトを保存すると、一連のアクションを後で再生できるように遅延させることができるため、コマンドオブジェクトの実行タイミングを制御できます。同様に、やり直したり元に戻したりでき、各コマンドオブジェクトの実行を柔軟に制御できます。
さまざまなゲームジャンルでのこのパターンの一般的な用途は次のとおりです。
- リアルタイムストラテジーゲームでは、コマンドパターンを使用してユニットや建築アクションをキューに入れることができます。その後、リソースが使用可能になると、ゲームは各コマンドを実行します。
- ターン制ストラテジーゲームでは、プレイヤーはユニットを選択し、その動きやアクションをキューやその他のコレクションに保存できます。ターンの終わりに、ゲームはプレイヤーのキューにあるすべてのコマンドを実行します。
- パズルゲームでは、コマンドパターンによってプレイヤーがアクションを元に戻したりやり直したりすることができます。
- 格闘ゲームでは、特定のコマンドリストでボタンを押したりゲームパッドの動きを読み取ったりすると、コンボや必殺技が発生することがありました。
コマンドパターンなど、ゲーム開発のコンテキストにおけるさまざまなプログラミングデザインパターンを示すGitHubのサンプルプロジェクトを試してみてください。
このサンプルでは、プレイヤーは左側のボタンをクリックして迷路内を移動できます。プレーヤーが動き回ると、動きの軌跡が見えます。ただし、さらに重要なのは、以前のアクションを元に戻したりやり直したりできることです。
プロジェクト内の対応するシーンを見つけるには、「9 Command」という名前のフォルダーに移動します。
コマンドパターンを実装するには、アクションを含む一般的なオブジェクトが必要です。このコマンドオブジェクトには、実行するロジックと元に戻す方法が格納されます。
これを実装する方法はいくつかありますが、ICommand というインターフェースを使った簡単なバージョンを次に示します。
パブリックインターフェイス IComand
{
void Execute();
void Undo();
}
この場合、すべてのゲームプレイアクションが ICommand インターフェースを適用します (これは抽象クラスで実装することもできます)。
各コマンドオブジェクトは、それぞれ独自の Execute メソッドと Undo メソッドを担当します。そのため、ゲームにコマンドを追加しても、既存のコマンドには影響しません。
その後、CommandInvoker クラスがコマンドの実行と取り消しを行います。ExecuteCommand メソッドと UndoCommand メソッドに加えて、コマンドオブジェクトのシーケンスを保持するアンドゥスタックもあります。
サンプルプロジェクトでは、プレイヤーを小さな迷路の中で動かすことができます。プレイヤーの位置をシフトする簡単な方法は、PlayerMoverを作成することです。
そのためには、Vector3 を Move メソッドに渡して、プレイヤーを 4 つのコンパスの方向に沿って誘導する必要があります。レイキャストを使用して、適切な LayerMask で壁を検出することもできます。もちろん、コマンドパターンに適用したいものを実装することは、パターン自体とは別のものです。
コマンドパターンに従うには、PlayerMoverのMoveメソッドをオブジェクトとしてキャプチャします。Move を直接呼び出す代わりに、ICommand インターフェイスを実装する MoveCommand という新しいクラスを作成してください。
パブリッククラスの Moveコマンド:ICommand
{
プレイヤームーバープレイヤームーバー;
ベクトル3の動き;
パブリック移動コマンド (プレイヤームーバープレーヤー、ベクター 3 移動ベクトル)
{
this.playerMover = player;
this.movement = moveVector;
}
public void Execute()
{
playerMover.Move(movement);
}
public void Undo()
{
playerMover.Move(-movement);
}
}
ここで実現したいロジックが何であれ、移動ベクトルを指定して Move を呼び出します。
iCommand には、シーンを元の状態に戻すための Undo メソッドも必要です。この場合、Undo ロジックによって移動ベクトルが減算され、実質的にプレイヤーは反対方向に押しやられます。
MoveCommand には、実行する必要のあるすべてのパラメータが格納されます。これらをコンストラクターで設定します。この場合、適切な PlayerMover コンポーネントと移動ベクトルを保存します。
コマンドオブジェクトを作成して必要なパラメータを保存したら、CommandInvoker の静的な ExecuteCommand メソッドと undoCommand メソッドを使用して MoveCommand を渡します。これにより、MoveCommand の [実行] または [元に戻す] が実行され、[元に戻す] スタック内のコマンドオブジェクトが追跡されます。
インプットマネージャーは PlayerMover の Move メソッドを直接呼び出すことはありません。代わりに、RunMoveCommand というメソッドを追加して、新しい MoveCommand を作成し、それを CommandInvoker に送信してください。
次に、UI ボタンのさまざまな OnClick イベントを設定して、4 つの移動ベクトルを使用して RunPlayerCommand を呼び出します。
InputManagerの実装の詳細については、サンプルプロジェクトを確認してください。キーボードまたはゲームパッドを使用して独自の入力を設定することもできます。これで、プレイヤーは迷路をナビゲートできます。[元に戻す] ボタンをクリックすると、最初の正方形に戻ることができます。
リプレイアビリティやアンドゥアビリティの実装は、コマンドオブジェクトのコレクションを生成するのと同じくらい簡単です。コマンドバッファを使用して、特定のコントロールでアクションを順番に再生することもできます。
たとえば、特定のボタンを連続してクリックするとコンボムーブまたは攻撃がトリガーされる格闘ゲームを考えてみてください。コマンドパターンを使用してプレイヤーアクションを保存すると、これらのコンボの設定がはるかに簡単になります。
反対に、コマンドパターンは、他のデザインパターンと同様に、より多くの構造を導入します。アプリケーション内のコマンド・オブジェクトをデプロイするうえで、これらの追加クラスやインターフェースがどれだけのメリットをもたらすかを判断する必要があります。
基本を学んだら、コンテキストに応じて、コマンドのタイミングを変更したり、コマンドを連続して再生したり、逆に再生したりできます。
コマンドパターンを組み込む際には、次の点を考慮してください。
- その他のコマンドの作成:サンプルプロジェクトには、MoveCommand という 1 種類のコマンドオブジェクトしか含まれていません。ICommand を実装するコマンドオブジェクトはいくつでも作成でき、CommandInvoker を使用してそれらを追跡できます。
- やり直し機能を追加するには、別のスタックを追加するだけです。コマンドオブジェクトを元に戻すときは、それをやり直しの操作を追跡する別のスタックにプッシュします。これにより、取り消し履歴をすばやく切り替えたり、それらの操作をやり直したりできます。ユーザーがまったく新しいムーブメントを呼び出したら、やり直しスタックをクリアします(実装はサンプルプロジェクトにあります)。
- コマンドオブジェクトのバッファには別のコレクションを使用してください。先入れ先出し (FIFO) 動作が必要な場合は、キューの方が便利な場合があります。リストを使用する場合は、現在アクティブなインデックスを追跡します。アクティブなインデックスの前のコマンドは取り消すことができます。索引の後のコマンドはやり直すことができます。
- スタックのサイズを制限する:元に戻す操作とやり直す操作は、すぐに制御不能になる可能性があります。スタックのコマンド数は最小限にとどめてください。
- 必要なパラメーターをコンストラクターに渡します。これにより、MoveCommand の例にあるようなロジックをカプセル化できます。
- CommandInvoker は、他の外部オブジェクトと同様に、コマンドオブジェクトの内部動作を確認せず、Execute または Undo を呼び出すだけです。コンストラクターを呼び出すときに動作するのに必要なデータをコマンドオブジェクトに渡します。
Unity アプリケーションでデザインパターンを使用する方法や SOLID の原則に関するその他のヒントについては、無料の電子書籍「ゲームプログラミングパターンでコードをレベルアップ」を参照してください。
Unity の高度な技術電子書籍や記事はすべてベストプラクティスハブにあります。電子書籍は、ドキュメントの「高度なベストプラクティス」ページにも掲載されています。