質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.51%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

Q&A

解決済

1回答

2132閲覧

【Unity】自動で迎撃する固定砲台の処理について

shukrin

総合スコア14

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

0グッド

1クリップ

投稿2019/12/29 13:51

前提・実現したいこと

Unityを用いて2Dゲームを作っています。固定砲台の索敵範囲内に敵がいればその中からランダムに1体を選んでそちらを向いて発砲する、ということを実現したいと思っています。

該当のソースコード

まずオブジェクト同士の親子構造として、Turret.csがアタッチされたゲームオブジェクトTurretがあり、その子オブジェクトEnemySearchAreaには索敵用のスクリプトEnemySearchArea.csとサークルコライダーがアタッチされています。

索敵から攻撃までの流れは以下の通りです。
1、EnemySearchAreaのサークルコライダーに敵が接触したら、その敵のゲームオブジェクトをリストに追加
2、EnemySearchAreaのサークルコライダーから敵が出たら、その敵のゲームオブジェクトをリストから削除
3、リストの長さが0でない(=索敵範囲内に敵がいる)場合、その中からランダムに1体を選んで、そちらの方を向いて発砲
4、発砲するとゲームオブジェクトBulletが生成され、敵に当たるとダメージ処理を行う

以下がEnemySearchArea.csとなります。このスクリプトは索敵および索敵範囲内にいる敵リストの保持と、親オブジェクトであるTurretへの回転と発砲の指示を担当しています。

C#

1using System.Collections; 2using System.Collections.Generic; 3using UnityEngine; 4 5public class EnemySearchArea : MonoBehaviour 6{ 7 public bool setTarget; //ターゲットが設定されているか否か 8 private List<GameObject> targetList = new List<GameObject>(); //索敵範囲内にいるターゲットのリスト 9 10 // Start is called before the first frame update 11 void Start() 12 { 13 setTarget = false; 14 } 15 16 // Update is called once per frame 17 void Update() 18 { 19 if (!setTarget && targetList.Count > 0) //ターゲットがセットされておらず、かつ索敵範囲内に敵がいる場合 20 { 21 GameObject target = targetList[Random.Range(0, targetList.Count)]; //ターゲットをランダムに選択 22 setTarget = true; 23 this.transform.parent.GetComponent<Turret>().CoRotate(target); 24 this.transform.parent.GetComponent<Turret>().CoShot(); 25 } 26 } 27 28 private void OnTriggerEnter2D(Collider2D collision) 29 { 30 targetList.Add(collision.gameObject); 31 } 32 33 private void OnTriggerExit2D(Collider2D collision) 34 { 35 targetList.Remove(collision.gameObject); 36 } 37}

EnemySearchAreaからの指示を受けてTurret.csではゲームオブジェクトTurretの回転と発砲(ゲームオブジェクトBulletの生成)を行います。

C#

1using System.Collections; 2using System.Collections.Generic; 3using UnityEngine; 4 5public class Turret : MonoBehaviour 6{ 7 public GameObject bullet; 8 public float coolTime; //発砲間隔 9 public int shotsNum; //連射数 10 public float turningSpeed; //旋回速度 11 12 IEnumerator Shot() //発砲コルーチン 13 { 14 for(int i = 0; i <= shotsNum; i++){ //0.1秒ごとに発射 15 Instantiate(bullet, transform.position, transform.rotation, transform); 16 yield return new WaitForSeconds(0.1f); 17 } 18 yield return new WaitForSeconds(coolTime); //連射後のクールタイム 19 this.transform.GetChild(1).GetComponent<EnemySearchRange>().setTarget = false; //ターゲットを未セットにしてコルーチン終了 20 yield break; 21 } 22 23 IEnumerator Rotate(GameObject target) //targetの方を向くまで一定速度で回転 24 { 25 while (Vector2.Angle(target.transform.position - this.transform.position, transform.up.normalized) > 5f) 26 { 27 this.GetComponent<Rigidbody2D>().angularVelocity = (Vector2.SignedAngle(transform.up.normalized, target.transform.position - this.transform.position) < 0 ? -1f : 1f) * turningSpeed; 28 yield return null; 29 } 30 this.GetComponent<Rigidbody2D>().angularVelocity = 0f; 31 yield break; 32 } 33 34 public void CoShot() //外部からShotコルーチンを呼ぶためのメソッド 35 { 36 StartCoroutine("Shot"); 37 } 38 39 public void CoRotate(GameObject target) //外部からRotateコルーチンを呼ぶためのメソッド 40 { 41 StartCoroutine(Rotate(target)); 42 } 43}

Turretから発射されたゲームオブジェクトBulletは敵と接触するとダメージ処理を行います。ダメージ処理の内容は敵にアタッチされたスクリプトCharacter.csのDamaged()メソッドに書かれています。

C#

1using System.Collections; 2using System.Collections.Generic; 3using UnityEngine; 4 5public class Bullet : MonoBehaviour 6{ 7 public float speed; //弾速 8 public float damage; //1発当たりのダメージ量 9 10 private void OnTriggerEnter2D(Collider2D collision) 11 { 12 string layerName = LayerMask.LayerToName(collision.gameObject.layer); //衝突したオブジェクトのレイヤー名を取得 13 14 if (layerName == "Enemy" || layerName == "Player" ) 15 { 16 collision.GetComponent<Character>().Damaged(damage, GetComponent<Rigidbody2D>().velocity); 17 Destroy(this.gameObject); 18 } 19 } 20} 21

C#

1using System.Collections; 2using System.Collections.Generic; 3using UnityEngine; 4 5public class Character : MonoBehaviour 6{ 7 public float maxHP; 8 private float HP; 9 10 11 public void Damaged(float damage, Vector2 dmgDirection) //ダメージ処理 12 { 13 this.HP = this.HP - damage; 14 15 if (this.HP < 0) 16 { 17 Destroy(gameObject); 18 } 19 20 this.GetComponent<Rigidbody2D>().AddForce(dmgDirection.normalized, ForceMode2D.Impulse); //ノックバック 21 22 } 23 24 // Start is called before the first frame update 25 void Start() 26 { 27 this.HP = maxHP; 28 } 29 30}

発生している問題・エラーメッセージ

実際に上記のコードを実行してみたところEnemySearchAreaの中のCoRotate()にtargetを渡すところで以下のようなエラーが発生しました。

MissingReferenceException: The object of type 'GameObject' has been destroyed but you are still trying to access it. Your script should either check if it is null or you should not destroy the object.

先ほどのEnemyearchArea.csでは索敵範囲内の敵がHP0になって消滅するパターンを考えていなかったため、既にDestroyされた敵をターゲットにしようとしてエラーが発生したようです。

教えていただきたいこと

それぞれのEnemySearchAreaクラスのインスタンスのtargetListからある敵のゲームオブジェクトを一斉に削除するような方法はないでしょうか?もしそのような方法があればCharacter.csのDamaged()メソッドのDestroyの直前に行うことで、上記の問題が解決するのではないかと思っています。
またもし別の解決方法(コードを根本的に変更する必要のあるものでも構いません)があればぜひとも教えていただけないでしょうか。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

GameObjectがいつの間にかいなくなってたはゲーム制作あるあるですね。

基本的にGameObjectをDestroyするときはGameObject自身でDestroyせず、DestroyManagerのようなオブジェクトにDestroyを依頼する形にします。DestroyManagerはフレームの最後(GameObject同士のやり取りがなくなったとき)にまとめてDestroyします。
その場合、EnemySearchAreaはUpdate毎に範囲内のGameObjectを探すようにします。

別の方法としてはGameObjectがDestroyするときにEnemySearchAreaにリストから自分自身を削除するよう依頼するようにします。

投稿2019/12/29 14:32

編集2019/12/29 15:47
tatamyiwathy

総合スコア1039

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

shukrin

2019/12/29 15:35

DestroyManagerにDestroyを依頼したとしても、結局targetListにはDestroyされたゲームオブジェクトが残ったままではないでしょうか?
tatamyiwathy

2019/12/29 15:48

スミマセン。まさにその通りでございます。回答を修正しました。
shukrin

2019/12/30 07:27

何度もすみません。 >別の方法としてはGameObjectがDestroyするときにEnemySearchAreaにリストから自分自身を削除するよう依頼するようにします。 こちらの方法に関してなのですが、すべてののインスタンスのリストから自身を削除するには具体的にどうすればよろしいのでしょうか。
tatamyiwathy

2020/01/01 04:40

ゲームの進行を管理するGameObject(GameMasterとする)が存在すると思いますが、そのGameMasterに配列を持たせて DestroyするGameObjectはGameMasterの配列に自身を追加します。その後、フレームの最後でGameMasterが配列に登録されているGameObjectをDestroyします。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.51%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問