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

Unity チュートリアルのタワーディフェンステンプレートを触ってみる(4) では作成した TowerLevelData スクリプタブルオブジェクトと TowerLevel プレハブを用いてタワーの作成を進めた。今回はタワーの作成にあたって追加した Tower.cs スクリプトの解説を行っていく。
「Tower.cs」 は 「Targetable.cs」 を継承し、「Targetable.cs」 は 「DamageableBehaviour.cs」 を継承している。
大本である「DamageableBehaviour.cs」から順に解説していったほうがわかりやすいと思われるため、そちらから順に説明を行っていく。
『継承(けいしょう、inheritance:インヘリタンス)とはオブジェクト指向を構成する概念の一つである。あるオブジェクトが他のオブジェクトの特性を引き継ぐ場合、両者の間に「継承関係」があると言われる。
主にクラスベースのオブジェクト指向言語で、既存クラスの機能、構造を共有する新たなクラスを派生することができ(サブクラス化)、そのようなクラスは「親クラス(スーパークラス)を継承した」という。具体的には変数定義や操作(メソッド)などが引き継がれる。またJavaのインタフェース継承のように機能セットの仕様のみを引き継ぐ場合もある。
一般的に、BがAを継承する場合、B is a A. (BはAの一種である)という意味的な関係(Is-a関係)が成り立つ。従って、同じふるまいを持つからと言って、意味的に無関係なクラス間に継承関係を持たせるのは適切でない場合が多い。
プロトタイプベースのオブジェクト指向言語(Self、NewtonScript等)のように「クラス」という概念を持たない場合でも、クローン元となるオブジェクトを指して「継承」と呼ぶ。
継承と類似の概念に「委譲」があるが、継承では一度定まった継承関係は通常変更されないのに対して、委譲対象は必要に応じて変更されうるものである。
Is-a関係を持つ継承とは階層が異なる概念として集約 (aggregation) とコンポジション集約 (composition) があるが、これはクラス間の関係がHas-aである包含関係であり、クラス間の関係は継承よりも疎である。』
とのことだが、そこまで深く理解しなくても「Tower.cs」は「Targetable.cs」にある public もしくは protected の関数やグローバル変数を自在に使用することができるという認識で問題ないと思う。
1.タワー編 – DamageableBehaviour.cs
DamageableBehaviour.cs について稚拙ながら解説
「DamageableBehaviour.cs」は「Asset/Scripts/Core/Health/DamageableBehaviour.cs」の指しておりスクリプトについては以下の通り。内容としてはダメージを受ける、オブジェクトの削除を行う内容となっている。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123 using System;
using UnityEngine;
namespace Core.Health
{
/// <summary>
/// Abstract class for any MonoBehaviours that can take damage
/// </summary>
public class DamageableBehaviour : MonoBehaviour
{
/// <summary>
/// The Damageable object
/// </summary>
public Damageable configuration;
/// <summary>
/// Gets whether this <see cref="DamageableBehaviour" /> is dead.
/// </summary>
/// <value>True if dead</value>
public bool isDead
{
get { return configuration.isDead; }
}
/// <summary>
/// The position of the transform
/// </summary>
public virtual Vector3 position
{
get { return transform.position; }
}
/// <summary>
/// Occurs when damage is taken
/// </summary>
public event Action<HitInfo> hit;
/// <summary>
/// Event that is fired when this instance is removed, such as when pooled or destroyed
/// </summary>
public event Action<DamageableBehaviour> removed;
/// <summary>
/// Event that is fired when this instance is killed
/// </summary>
public event Action<DamageableBehaviour> died;
/// <summary>
/// Takes the damage and also provides a position for the damage being dealt
/// </summary>
/// <param name="damageValue">Damage value.</param>
/// <param name="damagePoint">Damage point.</param>
/// <param name="alignment">Alignment value</param>
public virtual void TakeDamage(float damageValue, Vector3 damagePoint, IAlignmentProvider alignment)
{
HealthChangeInfo info;
configuration.TakeDamage(damageValue, alignment, out info);
var damageInfo = new HitInfo(info, damagePoint);
if (hit != null)
{
hit(damageInfo);
}
}
protected virtual void Awake()
{
configuration.Init();
configuration.died += OnConfigurationDied;
}
/// <summary>
/// Kills this damageable
/// </summary>
protected virtual void Kill()
{
HealthChangeInfo healthChangeInfo;
configuration.TakeDamage(configuration.currentHealth, null, out healthChangeInfo);
}
/// <summary>
/// Removes this damageable without killing it
/// </summary>
public virtual void Remove()
{
// Set health to zero so that this behaviour appears to be dead. This will not fire death events
configuration.SetHealth(0);
OnRemoved();
}
/// <summary>
/// Fires kill events
/// </summary>
void OnDeath()
{
if (died != null)
{
died(this);
}
}
/// <summary>
/// Fires the removed event
/// </summary>
void OnRemoved()
{
if (removed != null)
{
removed(this);
}
}
/// <summary>
/// Event fired when Damageable takes critical damage
/// </summary>
void OnConfigurationDied(HealthChangeInfo changeInfo)
{
OnDeath();
Remove();
}
}
}
14行目 : 「public Damageable configuration;」は以下の図の赤枠部分を指してるグローバル変数である。

36行目 ~ 46行目 : 「public event Action ~~;」は外部から「DamageableBehaviour」使用したいメソッドを定義するために使用する。例えば一番最初に出てくる、「public event Action<HitInfo> hit;」であれば、「Asset/Scripts/Core/Health/DamageableListener.cs」の OnHit という関数を登録しており、共通の処理で異なるスクリプトとして振る舞うことができるよう設計されている。
55行目 : 「 public virtual void TakeDamage(float damageValue, Vector3 damagePoint, IAlignmentProvider alignment)」はダメージを受けた時の処理を定義している。「Damageable(ダメージ計算が起こりうる)」クラスでダメージ計算を行い、「DamageableListener」で登録した関数に対してダメージ情報を与えている。また、この関数は「virtual」で定義されており、これは「Tower.cs」 や 「Targetable.cs」にて処理内容の書き換えを許可している。
58行目 : 「configuration.TakeDamage(damageValue, alignment, out info);」の引数として out という文字が入っているこれは、configuration.TakeDamage で計算した内容を info という変数に入れてくれという C# の記載方法の一つである。
66行目 : 「protected virtual void Awake()」では、「DamageableBehaviour」起動時の初期化を行っている。具体的には「Damageable(ダメージ計算が起こりうる)」クラスの初期化とタワーがやられたときの挙動を定義している。
75行目 : 「protected virtual void Kill()」では、タワーを消去する処理を定義じている。
85行目 : 「public virtual void Remove()」では、タワーを撤去する処理を定義じている。Kill と Remove の違いとして、kill は敵にやられて削除するが Remove は プレイヤーが意図的に撤去する処理となっている。
以降はそれぞれタワーがやられた時、削除した時、ダメージによってやられた時の処理を定義している。