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

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

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

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

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

[cce_csharp]using Core.Health;
using TowerDefense.Affectors;
using TowerDefense.Towers;
using UnityEngine;

namespace TowerDefense.Agents
{
    /// <summary>
    /// An implementation of Agent that will attack 
    /// any Towers that block its path
    /// </summary>
    public class AttackingAgent : Agent
    {
        /// <summary>
        /// Tower to target
        /// </summary>
        protected Tower m_TargetTower;

        /// <summary>
        /// The attached attack affector
        /// </summary>
        protected AttackAffector m_AttackAffector;
        
        /// <summary>
        /// Is this agent currently engaging a tower?
        /// </summary>
        protected bool m_IsAttacking;

        public override void Initialize()
        {
            base.Initialize();
            
            // Attack affector
            m_AttackAffector.Initialize(configuration.alignmentProvider);
            
            // We don't want agents to attack towers until their path is blocked, 
            // so disable m_AttackAffector until it is needed
            m_AttackAffector.enabled = false;
        }

        /// <summary>
        /// Unsubscribes from tracked towers removed event
        /// and disables the attached attack affector
        /// </summary>
        public override void Remove()
        {
            base.Remove();
            if (m_TargetTower != null)
            {
                m_TargetTower.removed -= OnTargetTowerDestroyed;
            }
            m_AttackAffector.enabled = false;
            m_TargetTower = null;
        }

        /// <summary>
        /// Gets the closest tower to the agent
        /// </summary>
        /// <returns>The closest tower</returns>
        protected Tower GetClosestTower()
        {
            var towerController = m_AttackAffector.towerTargetter.GetTarget() as Tower;
            return towerController;
        }

        /// <summary>
        /// Caches the Attack Affector if necessary
        /// </summary>
        protected override void LazyLoad()
        {
            base.LazyLoad();
            if (m_AttackAffector == null)
            {
                m_AttackAffector = GetComponent<AttackAffector>();
            }
        }
        
        /// <summary>
        /// If the tower is destroyed while other agents attack it, ensure it becomes null
        /// </summary>
        /// <param name="tower">The tower that has been destroyed</param>
        protected virtual void OnTargetTowerDestroyed(DamageableBehaviour tower)
        {
            if (m_TargetTower == tower)
            {
                m_TargetTower.removed -= OnTargetTowerDestroyed;
                m_TargetTower = null;
            }
        }
        
        /// <summary>
        /// Peforms the relevant path update for the states <see cref="Agent.State.OnCompletePath"/>, 
        /// <see cref="Agent.State.OnPartialPath"/> and <see cref="Agent.State.Attacking"/>
        /// </summary>
        protected override void PathUpdate()
        {
            switch (state)
            {
                case State.OnCompletePath:
                    OnCompletePathUpdate();
                    break;
                case State.OnPartialPath:
                    OnPartialPathUpdate();
                    break;
                case State.Attacking:
                    AttackingUpdate();
                    break;
            }
        }
        
        /// <summary>
        /// Change to <see cref="Agent.State.OnCompletePath" /> when path is no longer blocked or to
        /// <see cref="Agent.State.Attacking" /> when the agent reaches <see cref="AttackingAgent.m_TargetTower" />
        /// </summary>
        protected override void OnPartialPathUpdate()
        {
            if (!isPathBlocked)
            {
                state = State.OnCompletePath;
                return;
            }

            // Check for closest tower at the end of the partial path
            m_AttackAffector.towerTargetter.transform.position = m_NavMeshAgent.pathEndPosition;
            Tower tower = GetClosestTower();
            if (tower != m_TargetTower)
            {
                // if the current target is to be replaced, unsubscribe from removed event
                if (m_TargetTower != null)
                {
                    m_TargetTower.removed -= OnTargetTowerDestroyed;
                }
                
                // assign target, can be null
                m_TargetTower = tower;
                
                // if new target found subscribe to removed event
                if (m_TargetTower != null)
                {
                    m_TargetTower.removed += OnTargetTowerDestroyed;
                }
            }
            if (m_TargetTower == null)
            {
                return;
            }
            float distanceToTower = Vector3.Distance(transform.position, m_TargetTower.transform.position);
            if (!(distanceToTower < m_AttackAffector.towerTargetter.effectRadius))
            {
                return;
            }
            if (!m_AttackAffector.enabled)
            {
                m_AttackAffector.towerTargetter.transform.position = transform.position;
                m_AttackAffector.enabled = true;
            }
            state = State.Attacking;
            m_NavMeshAgent.isStopped = true;
        }
        
        /// <summary>
        /// The agent attacks until the path is available again or it has killed the target tower
        /// </summary>
        protected void AttackingUpdate()
        {
            if (m_TargetTower != null)
            {
                return;
            }
            MoveToNode();

            // Resume path once blocking has been cleared
            m_IsAttacking = false;
            m_NavMeshAgent.isStopped = false;
            m_AttackAffector.enabled = false;
            state = isPathBlocked ? State.OnPartialPath : State.OnCompletePath;
            // Move the Targetter back to the agent's position
            m_AttackAffector.towerTargetter.transform.position = transform.position;
        }
    }
}[/cce_csharp]

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

29行目 : 「public override void Initialize()」は継承元となっている Targetable および Agent の初期化と AttackingAgent  のグローバル変数である AttackAffector の初期化を行っている。

45行目 : 「public override void Remove()」は継承元となっている Targetable および Agent の削除処理と削除されたときの OnTargetTowerDestroyed 処理を m_TargetTower.removed のイベントから解除している。

60行目 : 「protected Tower GetClosestTower()」は AttackAffector  から最も近いタワーの取得を行っている。

69行目 : 「protected override void LazyLoad()」は継承元となっている Targetable および Agent と同様に遅延読み込みを行っている。

82行目 : 「protected virtual void OnTargetTowerDestroyed(DamageableBehaviour tower)」はこの Agent が攻撃しているタワーが他の Agent によって破壊されたときにタワーオブジェクトを null として扱うように変更している。

95行目 : 「protected override void PathUpdate()」は Agent の状態によって行う処理を場合分けしている。ここでは「Agent が進む経路がブロックされていないとき」、「Agent が進む経路がブロックされているとき」、「Agent が攻撃しているとき」で分岐が行われている。

115行目 : 「protected override void OnPartialPathUpdate()」は Agent の進む経路がブロックされているときの処理を行っている。近くにあるタワーを判定し、攻撃範囲内にタワーがあれば攻撃を開始に移行している。

164行目 : 「protected void AttackingUpdate()」は Agent が攻撃しているときの処理を行っている。攻撃しているタワーがなくなったときに経路の状態を確認し、次のノードへ移動する処理に移行している。

2.エージェント編 – FlyingAgent.cs

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

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

[cce_csharp]using UnityEngine;
using UnityEngine.AI;

namespace TowerDefense.Agents
{
    /// <summary>
    /// Agent that can pass "over" towers that block the path
    /// </summary>
    public class FlyingAgent : Agent
    {
        /// <summary>
        /// Time to wait to clear the navmesh obstacles
        /// </summary>
        protected float m_WaitTime = 0.5f;

        /// <summary>
        /// The current time to wait until we can resume agent movement as normal
        /// </summary>
        protected float m_CurrentWaitTime;

        /// <summary>
        /// If flying agents are blocked, they should still move through obstacles
        /// </summary>
        protected override void OnPartialPathUpdate()
        {
            if (!isPathBlocked)
            {
                state = State.OnCompletePath;
                return;
            }
            if (!isAtDestination)
            {
                return;
            }
            m_NavMeshAgent.enabled = false;
            m_CurrentWaitTime = m_WaitTime;
            state = State.PushingThrough;
        }
        
        /// <summary>
        /// Controls behaviour based on the states <see cref="Agent.State.OnCompletePath"/>, <see cref="Agent.State.OnPartialPath"/> 
        /// and <see cref="Agent.State.PushingThrough"/>
        /// </summary>
        protected override void PathUpdate()
        {
            switch (state)
            {
                case State.OnCompletePath:
                    OnCompletePathUpdate();
                    break;
                case State.OnPartialPath:
                    OnPartialPathUpdate();
                    break;
                case State.PushingThrough:
                    PushingThrough();
                    break;
            }
        }

        /// <summary>
        /// When flying agents are pushing through, give them a small amount of time to clear the gap and turn on their agent
        /// once time elapses
        /// </summary>
        protected void PushingThrough()
        {
            m_CurrentWaitTime -= Time.deltaTime;

            // Move the agent, overriding its NavMeshAgent until it reaches its destination
            transform.LookAt(m_Destination, Vector3.up);
            transform.position += transform.forward * m_NavMeshAgent.speed * Time.deltaTime;
            if (m_CurrentWaitTime > 0)
            {
                return;
            }
            // Check if there is a navmesh under the agent, if not, then reset the timer
            NavMeshHit hit;
            if (!NavMesh.Raycast(transform.position + Vector3.up, Vector3.down, out hit, navMeshMask))
            {
                m_CurrentWaitTime = m_WaitTime;
            }
            else
            {
                // If the time elapses, and there is a NavMesh under it, resume agent movement as normal
                m_NavMeshAgent.enabled = true;
                NavigateTo(m_Destination);
                state = isPathBlocked ? State.OnPartialPath : State.OnCompletePath;
            }
        }
    }
}[/cce_csharp]

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

24行目 : 「protected override void OnPartialPathUpdate()」は Agent の進む経路がブロックされているときの処理を行っている。障害物があれば停止時間を設定し通過状態に移行している。

44行目 : 「protected override void PathUpdate()」は Agent の状態によって行う処理を場合分けしている。ここでは「Agent が進む経路がブロックされていないとき」、「Agent が進む経路がブロックされているとき」、「Agent が障害物を無視して通過しているとき」で分岐が行われている。

64行目 : 「protected void PushingThrough()」は指定時間待機後、障害物を通過する処理を行っている。

%d人のブロガーが「いいね」をつけました。