現場レベルのゲーム制作が、すべてここで学べます。
この講座は3Dダンジョン探索型RPGの作り方について説明しています。今回はその第9回目になります。
前回は戦闘のステート管理などを実装し、アクターの通常攻撃スキルを使用できるところまでを実装しました。
前回の記事:

今回はエネミーもスキルを発動してくるようにしてお互いに攻撃を行えるようにします。エネミーのターンも終了するようになるので「行動開始→スキル発動→ターン終了→次のキャラクターのターンへ」という戦闘のゲームループが完成します。
ほか、未実装となっている戦闘システムまわりの処理(複数ターゲット選択など)やオートバトルシステムも実装していきます。これらが完了すれば戦闘システムの開発は一区切りとなります。
ターン制複数バトルにおけるエネミーターン処理を実装する
エネミーにターンが周ってきた場合の処理を実装していきます。
アクターのターンと違うところは「コマンドや対象選択などをプレイヤーは行わない」「どのスキルを使用するかはEnemyData(のuseSkills)で指定されたものの中からランダムに決める」という点です。
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 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 |
using System.Collections.Generic; using UnityEngine; using DG.Tweening; /// <summary> /// (Battle) /// バトルマネージャー /// </summary> public class BattleManager : MonoBehaviour { (省略) // Start void Start () { (省略) } #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) {// アクター ActorTurnProcess (actorData); // アクターのターン開始処理 } else if (turnCharacter is EnemyData enemyData) {// エネミー // エネミーが使用するスキルを取得 selectingSkillData = enemyManager.GetRandomEnemySkill ((EnemyData)turnCharacter); // ステート自動進行 ChangeBattleState (BattleState.SelectingTarget); ChangeBattleState (BattleState.Action); } } /// <summary> /// 指定されたアクターのターン開始時処理 /// </summary> private void ActorTurnProcess (ActorData actorData) { // ステート進行 ChangeBattleState (BattleState.SelectingCommand); } /// <summary> /// 現在の戦闘進行ステートを変更し、表示中のUIに適用する /// </summary> public void ChangeBattleState (BattleState nextBattleState) { if (isBattleFinished) return; // ステート変更を適用 nowBattleState = nextBattleState; // 各ステートで専用表示されるUIの一律初期化 battleCommandUIsTrs.gameObject.SetActive (false); switch (nowBattleState) { case BattleState.Wait: // 待機状態 break; case BattleState.SelectingCommand: // (味方)コマンド選択中 // 選択スキル初期化 selectingSkillData = null; // 選択中の対象データを初期化 ClearSelectTargets (); // コマンドUI表示 battleCommandUIsTrs.gameObject.SetActive (true); break; case BattleState.SelectingSkill: // (味方)スキル選択中 break; case BattleState.SelectingItem: // (味方)アイテム選択中 break; case BattleState.SelectingTarget: // (味方)対象選択中 // 選択中の対象データを初期化 ClearSelectTargets (); // 初期選択キャラクター TargetType targetType = TargetType.Enemy_1; if (selectingSkillData != null) targetType = selectingSkillData.targetType; switch (targetType) { case TargetType.Enemy_1: // 敵1体 enemyManager.SelectDefaultEnemyUI (); break; case TargetType.Actor_Hate: // 味方1体(ヘイト基準で初期選択) SelectActorUIByHate (); break; } break; case BattleState.Action: // アクション実行中 StartActionProcess (); // 行動処理実行 break; } } /// <summary> /// アクターコマンドボタン選択時処理:通常攻撃 /// </summary> public void ActorCommand_NormalAttack () { // スキルデータをセット selectingSkillData = skillData_ActorNormalAttack; // ステート進行 ChangeBattleState (BattleState.SelectingTarget); } #endregion #region ターゲット選択関連 /// <summary> /// アクターUIタップ選択時処理 /// </summary> public void SelectActorUI (ActorUI actorUI, bool isOneTarget) { // 対象選択ステートでないなら終了 if (nowBattleState != BattleState.SelectingTarget) return; // スキルの対象がアクターでないなら終了 TargetType targetType = TargetType.Enemy_1; if (selectingSkillData != null) targetType = selectingSkillData.targetType; if (targetType == TargetType.Enemy_1 || targetType == TargetType.Enemy_All || targetType == TargetType.Enemy_Random) return; // 対象キャラが不在なら終了 if (actorUI == null || actorUI.actorData == null || actorUI.actorData.isKnockedOut) return; if (selectTargetActorUIs.Contains (actorUI)) {// 選択確定 // 行動開始 ChangeBattleState (BattleState.Action); } else {// 選択開始 if (selectTargetActorUIs.Count > 0 && targetType == TargetType.Actor_Self) return; // 単体選択タイプであった場合、それまでの選択対象をリセットする if (isOneTarget) { foreach (var charaUI in actorUI_Vanguard) charaUI.SetTargerSelectMode (false); foreach (var charaUI in actorUI_Rearguard) charaUI.SetTargerSelectMode (false); foreach (var charaUI in selectTargetEnemyUIs) charaUI.SetTargerSelectMode (false); selectTargetActorUIs.Clear (); } // 対象に追加 selectTargetActorUIs.Add (actorUI); // UIの対象選択時表示 actorUI.SetTargerSelectMode (true); } } /// <summary> /// エネミーUIタップ選択時処理 /// </summary> public void SelectEnemyUI (EnemyUI enemyUI, bool isOneTarget) { // 対象選択ステートでないなら終了 if (nowBattleState != BattleState.SelectingTarget) return; // スキルの対象がエネミーでないなら終了 TargetType targetType = TargetType.Enemy_1; if (selectingSkillData != null) targetType = selectingSkillData.targetType; if (targetType == TargetType.Actor_1 || targetType == TargetType.Actor_All || targetType == TargetType.Actor_Self || targetType == TargetType.Actor_Hate || targetType == TargetType.Actor_Random_Hate) return; // 対象キャラが不在なら終了 if (enemyUI == null || enemyUI.enemyData == null || enemyUI.enemyData.isKnockedOut) return; if (selectTargetEnemyUIs.Contains (enemyUI)) {// 選択確定 // 行動開始 ChangeBattleState (BattleState.Action); } else {// 選択開始 // 単体選択タイプであった場合、それまでの選択対象をリセットする if (isOneTarget) { foreach (var charaUI in actorUI_Vanguard) charaUI.SetTargerSelectMode (false); foreach (var charaUI in actorUI_Rearguard) charaUI.SetTargerSelectMode (false); foreach (var charaUI in selectTargetEnemyUIs) charaUI.SetTargerSelectMode (false); selectTargetEnemyUIs.Clear (); } // 対象に追加 selectTargetEnemyUIs.Add (enemyUI); // UIの対象選択時表示 enemyUI.SetTargerSelectMode (true); } } /// <summary> /// 各対象選択の情報と表示を初期化する /// </summary> private void ClearSelectTargets () { // 選択中の対象データを初期化 foreach (var actorUI in selectTargetActorUIs) actorUI.SetTargerSelectMode (false); foreach (var enemyUI in selectTargetEnemyUIs) enemyUI.SetTargerSelectMode (false); selectTargetActorUIs.Clear (); selectTargetEnemyUIs.Clear (); } /// <summary> /// ヘイト計算を基にしてアクターUI1体を選択する /// </summary> public void SelectActorUIByHate () { List<ActorUI> actorUIs = GetActorListByHate (); // 対象アクターUIリストからランダムな1体を選択 ActorUI targetActorUI = actorUIs[Random.Range (0, actorUIs.Count)]; SelectActorUI (targetActorUI, true); } /// <summary> /// ヘイト計算用のアクターUIリストを作成して返す /// </summary> public List<ActorUI> GetActorListByHate () { // 対象になりうるアクターUIリスト作成 List<ActorUI> actorUIs = new List<ActorUI> (); // 前衛キャラをリストに追加 foreach (var actorUI in actorUI_Vanguard) { if (actorUI.actorData == null || actorUI.actorData.isKnockedOut) continue; // (前衛は同じキャラがリストに複数回追加される事で対象に選ばれやすくする) actorUIs.Add (actorUI); actorUIs.Add (actorUI); actorUIs.Add (actorUI); } // 後衛キャラをリストに追加 foreach (var actorUI in actorUI_Rearguard) { if (actorUI.actorData == null || actorUI.actorData.isKnockedOut) continue; actorUIs.Add (actorUI); } return actorUIs; } #endregion #region アクション処理 (省略) #endregion #region キャラクターステータス変動処理 (省略) #endregion #region 各種Get/Set系 (省略) #endregion } |
ActorUIがクリックされた時に呼び出される処理を、EnemyUIの時と同様に作成しておきます。
エネミーの通常攻撃スキルの対象設定はTargetType.Actor_Hate(ヘイト値基準で味方1体をランダムに選択)となっているのでこの場合の対象自動選択機能を実装しています。
このゲームにおけるヘイト計算は単純に「前衛は後衛より3倍ターゲットになりやすい」というものになっています。
条件式を追加すれば「最もHPの低いアクターを優先的に狙う」のようにすることも可能です。
この状態でテストプレイすれば、エネミーにターンが周ってきたときにそのエネミーが行動し、一撃でアクターを倒すのを確認できます。

ダメージポップアップ・総合ダメージ表示をUnityで実装
いまはダメージ量はConsoleのログにしか表示されないため、分かりやすくするようにダメージ発生時には画面上にポップアップ出現するように演出を追加します。また、1度の攻撃で複数体・複数回数ダメージを与えた時のために総合ダメージ量も別で表示するようにします。
Canvas UIの子オブジェクトにダメージ表示UIを構築する
まずはダメージを表示するための場所をcanvas UIの子オブジェクトに作っていきます。
(GameObject)ダメージポップアップの親オブジェクト_DamagePopups
ダメージポップアップのUIオブジェクトを出現させるときの親にする空オブジェクトとしてDamagePopupsを作成します。Inspectorからの設定は不要です。

(TextMeshPro – DamagePopups)ダメージポップアップUI_DamagePopup
DamagePopups以下にTextMeshProオブジェクトとしてDamagePopupを作成します。ダメージの数量を表示するオブジェクトになります。
- RectTransform:(Width, Height) = (100, 100)に
- TextMeshPro – Text:空欄もしくは任意の数字(確認用)
- TextMeshPro – Font Asset:Assets/Fonts/MPLUS1p-Regular SDF
- TextMeshPro – Material Preset:BlackShadow
- TextMeshPro – Font Size:58
- TextMeshPro – Spacing Options:Lineを-50
- TextMeshPro – Alignment:中央揃え
- TextMeshPro – Text Wrapping Mode:No Wrap
Text Wrapping ModeをNoWarpにすることで、長い文章などを表示させても自動的に改行されなくなります。

このオブジェクトは作成できたらプレハブ化し、シーン上からは削除しておきます。

(TextMeshPro)総合ダメージ量UI_TotalDamageText
1攻撃あたりの総合ダメージ量を表示するTextMeshProオブジェクト、TotalDamageTextをCanvasの子オブジェクトとして作成します。
- RectTransform:Positionは(-320, 169)、サイズは(160, 60)
- TextMeshPro – Text:空欄もしくは任意の文章(確認用)
- TextMeshPro – Font Asset:Assets/Fonts/NotoSans_BlackShadow
- TextMeshPro – Font Size:38
- TextMeshPro – Spacing Options:Lineを-30
- TextMeshPro – Text Wrapping Mode:No Wrap
後述しますが、TextMeshProのRichText機能を使うことで1つのTextMeshProオブジェクト内でも「文章の特定の範囲内だけサイズや色を変更する」ということが可能です。後ほどC#スクリプトからこの性質をダメージ表示処理に活用します。

ダメージポップアップ・総合ダメージ量表示スクリプト
まずはダメージポップアップの表示を制御するスクリプトとしてDamagePopupSpawnerクラスを作成します。
DamagePopupSpawner.cs (新規)
まとめ

今回はエネミーの行動や複数ターゲット設定、状態異常の処理やオートバトル設定など、戦闘に関する様々なシステムの実装を進めました。
次の章からは一度戦闘システムからは離れて、アクターの編成や強化などを行えるタウン画面の実装に移っていきます。
次の記事:

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






コメント