【Unity】的の当たった場所によって得点を決める

的にあたる場所による得点の違いを得るには、さまざまな方法があります
的に複数のコライダーを取り付け、どこに当たったか検出する方法もあります

今回は、的の中心から距離を測り、その情報を元に得点にする方法を取ります

いくつかの計算パターンを示します
その測定方法を通して、Unityの計算方法を学んでいきましょう

どのゲームオブジェクトで当たりを検出するか

OnCollisionイベントを含んだスクリプトをどのゲームオブジェクトにアタッチするかです

ボールと的の2つが考えられますね
どちらでも検出は可能ですが、今回は、的側で判定するようにしましょう

次のコードは、接触判定のイベントハンドラです
引数のcollisionには接触した相手の情報が入ります
たとえば、接触してきたゲームオブジェクトの名前(ヒエラルキーに表示されています)を取得したければ次のように記述します

private void OnCollisionEnter(Collision collision)
{
    Debug.Log(collision.gameObject.name);
}

ボールについては、ただ飛んでいくだけの処理になります

考え方

的の中心に近いほど、得点が高くなるようにしたいと思います
そうすると、計算には的の中心の座標とボールが当たったところの座標が必要ですね
その距離を測るといいですね
中心に近いほど、得点を高くすればいいですね
今回のサンプルでは、得点計算に使う2点間の距離の算出までを行います
なので、得点の表示などは含まれていません

的の中心座標

Boxコライダーのcenterプロパティを使う

BoxColliderには、コライダーの中心を取得できるプロパティがあります

centerプロパティ

ただし、これは、BoxColliderがアタッチされているゲームオブジェクトのローカル座標として取得になります

ワールド座標にするには、スクリプトがアタッチされているゲームオブジェクトのワールド座標の位置を足せばいいです

boxCollider.center + transform.position

変形としてローカル座標のその場所自体をワールド座標に変換するメソッドを使う方法もあります
結果は上記と同じになります

transform.TransformPoint(boxCollider.center)

中心点を自作

的のゲームオブジェクトの子オブジェクトとして空のゲームオブジェクトを作成し、中心とします

中心点はコライダーと関係なく独立させています

Positionを好きなところに設定できます

的に当たったボールの座標

Collisionクラスの接触点情報のプロパティを使う

Collisionイベント発生時に渡される引数からは数々の接触情報が得られます
contactsプロパティは接触したところの座標などの情報が配列として格納されています
pointプロパティは、座標情報を取得するものです

次のコードは1つ目の接触情報から座標を取得するものです
接触時のグローバル座標として使うことができます

collision.contacts[0].pointプロパティ

Collisionクラスの接触オブジェクトの座標プロパティを使う

接触したゲームオブジェクトの座標を取得することもできます
これは、厳密に言うと接触している接点の情報ではないので、上記との差が出ることがありますが誤差の範囲と考えることができます

collision.transform.positionプロパティ

距離の計算

的の中心座標と的に当たったボールの座標が取得できたので距離を計算します
今回のゲームは3Dなので当然奥行きがあります
つまり、単純に計算すると、的の中心とボールの中心は奥行き分だけ差が生じることになります

計算したいのは、xとyの情報なので、Vector2の距離計算メソッドを使います

Vector2.Distance(2D座標1, 2D座標2)

引数の2D座標1と2D座標2間の距離が算出されるメソッドです

さて、ここで不思議なことがあります
必要な引数は2D座標、つまりVector2構造体になりますが、今取得できたのは、オブジェクトのポジション、つまり3D座標(Vector3構造体)です

なぜ、何もしなくても変換されるかですが、UnityのVector2構造体には、Vector3構造体が代入されると自動的にVector2構造体に変換するコードが書かれているからです
これはオペレータ演算子のオーバーロードと呼ばれます
詳細は下記リンクを参照してください

的にアタッチするスクリプト

パターン1

的の中心座標Boxコライダーのcenterプロパティを使う
(コライダーのローカル座標をグローバル座標に変換するメソッドを使う)
的に当たったボールの座標Collisionクラスの接触点情報のプロパティを使う
using UnityEngine;

public class TargetController : MonoBehaviour
{
    // BoxCollider コンポーネントを格納するためのフィールド
    private BoxCollider boxCollider;

    private void Start()
    {
        // ゲームオブジェクトから BoxCollider コンポーネントを取得して保存
        boxCollider = GetComponent<BoxCollider>();
    }

    private void OnCollisionEnter(Collision collision)
    {
        // 衝突点のワールド座標を取得
        Vector3 collisionPoint = collision.contacts[0].point;
        // 中心点(BoxColliderの中心)のローカル座標をワールド座標に変換
        Vector3 centerInWorldSpace = transform.TransformPoint(boxCollider.center);
        // 衝突点から中心点までの距離を計算(奥行きは考慮しない)
        float distance1 = Vector2.Distance(collisionPoint, centerInWorldSpace);

        // 結果をデバッグログに表示
        Debug.Log("Target側での距離計測1 : " + distance1);
    }
}

パターン2

的の中心座標Boxコライダーのcenterプロパティを使う
(ゲームオブジェクトの座標+コライダーのローカル座標)
的に当たったボールの座標Collisionクラスの接触点情報のプロパティを使う
using UnityEngine;

public class TargetController : MonoBehaviour
{
    // BoxCollider コンポーネントを格納するためのフィールド
    private BoxCollider boxCollider;

    private void Start()
    {
        // ゲームオブジェクトから BoxCollider コンポーネントを取得して保存
        boxCollider = GetComponent<BoxCollider>();
    }

    private void OnCollisionEnter(Collision collision)
    {
        // 衝突点のワールド座標を取得
        Vector3 collisionPoint = collision.contacts[0].point;

        // 中心点(BoxColliderの中心)のローカル座標をワールド座標に変換
        Vector3 centerInWorldSpace = boxCollider.center + transform.position;
        
        // 衝突点から中心点までの距離を計算(奥行きは考慮しない)
        float distance2 = Vector2.Distance(collisionPoint, centerInWorldSpace);

        // 結果をデバッグログに表示
        Debug.Log("Target側での距離計測2 : " + distance2);
    }
}

パターン3

的の中心座標Boxコライダーのcenterプロパティを使う
(ゲームオブジェクトの座標+コライダーのローカル座標)
的に当たったボールの座標Collisionクラスの接触オブジェクトの座標プロパティを使う
using UnityEngine;

public class TargetController : MonoBehaviour
{
    // BoxCollider コンポーネントを格納するためのフィールド
    private BoxCollider boxCollider;

    private void Start()
    {
        // ゲームオブジェクトから BoxCollider コンポーネントを取得して保存
        boxCollider = GetComponent<BoxCollider>();
    }

    private void OnCollisionEnter(Collision collision)
    {
        // 衝突点のワールド座標を取得
        Vector3 collisionPoint = collision.transform.position;

        // 中心点(BoxColliderの中心)のローカル座標をワールド座標に変換
        Vector3 centerInWorldSpace = boxCollider.center + transform.position;

        // 衝突点から中心点までの距離を計算(奥行きは考慮しない)
        float distance3 = Vector2.Distance(collisionPoint, centerInWorldSpace);

        // 結果をデバッグログに表示
        Debug.Log("Target側での距離計測3 : " + distance3);
        Debug.Log("接触点数 : " + collision.contactCount);
    }
}

パターン4

的の中心座標中心点を自作
的に当たったボールの座標Collisionクラスの接触オブジェクトの座標プロパティを使う
using UnityEngine;

public class TargetController : MonoBehaviour
{
    // 的の中心を自由に決める(ゲームオブジェクトをドラッグ&ドロップしますが、型宣言が Transformのため、Transformコンポーネントが代入されます:仕様)
    [SerializeField]
    Transform center;

    private void OnCollisionEnter(Collision collision)
    {
        // 衝突点のワールド座標を取得
        Vector3 collisionPoint = collision.transform.position;

        // 中心点のワールド座標
        Vector3 centerWorldSpace = center.position;

        // 衝突点から中心点までの距離を計算(奥行きは考慮しない)
        float distance4 = Vector2.Distance(collisionPoint, centerWorldSpace);

        // 結果をデバッグログに表示
        Debug.Log("Target側での距離計測4 : " + distance4);
    }
}

実行結果

Target側での距離計測1 : 1.191494
Target側での距離計測2 : 1.191494
Target側での距離計測3 : 1.190701
Target側での距離計測4 : 1.190701
接触点数 : 1

参考

ボール側のスクリプト

using UnityEngine;

public class IgaguriController : MonoBehaviour
{
    public void Shoot(Vector3 dir)
    {
        GetComponent<Rigidbody>().AddForce(dir);
    }

    void OnCollisionEnter(Collision collision)
    {
        if (!collision.gameObject.CompareTag("Target")) return;

        GetComponent<Rigidbody>().isKinematic = true;
        GetComponent<ParticleSystem>().Play();

        // 的の子オブジェクトにする
        transform.parent = collision.transform;

        // 1秒後に消滅
        Destroy(gameObject, 1);
    }

    void Start()
    {
        Application.targetFrameRate = 60;
        // Shoot(new Vector3(0, 200, 2000));
        Vector2 v2 = new Vector3(1, 2, 3);
    }
}

全テストを含めたコード

using UnityEngine;

public class TargetController : MonoBehaviour
{
    // 的の中心を自由に決める
    [SerializeField]
    Transform center;

    // BoxCollider コンポーネントを格納するためのフィールド
    private BoxCollider boxCollider;

    private void Start()
    {
        // ゲームオブジェクトから BoxCollider コンポーネントを取得して保存
        boxCollider = GetComponent<BoxCollider>();
    }

    private void OnCollisionEnter(Collision collision)
    {
        // 衝突点のワールド座標を取得
        Vector3 collisionPoint = collision.contacts[0].point;

        // ローカル座標をワールド座標に変換
        Vector3 centerInWorldSpace = transform.TransformPoint(boxCollider.center);

        // BoxColliderのローカル座標の中心
        Vector3 centerInLocalSpace = boxCollider.center;

        // 衝衝突点から中心までの距離を計算(奥行きは考慮しない)
        float distance1 = Vector2.Distance(collisionPoint, centerInWorldSpace);

        float distance2 = Vector2.Distance(collisionPoint, transform.position + centerInLocalSpace);

        float distance3 = Vector2.Distance(collision.transform.position, transform.position + centerInLocalSpace);

        float distance4 = Vector2.Distance(collision.transform.position, center.position);

        // 結果をデバッグログに表示
        Debug.Log("Target側での距離計測1 : " + distance1);
        Debug.Log("Target側での距離計測2 : " + distance2);
        Debug.Log("Target側での距離計測3 : " + distance3);
        Debug.Log("Target側での距離計測4 : " + distance4);
        Debug.Log("接触点数 : " + collision.contactCount);
    }
}

C#,Unity,学習,設計

Posted by hidepon