インターフェースとTryGetComponent
CoddyのUnityジャーニー「Unityにおけるゲームアーキテクチャ」セクションの一部。レッスン 7/50。
TryGetComponentは1回の呼び出しでコンポーネントを探し、見つかったかどうかを知らせます。インターフェースにも使えるため、ぶつかった相手がダメージを受けるかどうかを調べる一般的な方法です。この地雷は、IDamageableを持つ周囲のすべてにダメージを与えます。
public class Mine : MonoBehaviour
{
public float radius = 3f;
public int damage = 5;
void OnTriggerEnter(Collider other)
{
foreach (Collider c in Physics.OverlapSphere(transform.position, radius))
{
if (c.TryGetComponent(out IDamageable target))
{
target.TakeDamage(damage);
}
}
Destroy(gameObject);
}
}out IDamageable targetは、呼び出しの中で変数を宣言します。オブジェクトがIDamageableを実装するコンポーネントを持つ場合、TryGetComponentはtrueを返してtargetに値を設定します。そうでなければfalseを返し、targetはnullになります。検索対象はそのオブジェクトだけで、親や子は調べません。
GetComponentは、クラスが見つからない場合とインターフェースが見つからない場合で、異なる動作をします。Editorでは、Rigidbodyを持たないオブジェクトに対してGetComponent<Rigidbody>()を呼び出すと、Unityの偽のnullが返ります。== nullはtrueになりますが、?.では検出されず、呼び出しはMissingComponentExceptionをスローします。インターフェースには偽のnullはありません。GetComponent<IDamageable>()は実際のC#のnullを返すため、== nullと?.のどちらも使えます。TryGetComponentなら、どちらの場合もこの問題を回避でき、Editorではコンポーネントが見つからなくてもメモリを割り当てません。
チャレンジ
簡単レーザータワーは左右の矢印キーで回転し、Spaceキーを押すとLaser.csのFire()が呼び出されます。Fireを記述してください。タワーの前方に向けて、rangeユニットの長さのレイを飛ばし(Inspectorで10に設定されています)、次の処理を行います。
- IDamageableを持つものに当たった場合、そのオブジェクトに
TakeDamage(damage)を呼び出し、Hitとオブジェクト名をログに出力します。たとえば、Hit Robotのようになります。 - IDamageableは持たないものの、Rigidbodyを持つ場合は、
AddForce(transform.forward * push, ForceMode.Impulse)で押し、Pushedとその名前をログに出力します。 - それ以外の場合は、
Blocked byとその名前をログに出力します。たとえば、Blocked by Pillarのようになります。何にも当たらなかった場合は、Missedをログに出力します。
両方のコンポーネント検索にTryGetComponentを使ってください。IDamageable.cs、Robot.cs、Balloon.csは準備済みです。Playを押して全方向に発射し、その後Checkを押してください。
自分で試してみよう
using UnityEngine;
using UnityEngine.InputSystem;
public class Laser : MonoBehaviour
{
public float range = 10f;
public int damage = 1;
public float push = 5f;
public float turnSpeed = 90f;
private InputAction moveAction;
void Start()
{
moveAction = InputSystem.actions.FindAction("Move");
}
void Update()
{
float turn = moveAction.ReadValue<Vector2>().x;
transform.Rotate(0f, turn * turnSpeed * Time.deltaTime, 0f);
if (Keyboard.current.spaceKey.wasPressedThisFrame)
{
Fire();
}
}
void Fire()
{
// 前方に長さ range 単位のレイを飛ばし、命中した対象を処理する
}
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。