プロシージャル生成のゲームやランダムな要素を含むレベルデザインでは、プレハブをランダムに配置する技術が重要になります。この記事では、シンプルなランダム配置から高度なグリッドベースの生成まで、Unityでのプレハブのランダム配置方法を解説します。
以下のスクリプトを使用すると、指定した範囲内に複数のプレハブをランダムに配置できます。
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()
{
// 省略
}
}
このスクリプトは基本的なランダム配置を提供し、以下の機能を備えています:
より高度な制御が必要な場合は、グリッドベースのスポナーが効果的です。
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(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 以上を基準としています。