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);
}