Unity チュートリアルのタワーディフェンステンプレートを触ってみる(12)
Unity チュートリアルのタワーディフェンステンプレートを触ってみる(11)ではエージェント(敵ユニット)の作成を行った。今回はエージェントに使用したスクリプトのうち、AttackingAgent と FlyingAgent の継承元となる Agent 解説を行っていきたいと思う。
1.エージェント編 – Agent.cs
Agent.cs について稚拙ながら解説
「Agent.cs」は「Asset/Scripts/TowerDefense/Agents/Agent.cs」の指しておりスクリプトについては以下の通り。内容としては Agent の挙動制御を行っている。
[cce_csharp]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
}
}[/cce_csharp]
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 に搭載されている、開発用に表示できる範囲やエリアの表示するための関数(処理については自作)」と思っていただければ良いと思う。
