【Unity】オブジェクトの発生場所が被らないようにするには?(Prefabは複数に対応)

2024年4月25日

発生場所が重ならないようにオブジェクトを生成するためには、生成したオブジェクトの位置を記録しておき、新しいオブジェクトを生成する際にその位置を確認する必要があります。以下はそのロジックを組み込んだ修正コードです。

ベースとなるコード

ランダムな場所にプレファブを表示できますが、同じところに表示されることがあります

using System;
using System.Collections;
using System.Collections.Generic;
using System.Net.Mime;
using System.Security.Cryptography;
using UnityEngine;
using Random = UnityEngine.Random;
// アイテムがランダムで出てくるスクリプト(教科書:P220)
// 参考動画:https://youtu.be/DCbv5cyDNgI?si=x8QJhX-tSSxpx8q6
// 参考サイト:https://cbagames.jp/2023/06/01/unityprefabrandom/
public class ObjectGenerator : MonoBehaviour
{
    // 【 プロパティ 】
    [SerializeField] public GameObject[] obj; // オブジェクトを入れる配列
    int num; // 配列番号用
    float span = 1.0f;
    float delta;
    float posX; // 横の位置
    float posY; // 縦の位置
    Vector3 randomPos; // 重ならないように配置する用
    int counter;
    // 当たり判定用
   /* GameObject item = GameObject.obj[num];
    GameObject obstacle = GameObject.FindGameObjectsWithTag("obstacle");*/
    void Update()
    {
        this.delta += Time.deltaTime;
        if (this.delta > this.span)
        {
            this.delta = 0;
            posX = Random.Range(-8, 8);
            posY = Random.Range(-4, 4);
            num = Random.Range(0, obj.Length); // 0~オブジェクト数の範囲で表示
            // どこに配置するか指定
            randomPos = new Vector3(posX, posY, 0);
            // (objを, randomPosの位置に, 角度を変えないで配置)
            Instantiate(obj[num], randomPos, Quaternion.identity).name = obj[num].name + counter++;
        }
        // 生成したアイテムを配列に入れて管理する
        if (Input.GetKeyDown(KeyCode.Space))
        {
            GameObject[] items = GameObject.FindGameObjectsWithTag ("item");
            foreach (var item in items)
            {
                Debug.Log(item.name);
            }
        }
    }
}

生成オブジェクトが被らないようにしたコード

  1. ヘッダの整理
    • 不使用の System, System.Collections, System.Net.Mime, System.Security.Cryptography ヘッダが削除されました。これによりコードがすっきりし、関連性のない名前空間が取り除かれています。
  2. 変数のアクセス修飾子と初期化
    • 変数 spancounterSerializeField および private 修飾子が追加され、counter が初期値 0 で明示的に初期化されています。これにより、Unity エディタから直接編集可能になりつつ、クラス外からの不適切なアクセスが防がれます。
  3. 位置生成のロジックの改善
    • 重複しない位置を生成するための GenerateNonOverlappingPosition メソッドが追加されました。このメソッドは、すでに生成された位置との重複を避けるように設計されており、オブジェクト間の最小距離 radius を保ちます。これにより、オブジェクトの重なりがなく、より視覚的に整理されたシーンが実現します。
  4. 位置を記録するためのリストの追加
    • 新たに positions リストが追加され、生成したオブジェクトの位置が記録されるようになりました。これにより、新しいオブジェクトの位置を決める際に、以前のオブジェクトとの位置関係を考慮できるようになります。
  5. デバッグ用コードの削除
    • オブジェクト生成時のデバッグ出力や、スペースキーを押下してオブジェクトの情報を出力する部分が削除されています。これにより、コードが本番環境に適した形になり、不要なデバッグログが削除されました。

これらの変更により、コードの効率が向上し、オブジェクト生成の精度が高まり、可読性と保守性が改善されています。

using System;
using System.Collections.Generic;
using UnityEngine;
using Random = UnityEngine.Random;

public class ObjectGenerator : MonoBehaviour
{
    [SerializeField] private GameObject[] obj; // 生成するオブジェクトの配列
    [SerializeField] private float span = 1.0f; // 生成する間隔(秒)
    private float delta; // 経過時間を計る変数
    private int counter = 0; // 生成したオブジェクトの数を数えるカウンター
    private List<Vector3> positions = new List<Vector3>(); // 生成したオブジェクトの位置を記録するリスト
    private float radius = 1.0f; // 他のオブジェクトとの最小距離

    void Update()
    {
        this.delta += Time.deltaTime; // 経過時間を更新
        if (this.delta > this.span) // 経過時間が設定した間隔を超えたら
        {
            this.delta = 0; // 経過時間をリセット
            Vector3 randomPos = GenerateNonOverlappingPosition(); // 重複しない位置を生成
            int num = Random.Range(0, obj.Length); // 生成するオブジェクトを配列からランダムに選択
            Instantiate(obj[num], randomPos, Quaternion.identity).name = obj[num].name + counter++; // オブジェクトを生成し、名前にカウンターを追加
        }
    }

    // 重複しない位置を生成するメソッド
    private Vector3 GenerateNonOverlappingPosition()
    {
        bool isOverlapping; // 位置が重複しているかどうかのフラグ
        Vector3 newPosition; // 新しい位置
        do
        {
            isOverlapping = false; // フラグをリセット
            float posX = Random.Range(-8, 8); // X座標をランダムに選択
            float posY = Random.Range(-4, 4); // Y座標をランダムに選択
            newPosition = new Vector3(posX, posY, 0); // 新しい位置を設定

            // すでに生成された位置との重複チェック
            foreach (Vector3 pos in positions)
            {
                if (Vector3.Distance(pos, newPosition) < radius) // 生成済みの位置との距離がradius未満なら
                {
                    isOverlapping = true; // 重複フラグを立てる
                    break; // ループを抜ける
                }
            }
        } while (isOverlapping); // 重複がなくなるまで繰り返し

        positions.Add(newPosition); // 新しい位置をリストに追加
        return newPosition; // 新しい位置を返す
    }
}

考え方

このObjectGeneratorクラスはUnityのゲーム開発環境で使用されるスクリプトで、特定の間隔でランダムに位置にオブジェクトを生成することを目的としています。このプロセスは以下の手順に従います:

アルゴリズムの説明

  1. 初期設定: obj配列には、生成するGameObject(ゲームオブジェクト)のプレハブが保存されています。spanはオブジェクトを生成する時間間隔を定義し、deltaは経過時間を測るために使用します。positionsリストは生成されたオブジェクトの位置を記録しておくためのものです。radiusは新しいオブジェクトが他のオブジェクトとどれだけ離れていなければならないかを定義します。
  2. 時間経過のチェック: Updateメソッドは毎フレーム呼び出され、delta(経過時間)にTime.deltaTime(前フレームからの経過時間)を加算します。このdeltaspanより大きくなると、新しいオブジェクトの生成プロセスがトリガーされます。
  3. 位置の生成: GenerateNonOverlappingPositionメソッドは新しいオブジェクトのための位置をランダムに生成します。この位置は、(-8, 8)の範囲で水平(X座標)と(-4, 4)の範囲で垂直(Y座標)にランダムに選ばれます。新しい位置が生成される度に、既存のpositionsリスト内のすべての位置と比較され、新しい位置がどの位置ともradius以内になければ承認されます。このプロセスは、承認される位置が見つかるまで繰り返されます。
  4. オブジェクトの生成: 承認された位置に、obj配列からランダムに選ばれたオブジェクトのインスタンスが生成されます。このオブジェクトには一意の名前が付けられ(オブジェクト名にカウンターが追加される)、生成回数がカウンターに記録されます。

キーポイント

  • 非重複位置の生成: すでに生成された位置との重複を避けるために、新しい位置が各既存位置から特定のradius以上離れている必要があります。これにより、オブジェクトが重なることなく視覚的に快適な配置が保証されます。
  • 効率性と実行時間: 新しい位置を探すプロセスは、理論上時間がかかる可能性がありますが、通常のゲームプレイ環境では十分な効率性を持っています。ただし、生成するオブジェクトの数や設定されたradiusが大きくなると、適切な位置を見つけるのが困難になる可能性があります。

このアルゴリズムは、ゲーム内でオブジェクトがランダムに、かつ適切な間隔で配置されることを保証するために重要です。これにより、ゲームのプレイエリア内でのオブジェクトの分布が自然に見え、ユーザー体験が向上します。

追記)他のスクリプトでDestroyされることへの対応

ゲームオブジェクトが他のスクリプトによってデストロイされたときに正確に対応するためには、単に位置をリストに保持するのではなく、生成されたゲームオブジェクト自体の参照を追跡する必要があります。このようにすることで、そのオブジェクトがまだアクティブであるか、またはデストロイされたかを直接確認できます。以下に、そのための改良されたコードを示します。

  1. ヘッダの整理
    • 不使用の System, System.Collections, System.Net.Mime, System.Security.Cryptography ヘッダが削除されました。これによりコードがすっきりし、関連性のない名前空間が取り除かれています。
  2. 変数のアクセス修飾子と初期化
    • 変数 spancounterSerializeField および private 修飾子が追加され、counter が初期値 0 で明示的に初期化されています。これにより、Unity エディタから直接編集可能になりつつ、クラス外からの不適切なアクセスが防がれます。
  3. 位置生成のロジックの改善
    • 重複しない位置を生成するための GenerateNonOverlappingPosition メソッドが追加されました。このメソッドは、すでに生成された位置との重複を避けるように設計されており、オブジェクト間の最小距離 radius を保ちます。これにより、オブジェクトの重なりがなく、より視覚的に整理されたシーンが実現します。
  4. 位置を記録するためのリストの追加
    • 新たに positions リストが追加され、生成したオブジェクトの位置が記録されるようになりました。これにより、新しいオブジェクトの位置を決める際に、以前のオブジェクトとの位置関係を考慮できるようになります。
  5. デバッグ用コードの削除
    • オブジェクト生成時のデバッグ出力や、スペースキーを押下してオブジェクトの情報を出力する部分が削除されています。これにより、コードが本番環境に適した形になり、不要なデバッグログが削除されました。

これらの変更により、コードの効率が向上し、オブジェクト生成の精度が高まり、可読性と保守性が改善されています。

using System.Collections.Generic;
using UnityEngine;
using Random = UnityEngine.Random;

public class ObjectGenerator : MonoBehaviour
{
    [SerializeField] private GameObject[] obj; // 生成するオブジェクトの配列
    [SerializeField] private float span = 1.0f; // 生成する間隔(秒)
    private float delta; // 経過時間を計る変数
    private int counter = 0; // 生成したオブジェクトの数を数えるカウンター
    private List<GameObject> activeObjects = new List<GameObject>(); // アクティブなオブジェクトのリスト
    private float radius = 1.0f; // 他のオブジェクトとの最小距離

    void Update()
    {
        this.delta += Time.deltaTime; // 経過時間を更新
        if (this.delta > this.span) // 経過時間が設定した間隔を超えたら
        {
            this.delta = 0; // 経過時間をリセット
            Vector3 randomPos = GenerateNonOverlappingPosition(); // 重複しない位置を生成
            int num = Random.Range(0, obj.Length); // 生成するオブジェクトを配列からランダムに選択
            GameObject createdObj = Instantiate(obj[num], randomPos, Quaternion.identity); // オブジェクトを生成
            createdObj.name = obj[num].name + counter++; // 名前にカウンターを追加
            activeObjects.Add(createdObj); // リストにオブジェクトを追加
        }
    }

    private Vector3 GenerateNonOverlappingPosition()
    {
        activeObjects.RemoveAll(obj => obj == null);  // デストロイされたオブジェクトをリストから除去

        bool isOverlapping; // 位置が重複しているかどうかのフラグ
        Vector3 newPosition; // 新しい位置
        do
        {
            isOverlapping = false; // フラグをリセット
            float posX = Random.Range(-8, 8); // X座標をランダムに選択
            float posY = Random.Range(-4, 4); // Y座標をランダムに選択
            newPosition = new Vector3(posX, posY, 0); // 新しい位置を設定

            // アクティブなオブジェクトの位置との重複チェック
            foreach (GameObject activeObj in activeObjects)
            {
                if (Vector3.Distance(activeObj.transform.position, newPosition) < radius) // アクティブなオブジェクトとの距離がradius未満なら
                {
                    isOverlapping = true; // 重複フラグを立てる
                    break; // ループを抜ける
                }
            }
        } while (isOverlapping); // 重複がなくなるまで繰り返し

        return newPosition; // 新しい位置を返す
    }
}

この元のコードからの変更点は、生成されたオブジェクトの管理方法に関するものです。元のコードでは生成された各オブジェクトの位置をpositionsというリストに保持していますが、このリストには位置情報のみが保存されており、オブジェクトが後にデストロイされた場合でもその位置情報がリストに残り続けます。これにより、実際には存在しないオブジェクトの位置に基づいて新しいオブジェクトの生成位置が決定される可能性があり、非効率または誤った位置計算を引き起こすリスクがあります。

変更されたコードの説明

変更されたコードでは、positionsリストをactiveObjectsという新しいリストに置き換えています。このactiveObjectsリストは、生成されたGameObject自体の参照を保持することで、各オブジェクトがまだゲーム内に存在しているかどうかをリアルタイムで確認することを可能にします。

List<GameObject> activeObjects = new List<GameObject>(); // アクティブなオブジェクトのリスト

生成されたオブジェクトは、Instantiateメソッドによって作成された後、このリストに追加されます。そして、新しいオブジェクトの位置を決定する際には、リスト内の各オブジェクトがまだ有効かどうかをチェックし、無効なオブジェクト(null参照)はリストから削除します。

activeObjects.RemoveAll(obj => obj == null);  // デストロイされたオブジェクトをリストから除去

この方法により、常にアクティブなオブジェクトのみが位置の重複チェックに使用され、生成されるオブジェクトの位置決定の正確性が向上します。デストロイされたオブジェクトによる影響を排除することで、システム全体のパフォーマンスも改善される可能性があります。

重複チェックの改良

重複チェックでは、activeObjectsリスト内の各オブジェクトの位置を新しいオブジェクトの予定位置と比較します。この距離が設定されたradius未満であれば、その位置は使用されず、新しいランダム位置が選ばれます。

foreach (GameObject activeObj in activeObjects) { if(Vector3.Distance(activeObj.transform.position, newPosition) < radius) { isOverlapping = true; break; } }

このアプローチにより、新しいオブジェクトは既存のオブジェクトと適切な距離を保って生成されるため、ゲームのプレイエリアが適切に利用され、ユーザー体験が向上します。

特定(複数のタグ)を使って実現する方法

特定のタグを持つ複数のオブジェクトの位置を確認し、新しいオブジェクトの生成位置がこれらと重ならないようにするには、GameObject.FindGameObjectsWithTag メソッドを使うことが適切です。ここでは、positionsリストを使わずに、特定のタグを持つオブジェクト群から位置を取得して重複をチェックする方法に改修します。以下のコードでは、複数のタグに対応するために、タグのリストを用意して、それぞれのタグについて検索と重複チェックを行います。

  1. 不要なライブラリの削除: 変更後のコードでは System, System.Collections, System.Net.Mime, System.Security.Cryptography などの不要なライブラリのインクルードが削除されています。これにより、コードがよりシンプルで特定の目的に特化されています。
  2. プライバシーとアクセス修飾子の改善: オブジェクト配列 obj と生成間隔 spanprivate に設定され、直接の外部からのアクセスを制限しつつ、必要な場合には SerializeField 属性を使ってエディタから設定できるようになっています。
  3. 生成位置の重複チェックの追加: 新しいコードでは、生成されるオブジェクトが他のオブジェクトと重複しないように、GenerateNonOverlappingPosition メソッドを導入しています。このメソッドはランダムな位置を生成し、指定されたタグ (tagsToCheck) を持つすべてのオブジェクトとの距離をチェックし、必要に応じて再試行します。
  4. 変数の初期化とカウンター: counter 変数が宣言と同時に0で初期化されています。変更前のコードでは初期化が明示的に行われていませんでした。
  5. コードの構造とクリーンアップ: コメントや不要なコード(コメントアウトされた当たり判定用のコードなど)が削除され、全体的にクリーンでメンテナンスしやすいコードになっています。
  6. デバッグとテスト機能の削除: KeyCode.Space を押したときに全アイテムをログに記録する機能が削除されています。これはデバッグやテスト段階で役立つものの、最終的な実装では必要ないかもしれません。

以上の変更により、コードはより効率的で読みやすく、また具体的な機能に特化しています。

using System.Collections.Generic;
using UnityEngine;
using Random = UnityEngine.Random;

public class ObjectGenerator : MonoBehaviour
{
    [SerializeField] private GameObject[] obj; // 生成するオブジェクトの配列
    [SerializeField] private float span = 1.0f; // 生成する間隔(秒)
    private float delta; // 経過時間を計る変数
    private int counter = 0; // 生成したオブジェクトの数を数えるカウンター
    private float radius = 1.0f; // 他のオブジェクトとの最小距離
    [SerializeField] private List<string> tagsToCheck; // 位置の重複チェックに使用するタグのリスト

    void Update()
    {
        this.delta += Time.deltaTime;
        if (this.delta > this.span)
        {
            this.delta = 0;
            Vector3 randomPos = GenerateNonOverlappingPosition();
            int num = Random.Range(0, obj.Length);
            Instantiate(obj[num], randomPos, Quaternion.identity).name = obj[num].name + counter++;
        }
    }

    private Vector3 GenerateNonOverlappingPosition()
    {
        bool isOverlapping;
        Vector3 newPosition;
        do
        {
            isOverlapping = false;
            float posX = Random.Range(-8, 8);
            float posY = Random.Range(-4, 4);
            newPosition = new Vector3(posX, posY, 0);

            // 特定のタグを持つすべてのオブジェクトの位置をチェック
            foreach (string tag in tagsToCheck)
            {
                GameObject[] taggedObjects = GameObject.FindGameObjectsWithTag(tag);
                foreach (GameObject obj in taggedObjects)
                {
                    if (Vector3.Distance(obj.transform.position, newPosition) < radius)
                    {
                        isOverlapping = true;
                        break;
                    }
                }
                if (isOverlapping) break;
            }
        } while (isOverlapping);

        return newPosition;
    }
}

コードの説明

  1. タグのリスト: tagsToCheck はインスペクターから設定できるようにしてあり、重複チェックを行うタグを複数指定できます。
  2. オブジェクト位置の取得: 各タグに対して GameObject.FindGameObjectsWithTag を呼び出し、そのタグが付いているすべてのオブジェクトを取得します。
  3. 重複チェック: 取得したオブジェクトのそれぞれについて、新しい位置との距離を測定し、設定した radius 以内にある場合は重複とみなします。

この方法により、特定のタグを持つ既存のオブジェクトとの位置が重複しないように新しいオブジェクトの生成位置を決定できます。タグに基づく動的なチェックを行うため、システムの柔軟性が向上します。

トリガーを使う当たり判定で実現する方法

トリガー判定を使用して位置の重複をチェックするために、Unityのコライダーシステムを活用する必要があります。これを実現するために、Physics.OverlapSphere メソッドを使用して指定した位置に他のオブジェクトが存在するかを判断します。このメソッドは指定された位置に仮想の球を生成し、その球と交差するすべてのコライダーを検出します。

以下に、tagsToCheckリストを使わずに、トリガーを使って重複チェックを行うように修正したコードを示します。このコードでは、コライダーを持つすべてのオブジェクトをチェックし、新しいオブジェクトの生成位置が重複しないようにします。

  1. ヘッダーの整理:
    • System, System.Collections, System.Net.Mime, System.Security.Cryptography の使用がなくなりました。これにより、コードがよりクリーンになり、関連性のないライブラリの参照が減少しました。
  2. アクセス修飾子の変更:
    • obj 配列が public から private に変更され、外部からの直接アクセスを制限し、カプセル化を強化しました。
  3. 変数の初期化:
    • counter 変数が宣言時に 0 で初期化されるようになりました。
  4. 位置生成ロジックの改善:
    • posXposY を直接計算するのではなく、GenerateNonOverlappingPosition という新しいメソッドを通じて重複しない位置を生成します。このメソッドは物理的な重複(他のオブジェクトとの衝突)をチェックして、必要に応じて位置を再計算します。
  5. コメントの変更と削除:
    • 全体的にコメントが更新され、より直接的で理解しやすい説明になっています。また、不要なコメントやコード(例えば、コメントアウトされていた GameObject itemGameObject obstacle)が削除されました。
  6. 生成ロジックの変更:
    • Instantiate の呼び出しで、Vector3 ではなく Vector2 が使用されるようになり、これにより2Dゲームの設計に適した形式になっています。また、生成されるオブジェクトの名前にカウンターが付けられている点も変わりませんが、位置計算方法が変更されています。
  7. radius 変数の導入:
    • 新しい radius 変数が導入され、生成位置の計算において他のオブジェクトとの最小距離を考慮するようになりました。

これらの変更により、コードはより効率的かつ保守しやすい構造になっています。特に、オブジェクト生成位置の重複チェック機能は、ゲームプレイにおけるエラーや予期せぬ動作を防ぐのに役立ちます。

using System.Collections.Generic;
using UnityEngine;
using Random = UnityEngine.Random;

public class ObjectGenerator : MonoBehaviour
{
    [SerializeField] private GameObject[] obj; // 生成するオブジェクトの配列
    [SerializeField] private float span = 1.0f; // 生成する間隔(秒)
    private float delta; // 経過時間を計る変数
    private int counter = 0; // 生成したオブジェクトの数を数えるカウンター
    private float radius = 1.0f; // 他のオブジェクトとの最小距離

    void Update()
    {
        this.delta += Time.deltaTime; // 経過時間を加算
        if (this.delta > this.span) // 設定した間隔を超えた場合
        {
            this.delta = 0; // 経過時間をリセット
            Vector2 randomPos = GenerateNonOverlappingPosition(); // 重複しない位置を生成
            int num = Random.Range(0, obj.Length); // 配列からランダムにオブジェクトを選択
            Instantiate(obj[num], randomPos, Quaternion.identity).name = obj[num].name + counter++; // オブジェクトを生成し、名前にカウンターを追加
        }
    }

    private Vector2 GenerateNonOverlappingPosition()
    {
        bool isOverlapping; // 位置が重複しているかどうかのフラグ
        Vector2 newPosition; // 新しい位置
        do
        {
            isOverlapping = false; // 重複フラグをリセット
            float posX = Random.Range(-8, 8); // X座標をランダムに選択
            float posY = Random.Range(-4, 4); // Y座標をランダムに選択
            newPosition = new Vector2(posX, posY); // 新しい位置を設定

            // 指定位置にあるコライダーを検出
            Collider2D[] hitColliders = Physics2D.OverlapCircleAll(newPosition, radius);
            if (hitColliders.Length > 0) // コライダーが1つでも存在する場合
            {
                isOverlapping = true; // 位置が重複していると判断
            }
        } while (isOverlapping); // 重複が解消されるまで繰り返し

        return newPosition; // 重複しない位置を返す
    }
}

主な変更点

  1. トリガー判定の利用:
    • Physics2D.OverlapCircleAllメソッドを用いて、新しい位置に仮想の球円を生成し、その球と交差するコライダーがあるかどうかを調べます。このメソッドは、指定した半径内にあるすべてのコライダーを配列として返します。
  2. タグのリストの除去:
    • 元のコードで使用されていた tagsToCheck リストを使用せず、すべてのコライダーを対象にチェックします。これにより、特定のタグに依存することなく、位置の重複をより一般的に判定することが可能です。

このコードは、生成しようとする位置に他のオブジェクトが存在しないことを保証するために、物理的な交差判定を使用します。これにより、ゲーム内でのオブジェクトの配置が自然で、プレイエリアを最適に活用することができます。

上記コードのリファクタリング

  1. 変数名の改善と整理:
    • objobjects: 変数名が具体的かつ明確になりました。
    • spanspawnInterval: 何の間隔かをより明確に表しています。
    • deltaelapsedTime: 何に対する時間かがより明確になっています。
    • counterobjectCount: 生成されたオブジェクトの数を表す変数名がより分かりやすくなりました。
    • radiusminDistance: 他のオブジェクトとの距離を表す変数名が明確になりました。
  2. 関数のロジックと構造の改善:
    • GenerateNonOverlappingPosition()GenerateUniquePosition()と改名し、リファクタリングされました。新しい関数は位置を生成するために一定の試行回数(最大100回)でループを行い、有効な位置が見つかればそれを返します。適切な位置が見つからなければVector2.zeroを返す点が新しいエラーハンドリング戦略です。
    • IsPositionValid()という新しい関数が追加され、位置が有効かどうかを判断します。これにより、位置生成の詳細がより明確になり、読みやすくなっています。
  3. オブジェクトの生成を担当する新しい関数:
    • CreateObjectAtPosition(Vector2 position)という新しい関数が追加され、オブジェクトの生成プロセスが一箇所にまとめられました。これにより、Update()メソッドがよりシンプルで読みやすくなっています。
  4. 更新と改善されたエラーハンドリング:
    • 位置生成で適切な場所が見つからなかった場合にVector2.zeroを返すようになり、エラー時の挙動が明確に定義されています。これにより、不正な位置でのオブジェクト生成を防ぐことが可能になります。

これらの変更により、コードの可読性、保守性、および機能性が向上しています。

using UnityEngine;

public class ObjectGenerator : MonoBehaviour
{
    [SerializeField] private GameObject[] objects; // 生成するオブジェクトの配列
    [SerializeField] private float spawnInterval = 1.0f; // オブジェクトを生成する時間間隔(秒)
    private float elapsedTime = 0f; // タイマーで使用する経過時間
    private int objectCount = 0; // 生成したオブジェクトの総数
    private float minDistance = 1.0f; // 他のオブジェクトとの最小距離(重複回避のため)

    void Update()
    {
        elapsedTime += Time.deltaTime; // 毎フレームの経過時間を加算
        if (elapsedTime >= spawnInterval) // 指定された時間間隔が経過したかチェック
        {
            elapsedTime = 0f; // タイマーをリセット
            Vector2 position = GenerateUniquePosition(); // 重複しない位置を生成
            if (position != Vector2.zero) // 有効な位置が見つかった場合
            {
                CreateObjectAtPosition(position); // その位置にオブジェクトを生成
            }
        }
    }

    private Vector2 GenerateUniquePosition()
    {
        int maxAttempts = 100; // 位置の生成を試みる最大回数
        for (int i = 0; i < maxAttempts; i++)
        {
            Vector2 potentialPosition = new Vector2(Random.Range(-8, 8), Random.Range(-4, 4)); // 新しい位置をランダムに生成
            if (IsPositionValid(potentialPosition)) // 生成した位置が有効かどうかチェック
            {
                return potentialPosition; // 有効な位置を返す
            }
        }
        return Vector2.zero; // 適切な位置が見つからなければVector2.zeroを返す(エラーハンドリング)
    }

    private bool IsPositionValid(Vector2 position)
    {
        Collider2D[] hitColliders = Physics2D.OverlapCircleAll(position, minDistance); // 指定位置でコライダーの重複をチェック
        return hitColliders.Length == 0; // 重複がなければtrue(位置が有効)
    }

    private void CreateObjectAtPosition(Vector2 position)
    {
        int index = Random.Range(0, objects.Length); // 配列からランダムにオブジェクトを選択
        GameObject newObj = Instantiate(objects[index], position, Quaternion.identity); // オブジェクトをインスタンス化し、生成
        newObj.name = objects[index].name + objectCount++; // オブジェクトに一意の名前を付ける(管理のため)
    }
}

オブジェクト生成スクリプトの要素

  1. オブジェクトの配列: ゲームで生成したいオブジェクトを配列に入れます。配列とは、複数のアイテムを一つのリストにまとめることです。これにより、異なる種類のオブジェクトを一から選んで生成することが可能になります。
  2. 時間間隔 (span): オブジェクトが生成される間隔を秒単位で設定します。例えば、1.0f は1秒ごとにオブジェクトを生成するという意味です。
  3. 経過時間 (delta): これは、最後にオブジェクトが生成されてからの時間を測るための変数です。この時間が設定した間隔に達すると、新しいオブジェクトが生成されます。
  4. カウンター (counter): これは、生成されたオブジェクトの総数を数えるための変数です。これにより、各オブジェクトに一意の名前を付けることができます。
  5. 最小距離 (radius): 新しく生成するオブジェクトが、既に存在するオブジェクトから一定の距離以上離れていることを保証します。これにより、オブジェクトが重なり合わないようにします。

スクリプトの動作プロセス

  1. 時間のチェック: Update メソッドが毎フレーム実行され、delta(経過時間)が増加します。この経過時間が設定したspan(生成間隔)よりも大きくなると、新しいオブジェクトの生成プロセスが始まります。
  2. 位置の生成: GenerateNonOverlappingPosition メソッドは、他のオブジェクトと重ならない新しい位置を見つけるために使用されます。ランダムな位置を選び、その位置が他のオブジェクトから十分な距離にあるかを確認します。
  3. オブジェクトの生成: 位置が決まれば、配列からランダムにオブジェクトを選択し、その位置にインスタンス化(ゲームの世界に実際にオブジェクトを作成するプロセス)します。そして、そのオブジェクトには連番の名前が付けられます。

このプロセスにより、ゲーム内でオブジェクトが自動的に生成され、プレイヤーに新しい体験を提供することができます。これがUnityでのオブジェクト生成の基本的な流れです。

Unity

Posted by hidepon