Unity チュートリアルのタワーディフェンステンプレートを触ってみる(48)

Unity チュートリアルのタワーディフェンステンプレートを触ってみる(47)では Prefab から追加した GameManager の解説を行った。は GameManager に合わせて追加した LevelManager の解説を行っていく。
1.タワーディフェンステンプレートのステージの設定編 – LevelManager.cs
LevelManager.cs について稚拙ながら解説
「LevelManager.cs」は「Assets/Scripts/TowerDefense/Level/LevelManager.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
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351 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();
}
}
}
}
15行目 : 「public class LevelManager : Singleton<LevelManager>」はUnity チュートリアルのタワーディフェンステンプレートを触ってみる(46)で解説した Singleton を継承し、総称型として LevelManager を指定していることを指している。
Singleton と 総称型については、こちらを参照してほしい。
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 を実行している。