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()」は指定時間待機後、障害物を通過する処理を行っている。