この記事はターン制の戦術SRPGのプログラミング開発を解説する講座の第11回目です。
今回の記事はチェスや将棋などを含めたボードゲームやオリジナルの戦術SLGストラテジーを組む際にも役立つ内容です。
前回の記事でDoTweenのインストール方法から始まり、遅延処理の実行やアニメーションの作成方法を習得しました。Tween.Toなどを用いればHPゲージを指定した秒数で減少させたりもできました。
前回の記事:
ここまでの実装で、自分のターン中にキャラクターを操作し移動から攻撃まで一通り行えるようになりました。あとは敵ターンの処理を実装すればゲームの流れが出来上がります。
そこで、今回の記事ではSRPGにおける敵ターンAIストラテジーの行動・移動・戦闘処理を開発していきます。
敵AIがプレイヤーに攻撃できるかどうかや移動後に攻撃できない場合はどうするかといった例外処理も含めた実践的なAIストラテジーを開発していきます。
敵AIの行動ストラテジー処理の開発方針について
条件に沿って敵キャラクターの1人を行動させるメソッドを作成します。広義におけるAIの実装となります。
今回は以下のような仕様でシンプルに制作します。
- 敵フラグの立っているキャラクター1人を対象にする
- その敵の移動範囲を取得する
- 移動範囲内の位置1つごとに攻撃範囲を取得する
- 攻撃範囲内の位置1つごとに攻撃できるキャラクターが居るかチェックし、居るなら攻撃してターン終了
- 全キャラクターで探索を行っても攻撃相手が居ないなら何もせずターン終了
移動範囲や攻撃範囲の検索はプレイヤー操作の時と同じものを使用できます。
GameManager.cs内 新規メソッド
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
/// <summary> /// (敵のターン開始時に呼出) /// 敵キャラクターのうちいずれか一体を行動させてターンを終了する /// </summary> private void EnemyCommand () { // 生存中の敵キャラクターのリストを作成する var enemyCharas = new List<Character> (); // 敵キャラクターリスト foreach (Character charaData in charactersManager.characters) {// 全生存キャラクターから敵フラグの立っているキャラクターをリストに追加 if (charaData.isEnemy) enemyCharas.Add (charaData); } // 攻撃可能な敵キャラクター1体を見つけるまで処理 foreach (Character enemyData in enemyCharas) { // 移動可能な場所リストを取得する reachableBlocks = mapManager.SearchReachableBlocks (enemyData.xPos, enemyData.zPos); // それぞれの移動可能な場所ごとの処理 foreach (MapBlock block in reachableBlocks) { // 攻撃可能な場所リストを取得する attackableBlocks = mapManager.SearchAttackableBlocks (block.xPos, block.zPos); // それぞれの攻撃可能な場所ごとの処理 foreach (MapBlock attackBlock in attackableBlocks) { // 攻撃できる相手キャラクター(プレイヤー側のキャラクター)を探す Character targetChara = charactersManager.GetCharacterDataByPos (attackBlock.xPos, attackBlock.zPos); if (targetChara != null && !targetChara.isEnemy) {// 相手キャラクターが存在する // 敵キャラクター移動処理 enemyData.MovePosition (block.xPos, block.zPos); // 敵キャラクター攻撃処理 // (移動後のタイミングで攻撃開始するよう遅延実行) DOVirtual.DelayedCall ( 1.0f, // 遅延時間(秒) () => {// 遅延実行する内容 CharaAttack (enemyData, targetChara); } ); // 移動場所・攻撃場所リストをクリアする reachableBlocks.Clear (); attackableBlocks.Clear (); // 進行モードを進める(行動結果表示へ) ChangePhase (Phase.EnemyTurn_Result); return; } } } } // (攻撃可能な相手が見つからなかった場合何もせずターン終了) // 移動場所・攻撃場所リストをクリアする reachableBlocks.Clear (); attackableBlocks.Clear (); // 進行モードを進める(自分のターンへ) ChangePhase (Phase.MyTurn_Start); } |
- 遅延実行を用いて移動の後に攻撃を行うようにしています。
- CharaAttackメソッド内にて攻撃後のターン切り替え処理を実装済みなので、攻撃が完了したら自動的にプレイヤーのターンが始まります。
このメソッドを「敵のターンに切り替わった」タイミングで1度呼び出すようにします。今回はその方法として以前に用意したChangePhaseメソッドを拡張します。
GameManager.cs内 ChangePhase
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
/// <summary> /// ターン進行モードを変更する /// </summary> /// <param name="newPhase">変更先モード</param> private void ChangePhase (Phase newPhase) { // モード変更を保存 nowPhase = newPhase; // 特定のモードに切り替わったタイミングで行う処理 switch (nowPhase) { // 敵のターン:開始時 case Phase.EnemyTurn_Start: // 敵の行動処理を開始する EnemyCommand (); break; } } |
この拡張によって「プレイヤーキャラの攻撃後」「プレイヤーキャラの待機後」両方できちんと先ほどのメソッドが呼ばれるようになります。
それではテストプレイを行い、自分のターン→敵のターン→自分のターン… と相互に切り替わっていく所まで確認していきましょう。
ターン切り替え時に演出を追加する
シミュレーションRPGとして演出面を強化する為、ターン切り替えのタイミングで画面上に専用のロゴを表示してみましょう。
まずはプレイヤーのターン切り替わった事を示すロゴから制作します。
Canvas内に画像UIとしてUI>Imageで追加しましょう。名前はLogo_PlayerTurnとしました。(任意でロゴ系の親になるオブジェクトLogosを追加しておいても良いでしょう。)
Imageコンポーネントの[Source Image]には画像素材PlayerTurnを指定します。
素材を指定した後にコンポーネント下部にある[Set Native Size]ボタンを押すと、画像の大きさに合わせてRectTransformのWidth・Heightを自動調整してくれます。
また、このUIは画像の透明化・非透明化によって表示と非表示を切り替えていくのでオブジェクトは常にアクティブで存在し続ける事になります。ブロックへのタップがこのUIにブロックされる事を防ぐ為に、[Raycast Target]の設定をオフにしておきましょう(Raycast TargetをOffにしておけばUIの当たり判定が実行されない)。
初期状態は非表示とする為、[Color]パラメータのA(Alpha…不透明度)の値を0にしておきます。
画面中央に配置するのでアンカーや座標の変更は不要です。
これでプレイヤーのターン開始表示のUIが作成できたので、オブジェクトをコピー&ペーストして敵ターン開始表示のUI(Logo_EnemyTurn)も用意しておきます。画像素材はEnemyTurnを使用します。
大体このようになっていればOKです。
ロゴ画像の表示アニメーションを作成・呼出する
以上の画像UIをターン切り替え時に表示していくようにしましょう。
まとめ
自分のターン→敵のターン→自分のターン… の繰り返しの流れが一通り出来上がりました。
敵のAI部分であるTargetFinderクラスを拡張していけばより賢い行動をとってくれるようになるので興味のある方はぜひ試してみてください。
(例:ActionPlan型に戦闘結果の予測まで記録するようにし、最も与ダメージの大きい行動プランを採用するように変更する等…)
ゲームの完成が近づいてきました。次の記事ではこのゲームをより遊びやすく・より面白く改良していきます。
次の記事:
コメント