現場レベルのゲーム制作が、すべてここで学べます。
この講座は3Dダンジョン探索型RPGの作り方について説明しています。今回はその第8回目になります。
前回はカウントタイムシステムを実装し、速度から行動値を計算してキャラクターにターンをまわす処理まで実装しました。
前回の記事:

今回はバトルに用いるコマンドUIの実装やアニメーション作成、アクターキャラクターの通常攻撃によって敵のHPを減少させる処理まで実装していきます。
バトルコマンド・対象選択システムをUnity C#で実装
このゲームではアクターにターンがまわってくると戦闘画面右上にコマンドボタンが並んで表示されます。
このうち「ATTACK」ボタンを押下すると通常攻撃スキルを発動できます。
そして攻撃対象となるエネミー1体を選択するとスキルの効果処理が行われ、そのエネミーのHPを減らすという処理が行われます。HPが0になったエネミーは戦闘不能となり、戦闘から退場します。
この章ではここまでの実装を目標にしていきます。
アクター用バトルコマンドUI処理クラス作成
戦闘画面右上に表示するコマンドボタン群(バトルコマンドと呼称)の1つ1つにアタッチする用のクラスとしてBattleCommandUIを作成します。BattleManagerから制御されます。
BattleCommandUI.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 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
using UnityEngine; using UnityEngine.UI; using UnityEngine.EventSystems; using DG.Tweening; /// <summary> /// (GameMain) BattleManager配下 /// アクター用バトルコマンドボタンUI処理クラス /// </summary> public class BattleCommandUI : MonoBehaviour, IPointerClickHandler { // オブジェクト・コンポーネント参照 public BattleManager battleManager { get; private set; } // 点滅アニメーションパラメータ public Color animColor_A = new Color (1.0f, 1.0f, 1.0f, 0.6f); public Color animColor_B = new Color (0.9f, 0.9f, 0.9f, 0.4f); // コマンド種類定義 public enum BattleCommandType { NormalAttack, // 通常攻撃 Skills, // スキル一覧 Items, // アイテム一覧 Guard, // ガード Run, // 逃走 } // このボタンUIが対応するコマンド種類 public BattleCommandType battleCommandType; // 初期化関数(BattleManager.csから呼出) public void Init (BattleManager _battleManager) { // 参照取得 battleManager = _battleManager; // ボタン点滅アニメーション Image image = GetComponent<Image> (); image.color = animColor_A; // 初期表示色 // 1.0秒間隔で色を変更するTween image.DOColor (animColor_B, 1.0f) .SetEase (Ease.InSine) .SetLoops (-1, LoopType.Yoyo); } /// <summary> /// クリック入力時に実行 /// </summary> public void OnPointerClick (PointerEventData eventData) { // コマンド種類ごとに次の処理を呼び出し switch (battleCommandType) { case BattleCommandType.NormalAttack: // 通常攻撃 battleManager.ActorCommand_NormalAttack (); break; case BattleCommandType.Skills: // スキル一覧 break; case BattleCommandType.Items: // アイテム一覧 break; case BattleCommandType.Guard: // ガード break; case BattleCommandType.Run: // 逃走 break; } } } |
それぞれのバトルコマンドUIに対して、そのUIがどのコマンドと紐づいているかを指定できるようにし、その情報に基づいたメソッドを呼び出せるようにします。
OnPointerClickメソッドは、そのUIオブジェクトがマウスでクリックされる(またはスマートフォンでタップされる)時に呼び出される処理です。この機能を利用する場合はクラス名を宣言する箇所でIPointerClickHandlerの継承が必要になります(10行目)。
ちなみにここでいうクリックとは「UIオブジェクトでマウスを左ボタンを押し込み、そして離す」操作をすることでクリックと判定されます。今回は使いませんが、OnPointerDownというメソッドの場合はマウスの左ボタンを押し込まれたタイミングで呼び出されます。(こちらはIPointerDownHandlerが必要)
またUnityのUIにおいてマウスクリックorタップの判定をとりたいだけの場合はButtonというコンポーネントも利用できます。これはのちほど解説します。
BattleManager.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 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
using System.Collections.Generic; using UnityEngine; using DG.Tweening; /// <summary> /// (Battle) /// バトルマネージャー /// </summary> public class BattleManager : MonoBehaviour { // オブジェクト・コンポーネント参照 public EnemyManager enemyManager; // エネミーマネージャ public CountTimeManager countTimeManager; // カウントタイムシステムマネージャ // オブジェクト・コンポーネント参照 // アクターUI [SerializeField] private List<ActorUI> actorUI_Vanguard = null; // 前衛アクターUI参照 [SerializeField] private List<ActorUI> actorUI_Rearguard = null; // 後衛アクターUI参照 private List<ActorUI> actorUIs; // 前衛・後衛を足したアクターUIリスト // 戦闘に参加している全キャラクターのリスト public List<CharacterData> battleCharacterDatas { get; private set; } // バトルコマンド [SerializeField] private RectTransform battleCommandUIsTrs = null; // UIエリアのTransform [SerializeField] private List<BattleCommandUI> battleCommandUIs = null; // コマンドUIリスト [SerializeField] private SkillData skillData_ActorNormalAttack = null; // アクター通常攻撃のスキルデータ // テスト用・戦闘に参加するエネミーのデータ public List<EnemyData> debugBattleEnemyDatas; // ターン処理関連 // ターン処理中のキャラクター public CharacterData turnCharacter { get; private set; } // Start void Start () { // 変数初期化 battleCharacterDatas = new List<CharacterData> (); // 管理下コンポーネント初期化 countTimeManager.Init (this); enemyManager.Init (this); foreach (var battleCommandUI in battleCommandUIs) battleCommandUI.Init (this); // 各バトルコマンドUI初期化 // アクターUI初期化 // 前衛リストと後衛リストをまとめたリストを作成 actorUIs = new List<ActorUI> (actorUI_Vanguard); actorUIs.AddRange (actorUI_Rearguard); // リストを加算 // 前衛 for (int i = 0; i < actorUI_Vanguard.Count; i++) { if (i < Data.instance.formationDatas_Vanguard.Count) { // アクターUI初期化 actorUI_Vanguard[i].Init (this, Data.instance.formationDatas_Vanguard[i]); // 戦闘参加キャラクターリストに追加 battleCharacterDatas.Add (Data.instance.formationDatas_Vanguard[i]); // カウントタイムシステムに追加 countTimeManager.CreateCountTimeUI (Data.instance.formationDatas_Vanguard[i]); } else actorUI_Vanguard[i].DisableActorUI (); } // 後衛 for (int i = 0; i < actorUI_Rearguard.Count; i++) { if (i < Data.instance.formationDatas_Rearguard.Count) { // アクターUI初期化 actorUI_Rearguard[i].Init (this, Data.instance.formationDatas_Rearguard[i]); // 戦闘参加キャラクターリストに追加 battleCharacterDatas.Add (Data.instance.formationDatas_Rearguard[i]); // カウントタイムシステムに追加 countTimeManager.CreateCountTimeUI (Data.instance.formationDatas_Rearguard[i]); } else actorUI_Rearguard[i].DisableActorUI (); } // デバッグ用エネミー出現処理 foreach (var enemyData in debugBattleEnemyDatas) {// 各エネミーインスタンスをLevel1で作成 enemyManager.CreateEnemyData (enemyData, 1); } // その他UI初期化 battleCommandUIsTrs.gameObject.SetActive (false); // バトルコマンド非表示 // 指定時間後にカウントタイムシステムを動作開始 DOVirtual.DelayedCall ( 0.5f, // 0.5秒後に以下の処理を実行 () => { // 現在のカウントを確認&並べ替え countTimeManager.CheckAllCountTime (); // いずれかのキャラクターにターンが周ってくるまでカウントの減算ループを開始 if (turnCharacter == null) countTimeManager.IncrementCountTime (); } ); } #region ターン処理関連 /// <summary> /// 指定されたキャラクターのターンを開始する /// </summary> public void ActiveCharacterTurn (CharacterData targetCharacterData) { turnCharacter = targetCharacterData; // 全体のカウントタイムUI表示更新 countTimeManager.RefreshAllCountTimeUIs (); // ターンキャラクターのカウントタイムUI強調表示 countTimeManager.SetCountTimeUIHilight (turnCharacter); // 戦闘不能の場合は処理しない if (turnCharacter.isKnockedOut) { TurnEnd (); return; } // キャラクター種類別のターン開始処理 if (turnCharacter is ActorData actorData) {// アクター Debug.Log ("アクターのターン開始処理"); } else if (turnCharacter is EnemyData enemyData) {// エネミー Debug.Log ("エネミーのターン開始処理"); } } /// <summary> /// アクターコマンドボタン選択時処理:通常攻撃 /// </summary> public void ActorCommand_NormalAttack () { // (通常攻撃を開始する処理) } /// <summary> /// ターン終了処理 /// </summary> public void TurnEnd () { // カウントタイム初期化 countTimeManager.CharacterCountTimeReset (turnCharacter); // 各種変数情報初期化 turnCharacter = null; // 次のターンへ countTimeManager.CheckAllCountTime (); // いずれかのキャラクターにターンが周ってくるまでカウントの減算ループを開始 if (turnCharacter == null) countTimeManager.IncrementCountTime (); } #endregion (以降省略) } |
バトルコマンドに関する処理、および通常攻撃コマンドが選択された時に呼び出されるメソッドを形だけ用意しておきます。
戦闘開始時は各バトルコマンドUIのオブジェクトを非アクティブにすることでボタンを非表示にしておきます。
バトルコマンドはあくまでアクターの行動選択用なので、アクターのターン開始時にのみ表示すれば良いためです。
プレハブへの設定など
まずはBattleCommandUIプレハブの設定をInspectorから確認します。
こちらは元々Buttonコンポーネントとして作成されているので、Imageコンポーネントの下にButtonコンポーネントも同時に付与されているはずです。

Buttonコンポーネントについて
(ここで説明するButtonコンポーネントについては説明のみですので、実際に操作する必要はありません。)
もし上記のBattleCommandUIクラスを使わずにBattleManagerクラスのActorCommand_NormalAttackメソッドを呼び出したい場合、
OnClick()パラメータの[+]をクリックし、Scene上のManagersオブジェクトに対する参照をセットします。
そしてBattleManagerクラスのActorCommand_NormalAttackを選択すると、そのUIをクリックした時にここで指定したメソッドを呼び出してくれるようになります。
(プレハブ編集画面ではシーン上のオブジェクトを参照にとれないので、一度プレハブ編集画面から出て、シーン上のBattleCommandUIオブジェクトに対してこの設定をする必要があります。勿論この場合プレハブ自体への設定は変更されません。)

ただし、このButtonコンポーネントは「クリックやタップを検知して処理を呼び出す」という最低限の機能しか持ち合わせていないので、もう少し複雑な動作やアニメーションを付与したい場合は自分でスクリプトを作成する必要があります。そこで作ったのが先ほどのBattleCommandUIクラスという訳です。
Buttonコンポーネントの削除とBattleCommandUIのアタッチ
それではBattleCommandUIプレハブの編集画面に戻り、Buttonコンポーネントタブ右上にある縦向きの[…]ボタンをクリックし、Remove Componentを選択します。これでコンポーネントの削除が可能です。

あとはBattleCommandUIをアタッチすればOKです。

プレハブ編集画面はここまでです。元のシーンに戻りましょう。
Managersオブジェクトの設定
ManagersオブジェクトのBattleManagerコンポーネントからも各種参照をセットします。
BattleCommandUIAreaに対する参照、および5つあるバトルコマンドUIへの参照です。

現時点でテストプレイをすると、戦闘開始時に画面右上のバトルコマンドが全て非表示になっていることを確認できます(バトルコマンドを再表示する処理はこのあと作成します)。

戦闘のステート進行管理システムを実装する
ここまでの実装でキャラクターにターンが回ってくるようになりました。次はそのターン中に行う「コマンドを選ぶ」「スキルを選ぶ」「相手を選ぶ」「行動する」といった一連の流れを管理する仕組みを実装していきます。
戦闘中のプレイヤーの行動は、例えば「コマンド選択画面で攻撃を選んだら、次に対象選択画面に移る」といったように状況に応じて次にできることが決まっています。このようにゲームの「現在の状況」を管理し、その状況に応じて処理を切り替える仕組みをステート管理(または状態管理)と呼びます。
ステート管理とは
ステート管理を導入することで複雑な処理の流れを整理し、コードをわかりやすく保つことができます。
例えば今回の戦闘システムでは以下のような「状況(ステート)」が考えられます。
-
Wait: 誰のターンでもなくゲームの進行を待っている状態
-
SelectingCommand: アクターのターンで、どのコマンド(攻撃・スキルなど)を実行するか選択している状態
-
SelectingTarget: コマンドやスキルを選んだ後、どの相手に使うか選択している状態。
-
Action: 実際にスキルなどの行動が実行されている状態。
このように戦闘の流れをいくつかのステートに分割し、「今どのステートにいるのか」をenum変数で管理します。そしてその値に応じて表示するUIを切り替えたり、プレイヤーの入力を受け付けたりする処理を変化させます。
ステート管理スクリプトの実装
BattleManagerクラスを拡張し、ステート管理の機能を付与しつつ通常攻撃のコマンドボタンを押下できる所までを実装します。
BattleManager.cs
まとめ

戦闘全体の進行管理を行うステート機能およびスキルの対象選択、効果発動までの処理を実装しました。
次回はエネミー側の行動処理を実装しつつ、一部の未実装となっている戦闘まわりのシステムを拡充していきます。
次の記事:

現場レベルのゲーム制作が、すべてここで学べます。






コメント