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

Unity チュートリアルのタワーディフェンステンプレートを触ってみる(2) では実際に Unity を用いてタワーディフェンステンプレートを用いてタワーの設定データの作成と解説を行った。今回は実際にTowerLevel プレハブとそれに付随するスクリプトの解説を行っていく。

1.タワー編 – TowerLevel プレハブを作成する

『次に、これらのレベルそれぞれの TowerLevel プレハブを作成します。TowerLevel プレハブには、タワーのそのレベルに関連するスクリプタブルオブジェクトへの参照を持つ TowerLevel コンポーネントが含まれます。プレハブにはまた、タワーが敵を目標にし攻撃する方法を決定する子オブジェクトも含まれます。』

とのことなので指示通り「TowerLevel プレハブ」を作成していく。チュートリアルの手順では以下の通りとなっている。

  • Hierarchy ウィンドウの Create メニューから Create > Create Empty を選択します。
  • 新しいゲームオブジェクトにタワータイプとレベルを示す名前を付けます (例えば LaserTower_1)。
  • TowerLevel コンポーネントをゲームオブジェクトに追加します。
  • このタワーのレベルに対応する ScriptableObject を Level Data フィールドにドラッグします。
  • タワーの外観を決めるゲームオブジェクトを子として追加します。
  • タワーを格納する Prefabs/Towers フォルダーを作成します。
  • Hierarchy ビューにあるタワーのゲームオブジェクトを、新しく作ったフォルダにドラッグし、プレハブを作成します。

上記の指示通り空の GameObject に対して「LaserTower_1」という名称を付け、TowerLevel コンポーネントを追加した。その後、Level Data を TowerLevel  コンポーネントに追加し、タワーの外観を設定した。すると以下のような状態となる。

TowerLevel1 の作成

TowerLevel コンポーネントは「Assets/Scripts/TowerDefense/Towers/TowerLevel.cs」のことを指している。

タワーの外観(形状)については「Assets/Models/Towers/Laser」に格納されている、「LaserTower_BASE_L01」、「LaserTower_BASE_L01_Shadow」、「LaserTower_TURRET_L01」、「LaserTower_TURRET_L01_Shadow」を用いて作成している。

タワーの外観(テクスチャ)については「Assets/Models/Towers/Laser/Material」の「LaserTower_L01_Albedo_V1」を用いて設定した。

その後、作成したデータをプレハブに登録する。プレハブへの登録方法については別途記事を作成しているためこちらを参照してほしい。

稚拙ながら少々解説

「Assets/Scripts/TowerDefense/Towers/TowerLevel.cs」の内容としては攻撃エフェクトやダメージ計算を主に行っている。

[cce_csharp]using System.Collections.Generic;
using Core.Health;
using TowerDefense.Affectors;
using TowerDefense.Towers.Data;
using TowerDefense.UI.HUD;
using UnityEngine;

namespace TowerDefense.Towers
{
    /// < summary>
    /// An individual level of a tower
    /// < /summary>
    [DisallowMultipleComponent]
    public class TowerLevel : MonoBehaviour, ISerializationCallbackReceiver
    {
        /// < summary>
        /// The prefab for communicating placement in the scene
        /// < /summary>
        public TowerPlacementGhost towerGhostPrefab;

        /// < summary>
        /// Build effect gameObject to instantiate on start
        /// < /summary>
        public GameObject buildEffectPrefab;

        /// < summary>
        /// Reference to scriptable object with level data on it
        /// < /summary>
        public TowerLevelData levelData;

        /// < summary>
        /// The parent tower controller of this tower
        /// < /summary>
        protected Tower m_ParentTower;

        /// < summary>
        /// The list of effects attached to the tower
        /// < /summary>
        Affector[] m_Affectors;

        /// < summary>
        /// Gets the list of effects attached to the tower
        /// < /summary>
        protected Affector[] Affectors
        {
            get
            {
                if (m_Affectors == null)
                {
                    m_Affectors = GetComponentsInChildren< Affector>();
                }
                return m_Affectors;
            }
        }

        /// < summary>
        /// The physics layer mask that the tower searches on
        /// < /summary>
        public LayerMask mask { get; protected set; }

        /// < summary>
        /// Gets the cost value
        /// < /summary>
        public int cost
        {
            get { return levelData.cost; }
        }

        /// < summary>
        /// Gets the sell value
        /// < /summary>
        public int sell
        {
            get { return levelData.sell; }
        }

        /// < summary>
        /// Gets the max health
        /// < /summary>
        public int maxHealth
        {
            get { return levelData.maxHealth; }
        }

        /// < summary>
        /// Gets the starting health
        /// < /summary>
        public int startingHealth
        {
            get { return levelData.startingHealth; }
        }

        /// < summary>
        /// Gets the tower description
        /// < /summary>
        public string description
        {
            get { return levelData.description; }
        }

        /// < summary>
        /// Gets the tower description
        /// < /summary>
        public string upgradeDescription
        {
            get { return levelData.upgradeDescription; }
        }

        /// < summary>
        /// Initialises the Effects attached to this object
        /// < /summary>
        public virtual void Initialize(Tower tower, LayerMask enemyMask, IAlignmentProvider alignment)
        {
            mask = enemyMask;
            
            foreach (Affector effect in Affectors)
            {
                effect.Initialize(alignment, mask);
            }
            m_ParentTower = tower;
        }

        /// < summary>
        /// A method for activating or deactivating the attached < see cref="Affectors"/>
        /// < /summary>
        public void SetAffectorState(bool state)
        {
            foreach (Affector affector in Affectors)
            {
                if (affector != null)
                {
                    affector.enabled = state;
                }
            }
        }

        /// < summary>
        /// Returns a list of affectors that implement ITowerRadiusVisualizer
        /// < /summary>
        /// < returns>ITowerRadiusVisualizers of tower< /returns>
        public List< ITowerRadiusProvider> GetRadiusVisualizers()
        {
            List< ITowerRadiusProvider> visualizers = new List< ITowerRadiusProvider>();
            foreach (Affector affector in Affectors)
            {
                var visualizer = affector as ITowerRadiusProvider;
                if (visualizer != null)
                {
                    visualizers.Add(visualizer);
                }
            }
            return visualizers;
        }

        /// < summary>
        /// Returns the dps of the tower
        /// < /summary>
        /// < returns>The dps of the tower< /returns>
        public float GetTowerDps()
        {
            float dps = 0;
            foreach (Affector affector in Affectors)
            {
                var attack = affector as AttackAffector;
                if (attack != null && attack.damagerProjectile != null)
                {
                    dps += attack.GetProjectileDamage() * attack.fireRate;
                }
            }
            return dps;
        }

        public void Kill()
        {
            m_ParentTower.KillTower();
        }

        public void OnBeforeSerialize()
        {
        }

        public void OnAfterDeserialize()
        {
            // Setting this member to null is required because we are setting this value on a prefab which will 
            // persists post run in editor, so we null this member to ensure it is repopulated every run
            m_Affectors = null;
        }

        /// < summary>
        /// Insntiate the build particle effect object
        /// < /summary>
        void Start()
        {
            if (buildEffectPrefab != null)
            {
                Instantiate(buildEffectPrefab, transform);
            }
        }
    }
}[/cce_csharp]

13行目:[DisallowMultipleComponent] は GameObject に2つ以上同じコンポーネントをアタッチできないようにする定型文。

14行目 : [ISerializationCallbackReceiver] はシリアライズやデシリアライズ時にコールバックを受信するインターフェース。Unity では一部のデータをシリアライズ(バイトデータへ変換)することができないものがあるため、このインタフェースを用いてシリアライズを行う必要がある。上記プログラミングでは178行目の「public void OnBeforeSerialize()」関数と182行目の「public void OnBeforeSerialize()」のスクリプトが ISerializationCallbackReceiver の処理に当たる。ここではシリアライズ完了後、 Affector の配列である「m_affector」に対して null を代入している。

44行目 : [protected Affector[] Affectors] では GameObject に登録されているエフェクトを配列として取得し、それを返却している。

112行目 : [public virtual void Initialize(Tower tower, LayerMask enemyMask, IAlignmentProvider alignment)] では地震が登録されているタワー及び、敵用のレイアーの保持、エフェクトの初期化を行っている。

126行目 : [public void SetAffectorState(bool state)] ではエフェクトの引数によって与えられたパラメーターによって有効・無効処理を行っている。

141行目 : [public List< ITowerRadiusProvider> GetRadiusVisualizers()] エフェクトの範囲を表示するためのデータをリスト化し返却している。

159行目 : [public float GetTowerDps()] ではタワーの攻撃力とフレームあたりの攻撃回数から DPS (Damege Per Seconds)  の計算を行っている。

173行目 : [public void Kill] では登録されているタワーの破壊を行っている。

192行目 : [void Start()] ではタワーが建築されるエフェクトを生成している。

 

長くなってしまったため、一旦ここまで。チュートリアルを行うだけであれば、簡単なのだがスクリプトの内容を理解すると大変である。

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