【解決】衝突相手(Colliderが子)に衝突元の変数値を伝える

OnTrigger○○系で、OnCollision〇〇系の手法を使い、衝突相手(Colliderが子の位置)のスクリプトを取得しようとすると、
「NullReferenceException: Object reference not set to an instance of an object」が発生。
(衝突相手にスクリプトが付いているのに、「参照がない」というエラー)。

【結論】GetComponentを使用する先を変更

GetComponentに失敗して取得出来ていないので、使用する対象を変更する。

「OnTriggerEnter」時のコード例

この問題は、特に「OnTrigger○○系」で発生し易い。
(取得されるColliderが子の位置にある為)。


//衝突元側の記述。
//この例では、EnemyBulletのようなプレイヤーにダメージを与えるオブジェクトを想定。

    void OnTriggerEnter (Collider coll) {
    	if (coll.gameObject.CompareTag("Enemy")) {

//親オブジェクトにRigidbodyが付いている場合。
		coll.attachedRigidbody.GetComponent<Enemy>().Damaged(power);		


//以下の2例なら親オブジェクトにRigidbodyが付いていない場合でも取得出来る。
//親オブジェクトが1つもないと「NullReferenceException」エラーを吐くので注意。
//Colliderが1つ下の階層の場合(親のTransformを取得)。
    		coll.transform.parent.GetComponent<Enemy>().Damaged(power);

//Colliderが深い階層にある場合(一番上のTransformを取得)。
    		coll.transform.root.GetComponent<Enemy>().Damaged(power);

    	}
    }

一応「OnCollisionEnter」時のコード例

Rigidbodyが子の位置にある(変な)構造の場合は「OnCollision○○系」でも発生する。


//衝突元側の記述。
//この例では、EnemyBulletのようなプレイヤーにダメージを与えるオブジェクトを想定。

    void OnCollisionEnter (Collision coll) {
    	if (coll.gameObject.CompareTag("Enemy")) {

//親オブジェクトが1つもないと「NullReferenceException」エラーを吐くので注意。
//最上位と1つ上の間が必要という特殊な状況ならば「coll.transform.parent.parent」等と必要な数だけ繰り返す。
    		coll.transform.parent.GetComponent<Enemy>().Damaged(power);

    		coll.transform.root.GetComponent<Enemy>().Damaged(power);

    	}
    }    

【理由】「親オブジェクトにスクリプト、子に3Dモデル(Collider付き)」という構造だと発生

OnCollisionEnter等で扱うように「collider.gameObject.GetComponent」と書いた場合、
3Dモデル(Collider付き)のGameObjectが取得される。

親オブジェクトへの参照になっていないので、親オブジェクトに付いているスクリプトも当然、取得出来ない。

【結論2】「シーンに単一の物 x 複数の物」の組み合わせなら、GameManagerで処理という手もある

前述の「プレイヤーの弾丸 x 敵」という組み合わせでは微妙だが、「敵の弾丸 x プレイヤー」という組み合わせなら、GameManagerを介する事で、上手く処理する事が出来る。

敵の弾丸にGameManagerへの参照を渡す

参照を渡す方法は色々あるが、以下の2つの内のどちらかを使用するのがオススメ。

【Aパターン】敵の弾丸の初期化時にFindWithTagでGameManagerを探しておく

GameManagerに「GameManager」タグを設定してから、敵の弾丸のスクリプト内に以下を追加。
(注:オブジェクトプールにしていなければ、弾数によっては負荷が大きくなるかも知れない)。


    GameManager gameManager;

    void Start ()
    {
        gameManager = GameObject.FindWithTag("GameManager").GetComponent<GameManager>();
    }

【Bパターン】GameManagerを適当にシングルトン化する

オブジェクトプールにしていなくても負荷にならない。

GameManagerを介して「敵の弾丸」の変数値を渡す

GameManagerを介してプレイヤーのメソッドを呼ぶ。
もしくは、GameManagerに処理用のメソッドを作っておいて、それを呼ぶ。

【Aパターン】GameManagerを介して、直接プレイヤーのメソッドを呼ぶ

あらかじめ、GameManagerのインスペクターで、プレイヤーの参照を紐付けしておく。


public class GameManager : MonoBehaviour
{

	public Player player;

}

public class Player : MonoBehaviour
{

	float hp = 100;
//適用にダメージ処理。
	public void Damaged (damage) {
		hp -= damage;
	}

}

public class EnemyBullet : MonoBehaviour
{

	float power = 10;

//FindWithTagで取得した場合。
	gameManager.player.Damaged(power);

//シングルトンの場合。
	GameManager.instance.player.Damaged(power);

}

【Bパターン】GameManagerのメソッドを一旦噛まして、プレイヤーのメソッドを呼ぶ

自分で把握しているなら、どちらでも良いと思いますが、プレイヤーの参照を剥き出しにしているより、こちらの方が御行儀が良いでしょう。


public class GameManager : MonoBehaviour
{

	[SerializeField]
		Player player;

	public void DamagedPlayer (float damage) {
    		player.Damaged(damage);
	}

}

public class Player : MonoBehaviour
{

	float hp = 100;

	public void Damaged (damage) {
		hp -= damage;
	}

}

public class EnemyBullet : MonoBehaviour
{

	float power = 10;

//FindWithTagで取得した場合。
	gameManager.DamagedPlayer(power);

//シングルトンの場合。
	GameManager.instance.DamagedPlayer(power);

}

タイトルとURLをコピーしました