Unity チュートリアルのタワーディフェンステンプレートを触ってみる(47)では Prefab から追加した GameManager の解説を行った。は GameManager に合わせて追加した LevelManager の解説を行っていく。
1.タワーディフェンステンプレートのステージの設定編 – LevelManager.cs
LevelManager.cs について稚拙ながら解説
「LevelManager.cs」は「Assets/Scripts/TowerDefense/Level/LevelManager.cs」の指しておりスクリプトについては以下の通り。内容としてはゲームのレベル管理を行っている。
[cce_csharp]using System; using Core.Economy; using Core.Health; using Core.Utilities; using TowerDefense.Economy; using TowerDefense.Towers.Data; using UnityEngine; namespace TowerDefense.Level { /// <summary> /// The level manager - handles the level states and tracks the player's currency /// </summary> [RequireComponent(typeof(WaveManager))] public class LevelManager : Singleton<LevelManager> { /// <summary> /// The configured level intro. If this is null the LevelManager will fall through to the gameplay state (i.e. SpawningEnemies) /// </summary> public LevelIntro intro; /// <summary> /// The tower library for this level /// </summary> public TowerLibrary towerLibrary; /// <summary> /// The currency that the player starts with /// </summary> public int startingCurrency; /// <summary> /// The controller for gaining currency /// </summary> public CurrencyGainer currencyGainer; /// <summary> /// Configuration for if the player gains currency even in pre-build phase /// </summary> [Header("Setting this will allow currency gain during the Intro and Pre-Build phase")] public bool alwaysGainCurrency; /// <summary> /// The home bases that the player must defend /// </summary> public PlayerHomeBase[] homeBases; public Collider[] environmentColliders; /// <summary> /// The attached wave manager /// </summary> public WaveManager waveManager { get; protected set; } /// <summary> /// Number of enemies currently in the level /// </summary> public int numberOfEnemies { get; protected set; } /// <summary> /// The current state of the level /// </summary> public LevelState levelState { get; protected set; } /// <summary> /// The currency controller /// </summary> public Currency currency { get; protected set; } /// <summary> /// Number of home bases left /// </summary> public int numberOfHomeBasesLeft { get; protected set; } /// <summary> /// Starting number of home bases /// </summary> public int numberOfHomeBases { get; protected set; } /// <summary> /// An accessor for the home bases /// </summary> public PlayerHomeBase[] playerHomeBases { get { return homeBases; } } /// <summary> /// If the game is over /// </summary> public bool isGameOver { get { return (levelState == LevelState.Win) || (levelState == LevelState.Lose); } } /// <summary> /// Fired when all the waves are done and there are no more enemies left /// </summary> public event Action levelCompleted; /// <summary> /// Fired when all of the home bases are destroyed /// </summary> public event Action levelFailed; /// <summary> /// Fired when the level state is changed - first parameter is the old state, second parameter is the new state /// </summary> public event Action<LevelState, LevelState> levelStateChanged; /// <summary> /// Fired when the number of enemies has changed /// </summary> public event Action<int> numberOfEnemiesChanged; /// <summary> /// Event for home base being destroyed /// </summary> public event Action homeBaseDestroyed; /// <summary> /// Increments the number of enemies. Called on Agent spawn /// </summary> public virtual void IncrementNumberOfEnemies() { numberOfEnemies++; SafelyCallNumberOfEnemiesChanged(); } /// <summary> /// Returns the sum of all HomeBases' health /// </summary> public float GetAllHomeBasesHealth() { float health = 0.0f; foreach (PlayerHomeBase homebase in homeBases) { health += homebase.configuration.currentHealth; } return health; } /// <summary> /// Decrements the number of enemies. Called on Agent death /// </summary> public virtual void DecrementNumberOfEnemies() { numberOfEnemies--; SafelyCallNumberOfEnemiesChanged(); if (numberOfEnemies < 0) { Debug.LogError("[LEVEL] There should never be a negative number of enemies. Something broke!"); numberOfEnemies = 0; } if (numberOfEnemies == 0 && levelState == LevelState.AllEnemiesSpawned) { ChangeLevelState(LevelState.Win); } } /// <summary> /// Completes building phase, setting state to spawn enemies /// </summary> public virtual void BuildingCompleted() { ChangeLevelState(LevelState.SpawningEnemies); } /// <summary> /// Caches the attached wave manager and subscribes to the spawning completed event /// Sets the level state to intro and ensures that the number of enemies is set to 0 /// </summary> protected override void Awake() { base.Awake(); waveManager = GetComponent<WaveManager>(); waveManager.spawningCompleted += OnSpawningCompleted; // Does not use the change state function as we don't need to broadcast the event for this default value levelState = LevelState.Intro; numberOfEnemies = 0; // Ensure currency change listener is assigned currency = new Currency(startingCurrency); currencyGainer.Initialize(currency); // If there's an intro use it, otherwise fall through to gameplay if (intro != null) { intro.introCompleted += IntroCompleted; } else { IntroCompleted(); } // Iterate through home bases and subscribe numberOfHomeBases = homeBases.Length; numberOfHomeBasesLeft = numberOfHomeBases; for (int i = 0; i < numberOfHomeBases; i++) { homeBases[i].died += OnHomeBaseDestroyed; } } /// <summary> /// Updates the currency gain controller /// </summary> protected virtual void Update() { if (alwaysGainCurrency || (!alwaysGainCurrency && levelState != LevelState.Building && levelState != LevelState.Intro)) { currencyGainer.Tick(Time.deltaTime); } } /// <summary> /// Unsubscribes from events /// </summary> protected override void OnDestroy() { base.OnDestroy(); if (waveManager != null) { waveManager.spawningCompleted -= OnSpawningCompleted; } if (intro != null) { intro.introCompleted -= IntroCompleted; } // Iterate through home bases and unsubscribe for (int i = 0; i < numberOfHomeBases; i++) { homeBases[i].died -= OnHomeBaseDestroyed; } } /// <summary> /// Fired when Intro is completed or immediately, if no intro is specified /// </summary> protected virtual void IntroCompleted() { ChangeLevelState(LevelState.Building); } /// <summary> /// Fired when the WaveManager has finished spawning enemies /// </summary> protected virtual void OnSpawningCompleted() { ChangeLevelState(LevelState.AllEnemiesSpawned); } /// <summary> /// Changes the state and broadcasts the event /// </summary> /// <param name="newState">The new state to transitioned to</param> protected virtual void ChangeLevelState(LevelState newState) { // If the state hasn't changed then return if (levelState == newState) { return; } LevelState oldState = levelState; levelState = newState; if (levelStateChanged != null) { levelStateChanged(oldState, newState); } switch (newState) { case LevelState.SpawningEnemies: waveManager.StartWaves(); break; case LevelState.AllEnemiesSpawned: // Win immediately if all enemies are already dead if (numberOfEnemies == 0) { ChangeLevelState(LevelState.Win); } break; case LevelState.Lose: SafelyCallLevelFailed(); break; case LevelState.Win: SafelyCallLevelCompleted(); break; } } /// <summary> /// Fired when a home base is destroyed /// </summary> protected virtual void OnHomeBaseDestroyed(DamageableBehaviour homeBase) { // Decrement the number of home bases numberOfHomeBasesLeft--; // Call the destroyed event if (homeBaseDestroyed != null) { homeBaseDestroyed(); } // If there are no home bases left and the level is not over then set the level to lost if ((numberOfHomeBasesLeft == 0) && !isGameOver) { ChangeLevelState(LevelState.Lose); } } /// <summary> /// Calls the <see cref="levelCompleted"/> event /// </summary> protected virtual void SafelyCallLevelCompleted() { if (levelCompleted != null) { levelCompleted(); } } /// <summary> /// Calls the <see cref="numberOfEnemiesChanged"/> event /// </summary> protected virtual void SafelyCallNumberOfEnemiesChanged() { if (numberOfEnemiesChanged != null) { numberOfEnemiesChanged(numberOfEnemies); } } /// <summary> /// Calls the <see cref="levelFailed"/> event /// </summary> protected virtual void SafelyCallLevelFailed() { if (levelFailed != null) { levelFailed(); } } } }[/cce_csharp]
15行目 : 「public class LevelManager : Singleton<LevelManager>」はUnity チュートリアルのタワーディフェンステンプレートを触ってみる(46)で解説した Singleton を継承し、総称型として LevelManager を指定していることを指している。
Singleton と 総称型については、こちらを参照してほしい。
Unity チュートリアルのタワーディフェンステンプレートを触ってみる(45)では Wave を登録し管理する WaveManager について解説を行った。今回は Prefab から追加した GameManager 継承関係にある Singleton と PersistentSingleton、GameManagerBase の解説を行っていく。1.タワーディフェンステンプレートのステージの設定編 – Singleton.csSingleton.cs について稚拙ながら解説「Singleton.cs」は「Assets/Scripts/Core/Utilities/Singleton.cs」の指しておりスクリプトについては以下の通り。内容としては Singleton 処理を... Unity チュートリアルのタワーディフェンステンプレートを触ってみる(46) - StudioFun |
124行目 : 「public virtual void IncrementNumberOfEnemies()」は Agent の数を増加させる処理を行っている。内容としてはグローバル変数の numberOfEnemies の数を 1 増加させ、SafelyCallNumberOfEnemiesChanged の処理を行っている。
133行目 : 「public float GetAllHomeBasesHealth()」はすべての拠点の体力を合計している。
146行目 : 「public virtual void DecrementNumberOfEnemies()」は Agent の数を減少させる処理を行っている。内容としてはグローバル変数の numberOfEnemies の数を 1 減少させ、SafelyCallNumberOfEnemiesChanged を行っている。その後 numberOfEnemies が 0 以下であれば numberOfEnemies を 0とし、0 であれば ChangeLevelState で 状態を LevelState.Win とするの処理を行っている。
165行目 : 「public virtual void BuildingCompleted()」は ChangeLevelState で状態を LevelState.SpawningEnemies にする処理を行っている。
174行目 : 「protected override void Awake()」は Unity 固有の起動処理を行っている。内容としては Singleton の Awake を行いった後、WaveManager を取得し、spawningCompleted に OnSpawningCompleted の設定を行っている。その後、Currency の初期化と intro が null でなければ introCompleted に IntroCompleted を設定し、設定がなければ IntroCompleted を実行している。また、 homeBase の初期化も行っている。
210行目 : 「protected virtual void Update()」は Unity 固有の更新処理を行っている。内容としては定期的に currencyGainer (通貨の取得)を行っている。
222行目 : 「protected override void OnDestroy()」は Unity 固有の破壊された時の処理を行っている。内容としては waveManager と intro、homeBases それぞれに設定してる関数の解除を行っている。
240行目 : 「protected virtual void IntroCompleted()」は ChangeLevelState で状態を LevelState.Building としている。
252行目 : 「protected virtual void OnSpawningCompleted()」は ChangeLevelState で状態を LevelState.AllEnemiesSpawned としている。
261行目 : 「protected virtual void ChangeLevelState(LevelState newState)」は引数の状態によって処理を振り分けている。引数の状態が前回の状態と異なっていれば、状態によってそれぞれ処理を行っている。それぞれの処理については以下の通り。
- LevelState.SpawningEnemies : waveManager.StartWaves() を実行している
- LevelState.AllEnemiesSpawned : numberOfEnemies が 0 ならば ChangeLevelState で状態を LevelState.Winとしている
- LevelState.Lose : SafelyCallLevelFailed を実行している
- LevelState.Win : SafelyCallLevelCompleted を実行している
300行目 : 「protected virtual void OnHomeBaseDestroyed(DamageableBehaviour homeBase)」は拠点が破壊されたときの処理を行っている。内容としては numberOfHomeBasesLeft を減少させ、homeBaseDestroyed の設定があれば homeBaseDestroyed を実行し、numberOfHomeBasesLeft が 0 かつゲームオーバーでなければ ChangeLevelState で状態を LevelState.Loseとしている
321行目 : 「protected virtual void SafelyCallLevelCompleted()」はlevelCompleted に設定があれば levelCompleted を実行している。
332行目 : 「protected virtual void SafelyCallNumberOfEnemiesChanged()」は numberOfEnemiesChanged に設定があれば numberOfEnemiesChanged を実行している。
343行目 : 「protected virtual void SafelyCallLevelFailed()」は levelFailed に設定があれば levelFailed を実行している。