当たり判定でのメッセージやりとりを見てみよう

2022年8月23日

Unityでは、当たり判定はトリガーやコリジョンで相手のオブジェクトと接触を検知することで実現しています
単純な構成から拡張性が求められる(作り切りかオブジェクトが増えていくのか)によっても様々な作り方があります

前提となる知識

UnityのOnTriggerEnter機能を使ったことがある人

接触したらポイントを得るには

  • アイテムを取得したらポイントを得るケース(アイテムはDestroyされる)
  • 敵に接触したらポイントを得るケース(敵はDestroyされる)

相手によらず一定のポイントを得る

いつも同じポイントを得ることに限定したケースです

ポイントをもらう側

サンプルでは、どんな相手でも10ポイント得られます
一番単純なパターン

using UnityEngine;

public class Player : MonoBehaviour
{
    int score;

    void OnTriggerEnter(Collider other)
    {
        score += 10;
    }
}

同じく相手は限定されませんが、接触するとpoint分のポイント得られます
他のオブジェクトのスクリプトからpointフィールドの値を変更すると得られるポイントも変わります

using UnityEngine;

public class Player : MonoBehaviour
{
    public int point = 10;
    int score;

    void OnTriggerEnter(Collider other)
    {
        score += point;
    }
}

相手によって得られるポイントを変える

相手の種類によって、ポイント数がわかります
相手の種類を増やすことを考える場合です

リットは、ポイントをもらう側のコード追加で拡張ができること
デメリットは、相手の種類が増えたら、コードも都度変更しなければならないこと

小さなプロジェクトで拡張しないことが約束されているケースではこれでOK

ポイントをもらう側

接触した相手の tagをチェックし、tagに応じたポイントを加算します

using UnityEngine;

public class Player : MonoBehaviour
{
    int score;

    void OnTriggerEnter(Collider other)
    {
        if (other.CompareTag("Apple"))
        {
            score += 10;
        }
        if (other.CompareTag("Bomb"))
        {
            score -= 10;
        }
    }
}

ポイント数は相手が持っている(1)

ポイント数は、接触相手にアタッチされているスクリプトで定義されている仕組み
接触相手のオブジェクトを取得(other.gameObject)して、相手のスクリプト(コンポーネント)を取得後、public修飾子がついたpointフィールドの値を読み取ってスコアに加算しています

相手を増やした時に必要な変更点からメリットデメリットを考えます

メリット

  • ポイントをもらう側のコードを変更する必要がないこと
  • ポイントの宣言がポイントを与える側のコードに書けること

ポイントをもらう側

using UnityEngine;

public class Player : MonoBehaviour
{
    int score;

    void OnTriggerEnter(Collider other)
    {
        score += other.gameObject.GetComponent<State>().point;
    }
}

ポイントを与える側

using UnityEngine;

public class State : MonoBehaviour
{
    public int point = 10;
}

ポイント数は相手が持っている(2)

接触相手に必要となるスクリプトがアタッチされていない場合、エラーになるのを防ぐコードを追加
壁などのポイントがもらえない相手と接触してもポイントに影響しない

ポイントをもらう側

スクリプトを持っていない場合、nullになるので、「もしnullでなければ処理する」ように追記

using UnityEngine;

public class Player : MonoBehaviour
{
    int score;

    void OnTriggerEnter(Collider other)
    {
        State state = other.gameObject.GetComponent<State>();

        if (state != null)
        {
            score += state.point;
        }
    }
}

TryGetComponentメソッドに置き換えた例
処理は同じです。
state変数はローカル変数で、取得したコンポーネントが代入されています
このメソッドの戻り値は、取得できたかどうか?(true Or fales)です

using UnityEngine;

public class Player : MonoBehaviour
{
    int score;

    void OnTriggerEnter(Collider other)
    {
        if (other.TryGetComponent<State>(out var state))
        {
            score += state.point;
        }
    }
}

ポイント数は相手が持っている(3)

接触相手のスクリプト名(クラス名)を限定しない
これまでの例では、相手が持っているスクリプト名は限定されていました
次の例では、ポイントを持っていることが確定していれば、スクリプト名を限定しないような仕組みを実装してみます

インターフェース

ポイントを持たせたい相手は、Pointプロパティを必ず実装しなければならない決まりにする仕組みです
必要なものだけが書いてあるコードをインターフェースと言います

public interface IPoint
{
    int Point { get; set; }
}

ポイントを与える側

インターフェースを実装することで、強制的にコードが追加されます

using UnityEngine;

public class State : MonoBehaviour, IPoint
{
    public int Point { get; set; } = 10;
}

ポイントをもらう側

GetComponent<インターフェース名>に変更されています
このインターフェースが実装されていれば、スクリプトの型名は自由です
pointerは、ローカル変数(ここだけで使える)です。この変数は、他のブロックに影響しません

using UnityEngine;

public class Player : MonoBehaviour
{
    int score;

    void OnTriggerEnter(Collider other)
    {
        if (other.TryGetComponent<IPoint>(out var pointer))
        {
            score += pointer.Point;
        }
    }
}

ダメージを与える、受ける振る舞いを作りたい

  • 敵に接触したらダメージを与えるケース(敵のhpが減る。敵はすぐには、デストロイされない)

相手によらず、自分だけ一定のダメージを受ける

接触したら、相手によらず自分のhpが減ります
一番単純な例です

ダメージを受ける側

トリガーを検知した敵は、自らhpを減らす

using UnityEngine;

public class Enemy : MonoBehaviour
{
    int hp = 100;

     void OnTriggerEnter(Collider other)
    {
        hp -= 10;
    }
}

同じく相手は限定されませんが、接触するとdamagePoint分のダメージを受けます
他のオブジェクトのスクリプトからdamagePointフィールドの値を変更すると受けるダメージも変わります

using UnityEngine;

public class Player : MonoBehaviour
{
    public int damagePoint = 10;
    int hp = 100;

    void OnTriggerEnter(Collider other)
    {
        score += damagePoint;
    }
}

受けるダメージ量が一定(=与えるダメージ量)

ダメージを受ける側

using UnityEngine;

public class Enemy : MonoBehaviour
{
    int hp = 100;

    public void Damage()
    {
        hp -= 10;
    }
}

ダメージを与える側

using UnityEngine;

public class Player : MonoBehaviour
{
    void OnTriggerEnter(Collider other)
    {
        other.gameObject.GetComponent<Enemy>().Damage();
    }
}

与えるダメージ量を変える(=受けるダメージ量)

引数の値でダメージ量を渡しています

ダメージを受ける側

using UnityEngine;

public class Enemy : MonoBehaviour
{
    int hp = 100;

    public void Damage(int point)
    {
        hp -= point;
    }
}

ダメージを与える側

using UnityEngine;

public class Player : MonoBehaviour
{
    void OnTriggerEnter(Collider other)
    {
        other.gameObject.GetComponent<Enemy>().Damage(10);
    }
}

登場するオブジェクトを増やしたい

ポイントとダメージで同じことになります。
ダメージのパターンで見ていきます

将来、敵を増やしたい時にはこの仕組みを考えてみましょう

ダメージを受けるオブジェクトに必要な決まり

ダメージを受けるオブジェクトが実装しなければならないメソッドを指定
今回は、Damage(int point)メソッドとします

public interface IDamageable
{
    void Damage(int point);
}

ダメージを受ける側

ダメージを受ける機能が備わっていることを示すためIDamageableを実装します
インターフェースを実装すると言います

このインターフェースが実装されていれば、スクリプト名がEnemyである必要はありません

public class Enemy : MonoBehaviour, IDamageable

次のようなコードになります

using UnityEngine;

public class Enemy : MonoBehaviour, IDamageable
{
    int hp = 100;

    public void Damage(int point)
    {
        hp -= point;
    }
}

ダメージを与える側

ダメージを受けるのは、インターフェースを実装しているものに限定されます
GetComponent<インターフェース名>に変更されています

using UnityEngine;

public class Player : MonoBehaviour
{
    void OnTriggerEnter(Collider other)
    {
        other.gameObject.GetComponent<IDamageable>().Damage(10);
    }
}

TryGetComponentに変更しています
相手がIDamageableインターフェースを実装している場合のみダメージを与えます

using UnityEngine;

public class Player : MonoBehaviour
{
    int score;

    void OnTriggerEnter(Collider other)
    {
        if (other.TryGetComponent<IDamageable>(out var damage))
        {
           damage.Damage(10);
        }
    }
}

C#,Unity

Posted by hidepon