
今回から戦闘、BattleSystemを作っていきます。
基本的な形は、しまづさんの、
youtube.com
がベースになっています。
大きな違いは、ソロ戦闘ではなくパーティ戦なので、Player側もEnemy側も複数いるため、かなり処理が複雑になっている事と、各Manager・ControllerとUniRXを使って連携しているところでしょうか。
いわゆる、Model-View-Presenterパターンに似た構造を取っています。(厳密にはちょっと違うけれど・・・)
また、しまづさんのyoutubeは、作成時期のせいだと思いますが、Stateパターンの組み方をenumをSwitch分岐していますが、
今回作るゲームについては、最近、しまづさんに教わった、Battle SystemをOwnerにしたState単位に分けています。
かなり色々と変えていますが、基礎の考え方は、しまづさんのYouTubeが下敷きになっている事には変わりありません。
処理の流れは、ざっくりこんなイメージになっています。

まず、PlayerController.csから戦闘開始のSignalを受信したGameManager.csは、EnemyManager.csに敵パーティを生成させて受け取ります。
次に、
PlayerController.csのPlayerUnitと、生成された敵パーティをBattleSystem.csに渡して、戦闘開始する流れです。
ちょっとまどろっこしいですが、この様に、各Manager・Controllerは極力、互いに直接接続させず、GameManager.csに処理を集中させる作り方にしています。
さて、今回ですが、結局、まだ戦闘準備になります。
というのも、前まででCharacter.csは作成したものの、実際にそれをどのように運用するかを決めていないので、今回はその辺りになります。
まず、Playerですが、これはシンプルにPlayerController.csに持たせようと思います。
using System; using UnityEngine; using Cysharp.Threading.Tasks; using R3; public class PlayerController : MonoBehaviour { [SerializeField] public Character[] players = new Character[2]; [SerializeField] public PlayerUnit[] playerUnits = new PlayerUnit[2]; ・ ・ ・
次に、PlayerのCharacter.csが搭載されるPlayerUnit.csですが、
using System.Collections.Generic; using UnityEngine; public class PlayerUnit : CharacterUnit { public List<Item> bkpack_items{ get => Character.bkpack_items; set => Character.bkpack_items = value; } [SerializeField] TMPro.TextMeshProUGUI nameText; [SerializeField] TMPro.TextMeshProUGUI hpText; [SerializeField] TMPro.TextMeshProUGUI mpText; [SerializeField] TMPro.TextMeshProUGUI StatusText; ・ ・ ・
この様に、UIの設定が格納されていますので、いっそ、UIに搭載してしまおうと思います。

どうも、僕が作ると面白味のないデザインになってしまうので、UIのデザインはお好みで🤣
次に、Enemy側の所在ですがEnemyManager.csを作って、そこに管理を任せてしまいます。
EnemyManager.csは、EnemyのListを持ち、そこから敵パーティを生成します。
using UnityEngine; public class EnemyManager : MonoBehaviour { //EnemyのListを持つ [SerializeField] public Character[] enemys; public Character[] CreateEnemysParty(int floor)//現在はfloorは未使用。 { int enemyCount = Random.Range(1, 4);//出現するEnemyの数を決める。 Character[] enemysParty = new Character[enemyCount]; for (int i = 0; i < enemyCount; i++) { int enemyIndex = Random.Range(0, enemys.Length); enemysParty[i] = enemys[enemyIndex].Clone(1);//将来的にはここでEnemyのレベルも変動させる。 } return enemysParty; } }
いずれはダンジョンの階層によって、Enemyの数や種類、Enemyのレベルも調整させようと考えてはいますが、現時点ではシンプルな作りです。
戦闘が開始されると、GameManager.csがEnemyManager.csを使って敵パーティ(enemysParty)を生成し、BattleSystem.csに受け渡します。
Enemyも搭載されるべきCharacterUnit.cs(EnemyUnit.cs)が必要ですが、それは戦闘でしか使わないので、BattleSystem.csに持たせることにします。
メインのBattleSystem.csですが、正直、まだまだ開発途中なので、大幅に変わる可能性大です。😅
using UnityEngine; using R3; using System.Collections.Generic; using System; public class BattleSystem : MonoBehaviour { public BatteleStateBase CurrentState; public BatteleState_Idle Idle; public BattleState_Setup Setup; public BattleState_ActionSelection ActionSelection; public BattleState_EnemyActionSelection EnemyActionSelection; public BattleState_ActionTurn ActionTurn; public BattleState_TurnEnd BattleTurnEnd; [SerializeField] public GameObject EnemyImages;//Enemyの絵が表示される土台 [SerializeField] public MenuController battle_Command_menu; public PlayerUnit[] playerUnits;//戦闘中のPlayerUnitへの参照を保存する。 public GameObject[] enemyUnits;//EnemyManagerで作成された敵パーティを格納 [SerializeField] public GameObject plafab;//EnemyUnitのPrefab private void Awake() { Idle = new BatteleState_Idle(this); Setup = new BattleState_Setup(this); ActionSelection = new BattleState_ActionSelection(this); EnemyActionSelection = new BattleState_EnemyActionSelection(this); ActionTurn = new BattleState_ActionTurn(this); BattleTurnEnd = new BattleState_TurnEnd(this); CurrentState = Idle; } void Update() { CurrentState.OnUpdate(); } public void ChangeState(BatteleStateBase state) { if(CurrentState != null) { CurrentState.OnExit(); } CurrentState = state; CurrentState.OnEnter(); } public void Battle_Start(Character[] enemys,PlayerUnit[] get_playerUnits) { enemyUnits = new GameObject[enemys.Length]; playerUnits = new PlayerUnit[get_playerUnits.Length]; playerUnits = get_playerUnits; //同じ種類の敵がいた場合、名前の後ろにA,B,C...をつける { Dictionary<string, int> nameCount = new Dictionary<string, int>(); foreach (var enemy in enemys) { string enemyName = enemy.CharacterName; if (!nameCount.ContainsKey(enemyName)) { nameCount[enemyName] = 0; } char plus = (char)('A' + nameCount[enemyName]); enemy.CharacterName = $"{enemyName}{" "}{plus}"; nameCount[enemyName]++; } } //エネミーリストをBattlerNameBaseでソート System.Array.Sort(enemys, (a, b) => a.CharacterName.CompareTo(b.CharacterName)); for (int i = 0; i < enemys.Length; i++) { enemyUnits[i] = Instantiate(plafab, new Vector3(0, 0, 0), Quaternion.identity); enemyUnits[i].transform.SetParent(EnemyImages.transform); enemyUnits[i].GetComponent<EnemyUnit>().Setup(enemys[i], i + 1, this); } ChangeState(Setup); } }
細かい説明はおいおいしていくとして、今日のところはBattle_Start関数について
GameManager.csから、このBattle_Start関数が呼ばれ、同時にEnemyManager.csで生成されたenemysPartyと、PlayerContorollerが管理しているPlayerUnitへの参照がBattleSystem.csに受け渡されます。
Battle_Start関数は最初に受け取ったenemysPartyの敵の名前を確認して、敵の識別用に名前の後ろにA,B,C・・・を足しています。
まず、敵の名前と数という辞書型変数を作成して、
Dictionary<string, int> nameCount = new Dictionary<string, int>();
次に、enemysPartyに登録されているCharacter.csを一つずつ取り出し、名前を確認して、
辞書型変数にすでにその名前が登録されていれば、該当の名前の数を増やしています。
foreach (var enemy in enemys) { string enemyName = enemy.CharacterName; if (!nameCount.ContainsKey(enemyName))//辞書型変数に名前が含まれているか確認。 { nameCount[enemyName] = 0;//なければ、新たに、その名前と0を登録する。 }
そして、辞書型変数の数を参照して名前の後ろにA,B,C・・・を足しています。
これはchar型の足し算という方法で、
ざっくり言えば、char型の「A」という文字に1を足してやると「B」になる。という、一瞬、頭が😵💫するような法則に則っています。
char plus = (char)('A' + nameCount[enemyName]); enemy.CharacterName = $"{enemyName}{" "}{plus}"; nameCount[enemyName]++; }
敵パーティの名前の整理が終わったけれど、enemysPartyはEnemyManager.csがランダムにEnemyListから選んで作成しているので、名前ごとに並んでいるわけではないです。
今のところは、このままでも支障はないけれど、後々、魔法とかで範囲攻撃を導入する際に、敵が名前ごとに並んでいる方が都合が良いかもしれないので、並び替えることにします。
System.Array.Sort(enemys, (a, b) => a.CharacterName.CompareTo(b.CharacterName));
そして、最後にEnemyUnit.csに搭載してやり、これでようやく戦闘準備が完了です。
for (int i = 0; i < enemys.Length; i++) { enemyUnits[i] = Instantiate(plafab, new Vector3(0, 0, 0), Quaternion.identity); enemyUnits[i].transform.SetParent(EnemyImages.transform); enemyUnits[i].GetComponent<EnemyUnit>().Setup(enemys[i], i + 1, this); }
次から、各Stateの説明に行けるかなぁ・・・