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 が必須であることを指している。以前の記事についてはこちらから
Unity チュートリアルのタワーディフェンステンプレートを触ってみる(5)、(6)、(7) と Tower.cs スクリプトの解説を行ってきた。今回は引き続きUnity チュートリアルのタワーディフェンステンプレートを触ってみる(4)に記載している通り 「DamageCollider.cs」 がその前に継承関係にある「DamageZone.cs」の解説を行っていく。「DamageCollider.cs」は「DamageZone.cs」を継承している。そのため今回は「DamageCollider.cs」解説する前に「DamageZone.cs」について解説を行っていく。1.タワー編 – DamageZone.csDamageZo... Unity チュートリアルのタワーディフェンステンプレートを触ってみる(8) - StudioFun |
16行目 : 「public abstract class Agent : Targetable」 は以前解説を行った Targetable クラスを継承した抽象クラスであることを指している。Targetable クラスについてはこちらを参照してほしい。また、抽象クラスについてはこちらを参照してほしい。
Targetable
Unity チュートリアルのタワーディフェンステンプレートを触ってみる(5) では Tower.cs の継承元となっている DamageableBehaviour.cs スクリプトの解説を行った。今回は更に Tower.cs の継承元となっている Targetable.cs の解説を行っていく。「Tower.cs」 は 「Targetable.cs」 を継承し、「Targetable.cs」 は 「DamageableBehaviour.cs」 を継承している。前回は「DamageableBehaviour.cs」の行った。続いて「Targetable.cs」 を行っていく。DamageableBehaviour.cs の解説についてはこちらを参照してほしい。1.タワー編 - T... Unity チュートリアルのタワーディフェンステンプレートを触ってみる(6) - StudioFun |
抽象クラス
Unity チュートリアルのタワーディフェンステンプレートを触ってみる(5)、(6)、(7) と Tower.cs スクリプトの解説を行ってきた。今回は引き続きUnity チュートリアルのタワーディフェンステンプレートを触ってみる(4)に記載している通り 「DamageCollider.cs」 がその前に継承関係にある「DamageZone.cs」の解説を行っていく。「DamageCollider.cs」は「DamageZone.cs」を継承している。そのため今回は「DamageCollider.cs」解説する前に「DamageZone.cs」について解説を行っていく。1.タワー編 – DamageZone.csDamageZo... Unity チュートリアルのタワーディフェンステンプレートを触ってみる(8) - StudioFun |
21行目 : 「public enum State」 は列挙型のデータを定義している。『C#プログラミング言語の列挙型はCのenumの意味する多くの”小さな整数”を保持する。いくつかの数値演算はenumでは定義されないが、enum値は明示的に整数に変換し元に戻すことができる。またenum変数はenum宣言によって定義されなかった値を保存できる。 』(Wikipedia 列挙型 C# より引用)
列挙型 - Wikipedia - ja.wikipedia.org |
とのことだが、最初のうちはイメージとしてリストのイメージを持っていれば良いと思う。果物のリストであったり、今回のように「ブロックされていない経路上にいる状態」「ブロックされている経路上にいる状態」などの状態がのリストと言う認識だ。
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 はオブジェクトを選択している時のみ描画されます。ギズモは選択可能ではありません。 これはセットアップを簡単に実行するために使用されます。例えば、爆発スクリプトは 爆発半径を表示する球体のギズモを描画することができます。』とある。公式ページについてはこちらを参照していただきたい。
オブジェクトが選択されている場合は、ギズモを描画するために OnDrawGizmosSelected を実装します。 MonoBehaviour-OnDrawGizmosSelected() - Unity スクリプトリファレンス - docs.unity3d.com |
簡単に説明すると「Unity に搭載されている、開発用に表示できる範囲やエリアの表示するための関数(処理については自作)」と思っていただければ良いと思う。