Unity での手続き型ワールド生成

Unity のワールド生成とは、Unity ゲーム エンジン内で仮想世界、地形、風景、または環境を作成または手続き的に生成するプロセスを指します。この手法は、オープンワールド ゲーム、RPG、シミュレーションなどのさまざまな種類のゲームで広く使用されており、広大で多様なゲーム世界を動的に作成します。

Unity は、これらのワールド生成技術を実装するための柔軟なフレームワークと幅広いツールと API を提供します。C# を使用してカスタム スクリプトを作成してゲーム ワールドを生成および操作したり、地形システム、ノイズ関数、スクリプト インターフェイスなどの Unity 組み込み機能を利用して目的の結果を達成したりできます。さらに、ワールド生成タスクを支援できるサードパーティのアセットや Unity Asset Store で利用可能な プラグイン もあります。

Unity でのワールド生成にはいくつかのアプローチがあり、選択はゲームの特定の要件によって異なります。一般的に使用されるいくつかの方法を次に示します。

  • パーリン ノイズを使用した手続き型地形生成
  • セルラーオートマトン
  • ボロノイ図
  • 手続き型オブジェクトの配置

パーリン ノイズを使用した手続き型地形生成

Unity での手続き型地形生成は、さまざまなアルゴリズムと技術を使用して実現できます。一般的なアプローチの 1 つは、パーリン ノイズを使用して高さマップを生成し、その後、さまざまなテクスチャリングや葉のテクニックを適用して、現実的または様式化された地形を作成することです。

パーリン ノイズは、Ken Perlin によって開発されたグラディエント ノイズの一種です。ランダムに見えても一貫した構造を持つ、滑らかで連続的な値のパターンを生成します。パーリン ノイズは、自然に見える地形、雲、テクスチャ、その他の有機的な形状を作成するために広く使用されています。

Unity では、関数 'Mathf.PerlinNoise()' を使用してパーリン ノイズを生成できます。2 つの座標を入力として受け取り、0 と 1 の間の値を返します。さまざまな周波数と振幅でパーリン ノイズをサンプリングすることにより、プロシージャ コンテンツにさまざまなレベルの詳細と複雑さを作成することができます。

これを Unity で実装する方法の例を次に示します。

  • Unity エディタで、"GameObject -> 3D Object -> Terrain" に移動します。これにより、シーン内にデフォルトの地形が作成されます。
  • という "TerrainGenerator" という名前の新しい C# スクリプトを作成し、それを地形オブジェクトに アタッチします。以下は、パーリン ノイズを使用して手続き型地形を生成するスクリプトの例です。
using UnityEngine;

public class TerrainGenerator : MonoBehaviour
{
    public int width = 512;       // Width of the terrain
    public int height = 512;      // Height of the terrain
    public float scale = 10f;     // Scale of the terrain
    public float offsetX = 100f;  // X offset for noise
    public float offsetY = 100f;  // Y offset for noise
    public float noiseIntensity = 0.1f; //Intensity of the noise

    private void Start()
    {
        Terrain terrain = GetComponent<Terrain>();

        // Create a new instance of TerrainData
        TerrainData terrainData = new TerrainData();

        // Set the heightmap resolution and size of the TerrainData
        terrainData.heightmapResolution = width;
        terrainData.size = new Vector3(width, 600, height);

        // Generate the terrain heights
        float[,] heights = GenerateHeights();
        terrainData.SetHeights(0, 0, heights);

        // Assign the TerrainData to the Terrain component
        terrain.terrainData = terrainData;
    }

    private float[,] GenerateHeights()
    {
        float[,] heights = new float[width, height];

        for (int x = 0; x < width; x++)
        {
            for (int y = 0; y < height; y++)
            {
                // Generate Perlin noise value for current position
                float xCoord = (float)x / width * scale + offsetX;
                float yCoord = (float)y / height * scale + offsetY;
                float noiseValue = Mathf.PerlinNoise(xCoord, yCoord);

                // Set terrain height based on noise value
                heights[x, y] = noiseValue * noiseIntensity;
            }
        }

        return heights;
    }
}
  • Unity エディタの Terrain オブジェクトに と "TerrainGenerator" スクリプトをアタッチします。
  • 地形オブジェクトのインスペクター ウィンドウで、幅、高さ、スケール、オフセット、およびノイズ強度を調整して、生成された地形の外観を微調整します。
  • Unity エディターの Play ボタンを押すと、パーリン ノイズ アルゴリズムに基づいて手続き型地形が生成されます。

Perlin ノイズを使用した Unity Terrain の生成。

注: このスクリプトは、パーリン ノイズを使用して基本的な地形高さマップを生成します。より複雑な地形を作成するには、スクリプトを変更して追加のノイズ アルゴリズムを組み込んだり、侵食またはスムージング技術を適用したり、テクスチャリングを追加したり、地形の特徴に基づいて葉やオブジェクトを配置したりします。

セルラーオートマトン

セル オートマトンはセルのグリッドで構成される計算モデルであり、各セルは事前定義されたルールのセットと隣接するセルの状態に基づいて進化します。これは、コンピューターサイエンス、数学、物理学などのさまざまな分野で使用される強力な概念です。セル オートマトンは、単純なルールから生まれる複雑な動作パターンを示すことができるため、自然現象のシミュレーションや手続き型コンテンツの生成に役立ちます。

セル オートマトンの背後にある基本理論には次の要素が含まれます。

  1. グリッド: グリッドは、正方格子や六角格子などの規則的なパターンで配置されたセルの集合です。各セルは有限数の状態を持つことができます。
  2. 隣接セル: 各セルには隣接セルがあり、通常はそのすぐ隣のセルです。近傍は、フォン ノイマン近傍 (上、下、左、右) またはムーア近傍 (対角線を含む) など、さまざまな接続パターンに基づいて定義できます。
  3. ルール: 各セルの動作は、現在の状態と隣接するセルの状態に基づいてセルがどのように進化するかを指定する一連のルールによって決定されます。これらのルールは通常、条件ステートメントまたはルックアップ テーブルを使用して定義されます。
  4. Update: セルオートマトンは、ルールに従って各セルの状態を同時に更新することで進化します。このプロセスが繰り返し繰り返され、一連の世代が作成されます。

セル オートマトンには、次のようなさまざまな現実世界のアプリケーションがあります。

  1. 自然現象のシミュレーション: セル オートマトンは、流体力学、森林火災、交通の流れ、人口動態などの物理システムの動作をシミュレートできます。適切なルールを定義することで、セル オートマトンは現実世界のシステムで観察される新たなパターンやダイナミクスを捉えることができます。
  2. 手続き型コンテンツ生成: セルラー オートマトンを使用して、ゲームやシミュレーションで手続き型コンテンツを生成できます。たとえば、地形、洞窟システム、植生分布、その他の有機構造を作成するために使用できます。細胞の成長と相互作用を制御するルールを指定することで、複雑で現実的な環境を生成できます。

以下は、ライフ ゲームをシミュレートするために Unity に基本的なセル オートマトンを実装する簡単な例です。

using UnityEngine;

public class CellularAutomaton : MonoBehaviour
{
    public int width = 50;
    public int height = 50;
    public float cellSize = 1f;
    public float updateInterval = 0.1f;
    public Renderer cellPrefab;

    private bool[,] grid;
    private Renderer[,] cells;
    private float timer = 0f;
    private bool[,] newGrid;

    private void Start()
    {
        InitializeGrid();
        CreateCells();
    }

    private void Update()
    {
        timer += Time.deltaTime;

        if (timer >= updateInterval)
        {
            UpdateGrid();
            UpdateCells();
            timer = 0f;
        }
    }

    private void InitializeGrid()
    {
        grid = new bool[width, height];
        newGrid = new bool[width, height];

        // Initialize the grid randomly
        for (int x = 0; x < width; x++)
        {
            for (int y = 0; y < height; y++)
            {
                grid[x, y] = Random.value < 0.5f;
            }
        }
    }

    private void CreateCells()
    {
        cells = new Renderer[width, height];

        // Create a GameObject for each cell in the grid
        for (int x = 0; x < width; x++)
        {
            for (int y = 0; y < height; y++)
            {
                Vector3 position = new Vector3(x * cellSize, 0f, y * cellSize);
                Renderer cell = Instantiate(cellPrefab, position, Quaternion.identity);
                cell.material.color = Color.white;
                cells[x, y] = cell;
            }
        }
    }

    private void UpdateGrid()
    {
        // Apply the rules to update the grid
        for (int x = 0; x < width; x++)
        {
            for (int y = 0; y < height; y++)
            {
                int aliveNeighbors = CountAliveNeighbors(x, y);

                if (grid[x, y])
                {
                    // Cell is alive
                    if (aliveNeighbors < 2 || aliveNeighbors > 3)
                        newGrid[x, y] = false; // Die due to underpopulation or overpopulation
                    else
                        newGrid[x, y] = true; // Survive
                }
                else
                {
                    // Cell is dead
                    if (aliveNeighbors == 3)
                        newGrid[x, y] = true; // Revive due to reproduction
                    else
                        newGrid[x, y] = false; // Remain dead
                }
            }
        }

        grid = newGrid;
    }

    private void UpdateCells()
    {
        // Update the visual representation of cells based on the grid
        for (int x = 0; x < width; x++)
        {
            for (int y = 0; y < height; y++)
            {
                Renderer renderer = cells[x, y];
                renderer.sharedMaterial.color = grid[x, y] ? Color.black : Color.white;
            }
        }
    }

    private int CountAliveNeighbors(int x, int y)
    {
        int count = 0;

        for (int i = -1; i <= 1; i++)
        {
            for (int j = -1; j <= 1; j++)
            {
                if (i == 0 && j == 0)
                    continue;

                int neighborX = x + i;
                int neighborY = y + j;

                if (neighborX >= 0 && neighborX < width && neighborY >= 0 && neighborY < height)
                {
                    if (grid[neighborX, neighborY])
                        count++;
                }
            }
        }

        return count;
    }
}
  • に "CellularAutomaton" スクリプトを Unity シーンの GameObject にアタッチし、セル プレハブをインスペクターのフィールド 'cellPrefab' に割り当てます。

Unityのセルオートマトン。

この例では、セルのグリッドはブール配列で表されます。'true' は生きているセルを示し、'false' は死んだセルを示します。ライフ ゲームのルールがグリッドの更新に適用され、それに応じてセルの視覚的表現が更新されます。'CreateCells()' メソッドは各セルのゲームオブジェクトを作成し、メソッド 'UpdateCells()' はグリッドの状態に基づいて各ゲームオブジェクトの色を更新します。

注: これは単なる基本的な例であり、セル オートマトンには多くのバリエーションや拡張機能があり、検討することができます。ルール、セルの動作、およびグリッド構成を変更して、さまざまなシミュレーションを作成し、さまざまなパターンや動作を生成できます。

ボロノイ図

ボロノイ テッセレーションまたはボロノイ パーティションとも呼ばれるボロノイ図は、シードまたはサイトと呼ばれる一連の点への近さに基づいて空間を領域に分割する幾何学的構造です。ボロノイ図の各領域は、他のシードよりも特定のシードに近い空間内のすべての点で構成されます。

ボロノイ図の背後にある基本理論には次の要素が含まれます。

  1. シード/サイト: シードまたはサイトは空間内の点のセットです。これらのポイントはランダムに生成することも、手動で配置することもできます。各シードはボロノイ領域の中心点を表します。
  2. ボロノイ セル/領域: 各ボロノイ セルまたは領域は、他のシードよりも特定のシードに近い空間の領域に対応します。領域の境界は、隣接するシードを接続する線分の垂直二等分線によって形成されます。
  3. ドロネー三角形分割: ボロノイ図はドロネー三角形分割と密接に関連しています。ドロネー三角形分割は、三角形の外接円内にシードが存在しないようにシード ポイントを三角形分割するものです。ドロネー三角形分割を使用してボロノイ図を作成することも、その逆も同様です。

ボロノイ図には、次のようなさまざまな現実世界のアプリケーションがあります。

  1. 手続き型コンテンツ生成: ボロノイ図を使用して、手続き型地形、自然景観、有機的形状を生成できます。シードをコントロール ポイントとして使用し、ボロノイ セルに属性 (標高やバイオーム タイプなど) を割り当てることで、現実的で多様な環境を作成できます。
  2. ゲーム デザイン: ボロノイ図をゲーム デザインで使用して、ゲームプレイの目的でスペースを分割できます。たとえば、ストラテジー ゲームでは、ボロノイ図を使用して、ゲーム マップをさまざまな勢力が制御する領域またはゾーンに分割することができます。
  3. パスファインディングと AI: ボロノイ図は、最も近いシードまたは領域の効率的な計算を可能にする空間の表現を提供することで、パスファインディングと AI ナビゲーションを支援します。これらは、AI エージェントのナビゲーション メッシュやインフルエンス マップを定義するために使用できます。

Unity では、ボロノイ図を生成して利用する方法がいくつかあります。

  1. 手続き型生成: 開発者は、Unity のシード ポイントのセットからボロノイ図を生成するアルゴリズムを実装できます。フォーチュンのアルゴリズムやロイド緩和アルゴリズムなどのさまざまなアルゴリズムを使用して、ボロノイ図を構築できます。
  2. 地形生成: ボロノイ図を地形生成に利用して、多様で現実的な風景を作成できます。各ボロノイ セルは、山、谷、平野などの異なる地形特徴を表すことができます。標高、湿度、植生などの属性を各セルに割り当てることができ、その結果、変化に富んだ視覚的に魅力的な地形が得られます。
  3. マップの分割: ボロノイ図を使用して、ゲームプレイの目的でゲーム マップを領域に分割できます。各領域に異なる属性やプロパティを割り当てて、個別のゲームプレイ ゾーンを作成することが可能です。これは、戦略ゲーム、領土制御メカニズム、レベル デザインに役立ちます。

ボロノイ図機能を提供する Unity パッケージとアセットが利用可能で、ボロノイ ベースの機能を Unity プロジェクトに簡単に組み込むことができます。これらのパッケージには、ボロノイ図生成アルゴリズム、視覚化ツール、および Unity レンダリング システムとの統合が含まれることがよくあります。

以下は、Fortune のアルゴリズムを使用して Unity で 2D ボロノイ図を生成する例です。

using UnityEngine;
using System.Collections.Generic;

public class VoronoiDiagram : MonoBehaviour
{
    public int numSeeds = 50;
    public int diagramSize = 50;
    public GameObject seedPrefab;

    private List<Vector2> seeds = new List<Vector2>();
    private List<List<Vector2>> voronoiCells = new List<List<Vector2>>();

    private void Start()
    {
        GenerateSeeds();
        GenerateVoronoiDiagram();
        VisualizeVoronoiDiagram();
    }

    private void GenerateSeeds()
    {
        // Generate random seeds within the diagram size
        for (int i = 0; i < numSeeds; i++)
        {
            float x = Random.Range(0, diagramSize);
            float y = Random.Range(0, diagramSize);
            seeds.Add(new Vector2(x, y));
        }
    }

    private void GenerateVoronoiDiagram()
    {
        // Compute the Voronoi cells based on the seeds
        for (int i = 0; i < seeds.Count; i++)
        {
            List<Vector2> cell = new List<Vector2>();
            voronoiCells.Add(cell);
        }

        for (int x = 0; x < diagramSize; x++)
        {
            for (int y = 0; y < diagramSize; y++)
            {
                Vector2 point = new Vector2(x, y);
                int closestSeedIndex = FindClosestSeedIndex(point);
                voronoiCells[closestSeedIndex].Add(point);
            }
        }
    }

    private int FindClosestSeedIndex(Vector2 point)
    {
        int closestIndex = 0;
        float closestDistance = Vector2.Distance(point, seeds[0]);

        for (int i = 1; i < seeds.Count; i++)
        {
            float distance = Vector2.Distance(point, seeds[i]);
            if (distance < closestDistance)
            {
                closestDistance = distance;
                closestIndex = i;
            }
        }

        return closestIndex;
    }

    private void VisualizeVoronoiDiagram()
    {
        // Visualize the Voronoi cells by instantiating a sphere for each cell point
        for (int i = 0; i < voronoiCells.Count; i++)
        {
            List<Vector2> cell = voronoiCells[i];
            Color color = Random.ColorHSV();

            foreach (Vector2 point in cell)
            {
                Vector3 position = new Vector3(point.x, 0, point.y);
                GameObject sphere = Instantiate(seedPrefab, position, Quaternion.identity);
                sphere.GetComponent<Renderer>().material.color = color;
            }
        }
    }
}
  • このコードを使用するには、球プレハブを作成し、それを Unity インスペクターの seedPrefab フィールドに割り当てます。numSeeds 変数とdiagramSize変数を調整して、シードの数とダイアグラムのサイズを制御します。

Unityのボロノイ図。

この例では、VoronoiDiagram スクリプトは、指定された図のサイズ内にシード ポイントをランダムに配置することによってボロノイ図を生成します。メソッド 'GenerateVoronoiDiagram()' はシード ポイントに基づいてボロノイ セルを計算し、メソッド 'VisualizeVoronoiDiagram()' はボロノイ セルの各ポイントで球ゲームオブジェクトをインスタンス化し、ダイアグラムを視覚化します。

注: この例はボロノイ図の基本的な視覚化を提供しますが、セル ポイントを線で接続したり、地形生成やゲームプレイの目的で各セルに異なる属性を割り当てたりするなど、機能を追加してさらに拡張することができます。

全体として、ボロノイ図は、Unity で手続き型コンテンツを生成し、スペースを分割し、興味深く多様な環境を作成するための多用途で強力なツールを提供します。

手続き型オブジェクトの配置

Unity のプロシージャル オブジェクト配置には、オブジェクトを手動で配置するのではなく、アルゴリズムによってシーン内にオブジェクトを生成して配置することが含まれます。これは、環境に木、岩、建物、その他のオブジェクトを自然かつダイナミックに配置するなど、さまざまな目的に使用される強力なテクニックです。

Unity での手続き型オブジェクトの配置の例を次に示します。

using UnityEngine;

public class ObjectPlacement : MonoBehaviour
{
    public GameObject objectPrefab;
    public int numObjects = 50;
    public Vector3 spawnArea = new Vector3(10f, 0f, 10f);

    private void Start()
    {
        PlaceObjects();
    }

    private void PlaceObjects()
    {
        for (int i = 0; i < numObjects; i++)
        {
            Vector3 spawnPosition = GetRandomSpawnPosition();
            Quaternion spawnRotation = Quaternion.Euler(0f, Random.Range(0f, 360f), 0f);
            Instantiate(objectPrefab, spawnPosition, spawnRotation);
        }
    }

    private Vector3 GetRandomSpawnPosition()
    {
        Vector3 center = transform.position;
        Vector3 randomPoint = center + new Vector3(
            Random.Range(-spawnArea.x / 2, spawnArea.x / 2),
            0f,
            Random.Range(-spawnArea.z / 2, spawnArea.z / 2)
        );
        return randomPoint;
    }
}
  • このスクリプトを使用するには、Unity シーンに空のゲームオブジェクトを作成し、それに attach して "ObjectPlacement" スクリプトを追加します。オブジェクト プレハブを割り当て、要件に合わせてインスペクターの 'numObjects' および 'spawnArea' パラメーターを調整します。シーンを実行すると、オブジェクトは定義されたスポーン領域内に手順に従って配置されます。

Unity でのプロシージャル オブジェクトの配置。

この例では、'ObjectPlacement' スクリプトは、シーン内にオブジェクトを手続き的に配置する役割を果たします。'objectPrefab' フィールドには、配置するオブジェクトのプレハブを割り当てる必要があります。'numObjects' 変数は配置されるオブジェクトの数を決定し、変数 'spawnArea' はオブジェクトがランダムに配置される領域を定義します。

'PlaceObjects()' メソッドは、必要な数のオブジェクトをループし、定義されたスポーン領域内にランダムなスポーン位置を生成します。次に、ランダムな回転でランダムな位置ごとにオブジェクト プレハブをインスタンス化します。

注: プロジェクトの特定の要件に応じて、グリッドベースの配置、密度ベースの配置、ルールベースの配置などのさまざまな配置アルゴリズムを組み込むことで、このコードをさらに強化することができます。

結論

Unity の手続き型生成手法は、ダイナミックで没入型のエクスペリエンスを作成するための強力なツールを提供します。 パーリン ノイズやフラクタル アルゴリズムを使用して地形を生成する場合でも、ボロノイ図を使用して多様な環境を作成する場合でも、セル オートマトンで複雑な動作をシミュレートする場合でも、手続き的に配置されたオブジェクトをシーンに追加する場合でも、これらの技術はコンテンツ生成に柔軟性、効率性、無限の可能性をもたらします。 これらのアルゴリズムを活用し、Unity プロジェクトに統合することで、開発者は現実的な地形の生成、本物そっくりのシミュレーション、視覚的に魅力的な環境、魅力的なゲームプレイ メカニズムを実現できます。 プロシージャル生成は、時間と労力を節約するだけでなく、プレイヤーを魅了し、仮想世界に命を吹き込む、ユニークで常に変化するエクスペリエンスの作成を可能にします。

おすすめの記事
Unity ゲーム開発におけるストーリーテリングの重要性
Unity で地形に木をペイントする方法
アニメーションを Unity にインポートする方法
Unity での環境に適した Skybox の選択
Unity ゲームを著作権侵害から守る戦略
Unity で FNAF にインスピレーションを得たゲームを作成する方法
Unity でゲームに適切な BGM を選択する方法