Maemaemae

Unityでプレハブをランダムに配置する方法

Unityでプレハブをランダムに配置する方法

プロシージャル生成のゲームやランダムな要素を含むレベルデザインでは、プレハブをランダムに配置する技術が重要になります。この記事では、シンプルなランダム配置から高度なグリッドベースの生成まで、Unityでのプレハブのランダム配置方法を解説します。

基本的なランダム配置スクリプト

RandomPrefabSpawner

以下のスクリプトを使用すると、指定した範囲内に複数のプレハブをランダムに配置できます。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RandomPrefabSpawner : MonoBehaviour
{
    [Header("プレハブ設定")]
    [Tooltip("ランダムに配置するプレハブのリスト")]
    public GameObject[] prefabsToSpawn;

    [Tooltip("各プレハブの出現確率(リスト順に対応)")]
    public float[] spawnProbabilities;

    [Header("生成エリア設定")]
    [Tooltip("X軸の生成範囲(最小値)")]
    public float xMin = -10f;

    [Tooltip("X軸の生成範囲(最大値)")]
    public float xMax = 10f;

    [Tooltip("Z軸の生成範囲(最小値)")]
    public float zMin = -10f;

    [Tooltip("Z軸の生成範囲(最大値)")]
    public float zMax = 10f;

    [Tooltip("Y軸の固定値(0の場合は地面に配置)")]
    public float yPosition = 0f;

    [Header("生成数設定")]
    [Tooltip("生成するオブジェクトの総数")]
    public int numberOfObjectsToSpawn = 20;

    [Header("衝突検出設定")]
    [Tooltip("オブジェクト同士の衝突を避けるか")]
    public bool avoidCollisions = true;

    [Tooltip("衝突検出の半径")]
    public float collisionCheckRadius = 1.0f;

    // 省略(重要な部分のみを表示)

    void Start()
    {
        // スポーン確率の検証
        ValidateSpawnProbabilities();

        // オブジェクトの生成
        SpawnObjects();
    }

    // スポーン確率の検証と正規化
    void ValidateSpawnProbabilities()
    {
        // 省略
    }

    // オブジェクトの生成処理
    void SpawnObjects()
    {
        int successfulSpawns = 0;
        int totalAttempts = 0;

        while (successfulSpawns < numberOfObjectsToSpawn && totalAttempts < numberOfObjectsToSpawn * maxPlacementAttempts)
        {
            // 生成位置の決定
            Vector3 spawnPosition = new Vector3(
                Random.Range(xMin, xMax),
                yPosition,
                Random.Range(zMin, zMax)
            );

            // 省略(重要な部分のみを表示)
        }
    }

    // 位置が既に使用されているかチェック
    bool IsPositionOccupied(Vector3 position)
    {
        // 省略
    }

    // 確率に基づいてプレハブを選択
    GameObject SelectPrefabByProbability()
    {
        // 省略
    }
}

このスクリプトは基本的なランダム配置を提供し、以下の機能を備えています:

グリッドベースの高度な配置方法

GridBasedPrefabSpawner

より高度な制御が必要な場合は、グリッドベースのスポナーが効果的です。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GridBasedPrefabSpawner : MonoBehaviour
{
    [System.Serializable]
    public class PrefabWithWeight
    {
        public GameObject prefab;
        public float weight = 1f;
    }

    [Header("プレハブ設定")]
    [Tooltip("生成するプレハブと重み付けのリスト")]
    public List<PrefabWithWeight> prefabsToSpawn = new List<PrefabWithWeight>();

    [Header("グリッド設定")]
    [Tooltip("グリッドのセルサイズ")]
    public Vector2 cellSize = new Vector2(1f, 1f);

    [Tooltip("グリッドの幅(X方向のセル数)")]
    public int gridWidth = 10;

    [Tooltip("グリッドの高さ(Z方向のセル数)")]
    public int gridHeight = 10;

    [Header("生成設定")]
    [Range(0f, 1f)]
    [Tooltip("セルごとの生成確率")]
    public float fillPercentage = 0.5f;

    [Tooltip("生成パターン")]
    public enum SpawnPattern { CompletelyRandom, Perlin, Clustered }
    public SpawnPattern spawnPattern = SpawnPattern.CompletelyRandom;

    // 省略(重要な部分のみを表示)

    void Start()
    {
        // グリッドの初期化
        occupiedCells = new bool[gridWidth, gridHeight];

        // スポーン処理の実行
        SpawnObjectsOnGrid();
    }

    // グリッド上にオブジェクトを生成
    void SpawnObjectsOnGrid()
    {
        // 省略(重要な部分のみを表示)
    }

    // 重み付けに基づいてプレハブを選択
    GameObject SelectPrefabByWeight()
    {
        // 省略
    }

    // プレハブの再配置(Runtime中に呼び出し可能)
    public void RegenerateObjects()
    {
        // 省略
    }
}

グリッドベースのスポナーの主な特徴:

実践的な使用例

自然物(木や岩)の配置

自然な環境を作成するには、パーリンノイズを使用したランダム配置が効果的です。

// GridBasedPrefabSpawnerの設定例
spawnPattern = SpawnPattern.Perlin;
perlinScale = 0.1f;
fillPercentage = 0.3f;

アイテムや敵のスポーン

ゲームプレイ要素としてのアイテムや敵の配置には、衝突を避けるランダム配置が適しています。

// RandomPrefabSpawnerの設定例
avoidCollisions = true;
collisionCheckRadius = 2.0f;
numberOfObjectsToSpawn = 15;

マップタイルの生成

タイルベースのマップ生成には、グリッドベースの配置が最適です。

// GridBasedPrefabSpawnerの設定例
cellSize = new Vector2(1f, 1f);
gridWidth = 20;
gridHeight = 20;
centerGrid = true;
fillPercentage = 1.0f; // すべてのセルを埋める

高度なカスタマイズ

複数の配置ルールの組み合わせ

より複雑なレベル生成には、複数のスポナーを組み合わせることができます。

// 地形用のグリッドスポナー
terrainSpawner.spawnPattern = SpawnPattern.Perlin;
terrainSpawner.SpawnObjectsOnGrid();

// その後、アイテム用のランダムスポナー
itemSpawner.SpawnObjects();

エディタ拡張機能

エディタ上でスポーン結果をプレビューするには、OnDrawGizmosSelectedメソッドを活用します。

void OnDrawGizmosSelected()
{
    Gizmos.color = Color.green;
    Gizmos.DrawWireCube(
        new Vector3((xMin + xMax) / 2, yPosition, (zMin + zMax) / 2),
        new Vector3(xMax - xMin, 0.1f, zMax - zMin)
    );
}

パフォーマンス最適化

オブジェクトプーリング

大量のオブジェクトを生成する場合、オブジェクトプーリングを活用することで、パフォーマンスを向上させられます。

public class PrefabPool : MonoBehaviour
{
    public GameObject prefab;
    public int poolSize = 50;
    private List<GameObject> pooledObjects = new List<GameObject>();

    void Start()
    {
        // プールの初期化
        for (int i = 0; i < poolSize; i++)
        {
            GameObject obj = Instantiate(prefab, transform);
            obj.SetActive(false);
            pooledObjects.Add(obj);
        }
    }

    public GameObject GetPooledObject()
    {
        // 非アクティブなオブジェクトを検索
        foreach (GameObject obj in pooledObjects)
        {
            if (!obj.activeInHierarchy)
            {
                return obj;
            }
        }

        // プールが不足している場合は新規作成
        GameObject newObj = Instantiate(prefab, transform);
        pooledObjects.Add(newObj);
        return newObj;
    }
}

LODシステムの活用

広大なエリアに多数のオブジェクトを配置する場合は、LOD(Level Of Detail)システムを活用しましょう。

// LODグループの設定
LODGroup lodGroup = spawnedObject.AddComponent<LODGroup>();
LOD[] lods = new LOD[2];

Renderer[] highRenderers = spawnedObject.GetComponentsInChildren<Renderer>();
Renderer[] lowRenderers = lowPolyVersion.GetComponentsInChildren<Renderer>();

lods[0] = new LOD(0.5f, highRenderers);
lods[1] = new LOD(0.1f, lowRenderers);

lodGroup.SetLODs(lods);

よくある問題と解決策

プレハブの一部だけが配置される

問題: 一部のプレハブが配置されない、または偏りがある。

解決策: 確率の正規化を確認し、データ検証を追加します。

void ValidateSpawnProbabilities()
{
    float total = 0;
    foreach (float prob in spawnProbabilities)
    {
        total += prob;
    }

    if (Mathf.Abs(total - 1.0f) > 0.01f)
    {
        Debug.LogWarning("確率の合計が1ではありません。正規化します。");
        for (int i = 0; i < spawnProbabilities.Length; i++)
        {
            spawnProbabilities[i] /= total;
        }
    }
}

地形に対して正しく配置されない

問題: オブジェクトが地形に対して浮いたり埋まったりする。

解決策: レイキャストを使用して地形の高さに合わせます。

if (snapToTerrain)
{
    RaycastHit hit;
    if (Physics.Raycast(
        position + Vector3.up * 100f,
        Vector3.down,
        out hit,
        200f,
        terrainLayerMask))
    {
        position.y = hit.point.y + terrainOffset;
    }
}

参考資料


Unity 2022.3 LTS および C# 8.0 以上を基準としています。

UnityC#プレハブランダム生成レベルデザイン