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

Unity チュートリアルのタワーディフェンステンプレートを触ってみる(11ではエージェント(敵ユニット)の作成を行った。今回はエージェントに使用したスクリプトのうち、AttackingAgent と FlyingAgent の継承元となる Agent 解説を行っていきたいと思う。

1.エージェント編 – Agent.cs

Agent.cs について稚拙ながら解説

「Agent.cs」は「Asset/Scripts/TowerDefense/Agents/Agent.cs」の指しておりスクリプトについては以下の通り。内容としては Agent の挙動制御を行っている。


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
using System;
using ActionGameFramework.Health;
using Core.Utilities;
using TowerDefense.Affectors;
using TowerDefense.Level;
using TowerDefense.Nodes;
using UnityEngine;
using UnityEngine.AI;

namespace TowerDefense.Agents
{
    /// <summary>
    /// An agent will follow a path of nodes
    /// </summary>
    [RequireComponent(typeof(NavMeshAgent)), RequireComponent(typeof(AttackAffector))]
    public abstract class Agent : Targetable
    {  
        /// <summary>
        /// A means of keeping track of the agent along its path
        /// </summary>
        public enum State
        {
            /// <summary>
            /// When the agent is on a path that is not blocked
            /// </summary>
            OnCompletePath,

            /// <summary>
            /// When the agent is on a path is blocked
            /// </summary>
            OnPartialPath,

            /// <summary>
            /// When the agent has reached the end of a blocked path
            /// </summary>
            Attacking,

            /// <summary>
            /// For flying agents, when they move over obstacles
            /// </summary>
            PushingThrough,
           
            /// <summary>
            /// When the agent has completed their path
            /// </summary>
            PathComplete
        }

        /// <summary>
        /// Event fired when agent reached its final node
        /// </summary>
        public event Action<Node> destinationReached;

        /// <summary>
        /// Position offset for an applied affect
        /// </summary>
        public Vector3 appliedEffectOffset = Vector3.zero;
       
        /// <summary>
        /// Scale adjustment for an applied affect
        /// </summary>
        public float appliedEffectScale = 1;

        /// <summary>
        /// The NavMeshAgent component attached to this
        /// </summary>
        protected NavMeshAgent m_NavMeshAgent;

        /// <summary>
        /// The Current node that the agent must navigate to
        /// </summary>
        protected Node m_CurrentNode;

        /// <summary>
        /// Reference to the level manager
        /// </summary>
        protected LevelManager m_LevelManager;

        /// <summary>
        /// Stores the Destination to the next node so we don't need to get new random positions every time
        /// </summary>
        protected Vector3 m_Destination;
       
        /// <summary>
        /// Gets the attached nav mesh agent velocity
        /// </summary>
        public override Vector3 velocity
        {
            get { return m_NavMeshAgent.velocity; }
        }
       
        /// <summary>
        /// The current state of the agent along the path
        /// </summary>
        public State state { get; protected set; }

        /// <summary>
        /// Accessor to <see cref="m_NavMeshAgent"/>
        /// </summary>
        public NavMeshAgent navMeshNavMeshAgent
        {
            get { return m_NavMeshAgent; }
            set { m_NavMeshAgent = value; }
        }

        /// <summary>
        /// The area mask of the attached nav mesh agent
        /// </summary>
        public int navMeshMask
        {
            get { return m_NavMeshAgent.areaMask; }
        }

        /// <summary>
        /// Gets this agent's original movement speed
        /// </summary>
        public float originalMovementSpeed { get; private set; }

        /// <summary>
        /// Checks if the path is blocked
        /// </summary>
        /// <value>
        /// The status of the agent's path
        /// </value>
        protected virtual bool isPathBlocked
        {
            get { return m_NavMeshAgent.pathStatus == NavMeshPathStatus.PathPartial; }
        }

        /// <summary>
        /// Is the Agent close enough to its destination?
        /// </summary>
        protected virtual bool isAtDestination
        {
            get { return navMeshNavMeshAgent.remainingDistance <= navMeshNavMeshAgent.stoppingDistance; }
        }

        /// <summary>
        /// Sets the node to navigate to
        /// </summary>
        /// <param name="node">The node that the agent will navigate to</param>
        public virtual void SetNode(Node node)
        {
            m_CurrentNode = node;
        }

        /// <summary>
        /// Stops the navMeshAgent and attempts to return to pool
        /// </summary>
        public override void Remove()
        {
            base.Remove();
           
            m_LevelManager.DecrementNumberOfEnemies();
            if (m_NavMeshAgent.enabled)
            {
                m_NavMeshAgent.isStopped = true;
            }
            m_NavMeshAgent.enabled = false;

            Poolable.TryPool(gameObject);
        }

        /// <summary>
        /// Setup all the necessary parameters for this agent from configuration data
        /// </summary>
        public virtual void Initialize()
        {
            ResetPositionData();
            LazyLoad();
            configuration.SetHealth(configuration.maxHealth);
            state = isPathBlocked ? State.OnPartialPath : State.OnCompletePath;

            m_NavMeshAgent.enabled = true;
            m_NavMeshAgent.isStopped = false;
           
            m_LevelManager.IncrementNumberOfEnemies();
        }

        /// <summary>
        /// Finds the next node in the path
        /// </summary>
        public virtual void GetNextNode(Node currentlyEnteredNode)
        {
            // Don't do anything if the calling node is the same as the m_CurrentNode
            if (m_CurrentNode != currentlyEnteredNode)
            {
                return;
            }
            if (m_CurrentNode == null)
            {
                Debug.LogError("Cannot find current node");
                return;
            }

            Node nextNode = m_CurrentNode.GetNextNode();
            if (nextNode == null)
            {
                if (m_NavMeshAgent.enabled)
                {
                    m_NavMeshAgent.isStopped = true;
                }
                HandleDestinationReached();
                return;
            }
           
            Debug.Assert(nextNode != m_CurrentNode);
            SetNode(nextNode);
            MoveToNode();
        }

        /// <summary>
        /// Moves the agent to a position in the <see cref="Agent.m_CurrentNode" />
        /// </summary>
        public virtual void MoveToNode()
        {
            Vector3 nodePosition = m_CurrentNode.GetRandomPointInNodeArea();
            nodePosition.y = m_CurrentNode.transform.position.y;
            m_Destination = nodePosition;
            NavigateTo(m_Destination);
        }

        /// <summary>
        /// The logic for what happens when the destination is reached
        /// </summary>
        public virtual void HandleDestinationReached()
        {
            state = State.PathComplete;
            if (destinationReached != null)
            {
                destinationReached(m_CurrentNode);
            }
        }
       
        /// <summary>
        /// Lazy Load, if necesaary and ensure the NavMeshAgent is disabled
        /// </summary>
        protected override void Awake()
        {
            base.Awake();
            LazyLoad();
            m_NavMeshAgent.enabled = false;
        }
       
        /// <summary>
        /// Updates the agent in its different states,
        /// Reset destination when path is stale
        /// </summary>
        protected virtual void Update()
        {
            // Update behaviour for different states
            PathUpdate();
           
            // If the path becomes invalid, repath the agent to the destination
            bool validStalePath = m_NavMeshAgent.isOnNavMesh && m_NavMeshAgent.enabled &&
                                  (!m_NavMeshAgent.hasPath && !m_NavMeshAgent.pathPending);
            if (validStalePath)
            {
                // Compare against squared stopping distance on agent.
                // We intentionally do not pre-square this value so that it can be changed at runtime dynamically
                float squareStoppingDistance = m_NavMeshAgent.stoppingDistance * m_NavMeshAgent.stoppingDistance;
                if (Vector3.SqrMagnitude(m_Destination - transform.position) < squareStoppingDistance &&
                    m_CurrentNode.GetNextNode() != null)
                {
                    // Proceed if we're at our destination
                    GetNextNode(m_CurrentNode);
                }
                else
                {
                    // Otherwise try repath
                    m_NavMeshAgent.SetDestination(m_Destination);
                }
            }
        }

        /// <summary>
        /// Set the NavMeshAgent's destination
        /// </summary>
        /// <param name="nextPoint">The position to navigate to</param>
        protected virtual void NavigateTo(Vector3 nextPoint)
        {
            LazyLoad();
            if (m_NavMeshAgent.isOnNavMesh)
            {
                m_NavMeshAgent.SetDestination(nextPoint);
            }
        }

        /// <summary>
        /// This is a lazy way of caching several components utilised by the Agent
        /// </summary>
        protected virtual void LazyLoad()
        {
            if (m_NavMeshAgent == null)
            {
                m_NavMeshAgent = GetComponent<NavMeshAgent>();
                originalMovementSpeed = m_NavMeshAgent.speed;
            }
            if (m_LevelManager == null)
            {
                m_LevelManager = LevelManager.instance;
            }
        }

        /// <summary>
        /// Move along the path, change to <see cref="Agent.State.OnPartialPath" />
        /// </summary>
        protected virtual void OnCompletePathUpdate()
        {
            if (isPathBlocked)
            {
                state = State.OnPartialPath;
            }
        }

        /// <summary>
        /// Peforms the relevant path update
        /// </summary>
        protected abstract void PathUpdate();

        /// <summary>
        /// The behaviour for when the agent has been blocked
        /// </summary>
        protected abstract void OnPartialPathUpdate();
       
       
#if UNITY_EDITOR
        /// <summary>
        /// Draw the agent's path
        /// </summary>
        protected virtual void OnDrawGizmosSelected()
        {
            if (m_NavMeshAgent != null)
            {
                Vector3[] pathPoints = m_NavMeshAgent.path.corners;
                int count = pathPoints.Length;
                for (int i = 0; i < count - 1; i++)
                {
                    Vector3 from = pathPoints[i];
                    Vector3 to = pathPoints[i + 1];
                    Gizmos.DrawLine(from, to);
                }
                Gizmos.DrawWireSphere(m_NavMeshAgent.destination, 0.2f);
            }
        }
#endif
    }
}

15行目 : 「[RequireComponent(typeof(NavMeshAgent)), RequireComponent(typeof(AttackAffector))]」 は以前にも解説した通り、NavMeshAgent コンポーネントと AttackAffector が必須であることを指している。以前の記事についてはこちらから

16行目 : 「public abstract class Agent : Targetable」 は以前解説を行った Targetable クラスを継承した抽象クラスであることを指している。Targetable クラスについてはこちらを参照してほしい。また、抽象クラスについてはこちらを参照してほしい。

Targetable

 

抽象クラス

21行目 : 「public enum State」 は列挙型のデータを定義している。『C#プログラミング言語の列挙型はCのenumの意味する多くの”小さな整数”を保持する。いくつかの数値演算はenumでは定義されないが、enum値は明示的に整数に変換し元に戻すことができる。またenum変数はenum宣言によって定義されなかった値を保存できる。 』(Wikipedia 列挙型 C# より引用)

とのことだが、最初のうちはイメージとしてリストのイメージを持っていれば良いと思う。果物のリストであったり、今回のように「ブロックされていない経路上にいる状態」「ブロックされている経路上にいる状態」などの状態がのリストと言う認識だ。

150行目 : 「public override void Remove()」 は以前解説を行った Targetable クラスの Remove 関数を上書きしていることを指している。内容としては Targetable クラスの Remove 処理を行った後、「LevelManager」のエレメント要素数を減らし処理を行い、「NavMeshAgent」の無効化を行っている。「LevelManager」と「NavMeshAgent」については改めて解説を行う。

167行目 : 「public virtual void Initialize()」は上書き可能な初期化関数である。位置データの初期化と遅延読み込み、状態の初期化、「NavMeshAgent」の有効化を行っている。

183行目 : 「public virtual void GetNextNode(Node currentlyEnteredNode)」は上書き可能な次のノードを取得する関数である。現在のノードから次のノードを探索し、次のノードへ移動するという内容を行っている模様。ここについては「NavMeshAgent」と「Node」についても確認を行わなければ処理の内容を追うのが難しい。

215行目 : 「public virtual void MoveToNode()」は上書き可能な次のノードへの移動関数である。ここでは、次ノードの座標を取得して実際の処理については「NavigateTo」にて行っている。

226行目 : 「public virtual void HandleDestinationReached()」は上書き可能な到達したノードを記録する関数である。

238行目 : 「protected virtual void Awake()」は Unity に搭載されている起動時処理を行っている。

249行目 : 「protected virtual void Update()」は Unity に搭載されている更新処理を行っている。内容としてはパスが有効であれば、メッシュ上の停止距離を計算してノードの更新次の行き先等の設定を行っている。

280行目 : 「protected virtual void NavigateTo(Vector3 nextPoint)」 は次の地点へのナビゲートを行っている。

292行目 : 「protected virtual void LazyLoad()」は上書き可能な遅延読み込みを行っている。

308行目 : 「protected virtual void OnCompletePathUpdate()」はパスがブロックされているか判定し、ブロックされている際は状態を OnPartialPath にしている

331行目 : 「protected virtual void OnDrawGizmosSelected()」については公式ページに『オブジェクトが選択されている場合は、ギズモを描画するために OnDrawGizmosSelected を実装します。Gizmos はオブジェクトを選択している時のみ描画されます。ギズモは選択可能ではありません。 これはセットアップを簡単に実行するために使用されます。例えば、爆発スクリプトは 爆発半径を表示する球体のギズモを描画することができます。』とある。公式ページについてはこちらを参照していただきたい。

簡単に説明すると「Unity に搭載されている、開発用に表示できる範囲やエリアの表示するための関数(処理については自作)」と思っていただければ良いと思う。

スポンサーリンク
%d人のブロガーが「いいね」をつけました。