見出し画像

[Unity][C#Script] 乱数を制するものは、ゲーム制作を制する。パーリンノイズ地形を制してしてみる編

 乱数のひとつである、パーリンノイズで作ったテレイン地形の、パーリンノイズの数値を加工して地形がどのように変化するかを試してみます。
 目標は、山道っぽい地形を、ダンジョンっぽく変えてみることです。
 今回はソースコードのみの記事となりますので、実際にテレインを作る方法はパーリンノイズでテレイン地形を作るのは、以前の記事を参照ください。

1.プレーンな地形を作成する。

 まずは無加工のパーリンノイズ地形を作ってみます。スクリプトは以下となります。Script名はAutoGenerateTerrainとしております。
 なお、Mathf.PerlinNoise関数で得られるパーリンノイズの値は0~1.0です。

// K1Togami 2022/10/9
using UnityEngine;

public class AutoGenerateTerrain : MonoBehaviour
{
    public float scale = 0.5f;
    public float freq = 0.01f;
    public float seed = 0;

    [ContextMenu("Generate")]
    private void makeGround()
    {
        // テレインを取得し、頂点情報を格納する配列を作る。
        TerrainData genTerrain = GetComponent<Terrain>().terrainData;
        var heights = new float[genTerrain.heightmapResolution, genTerrain.heightmapResolution];

        // テレイン平面をパーリンノイズによって隆起させる。
        for (int x = 0; x < genTerrain.heightmapResolution; x++)
        {
            for (int y = 0; y < genTerrain.heightmapResolution; y++)
            {
                // Terrainの高さをセット
                heights[x, y] = perlinNoiseHeight(x, y);
            }
        }
        // テレインに頂点情報を反映
        genTerrain.SetHeights(0, 0, heights);
    }

    private float perlinNoiseHeight(int x, int y)
    {
        // パーリンノイズから高さのベースを算出
        float height = Mathf.PerlinNoise(x * freq + seed, y * freq + (seed / 2));

        return height*scale;
    }
}

これを実行(スクリプトを右クリックからGenerate)すると、画像のような地形になります。

ワイヤーフレーム・メッシュ表示としています。

2.パーリンノイズの数値を加工してみる。

1) 平地を作る

まずは、谷底をフラットにしてみます。
ソースコードのうち、高さを取得する関数を変更します。
具体的には、高さ0.35以下をすべてフラットにしてみます。

    private float perlinNoiseHeight(int x, int y)
    {
        // パーリンノイズから高さのベースを算出
        float height = Mathf.PerlinNoise(x * freq + seed, y * freq + (seed / 2));

        // パーリンノイズを加工 0.35以下をすべて0.35にする。
        if (height < 0.35f) height = 0.35f;
        
        return height*scale;
    }
}

実行すると、谷底に当たる部分にフラットな面ができました。山間の道、みたいな雰囲気ではないでしょうか。
平面のしきい値は、いろいろといじってみるとよいかもしれません。

ゲームのフィールドとして、使えそうでしょうか?

2) 山頂部を尖らせてみる(高さの半分を反転してみる)

 いきなりの提案ですが、高さ0.5から高さを反転してみると、山の山頂部が尖ります。(周波数は2倍になり、また、高さの最高値が0.5になります)
 (谷底をフラットにするのは解除しています)

// 山頂部先鋭化サンプル
    private float perlinNoiseHeight(int x, int y)
    {
        // パーリンノイズから高さのベースを算出
        float height = Mathf.PerlinNoise(x * freq + seed, y * freq + (seed / 2));

        // h=0.5 で谷部を反転する
        if (height > 0.5f)
        {
            height = (1.0f-height );
        }

        return height * scale;
    }

 山の先端がとがることで、山にあたる部分が急峻となりました。反面、谷底がすりばち型のようになだらかになるのが特徴です。
 システムにもよりますが、山頂を超えて隣の谷に行くのが難しくなると思います。

急峻な山に囲まれたことで、各谷部の独立性が高まったのではないでしょうか。

3) ダンジョン風(すりばち型の谷底をフラット)にする。

上で作った地形の谷底を反転し、フラットにします。しきい値はいろいろと試してみてほしいのですが、今回は0.35にしてみました。

//ダンジョン風サンプル
    private float perlinNoiseHeight(int x, int y)
    {
        // パーリンノイズから高さのベースを算出
        float height = Mathf.PerlinNoise(x * freq + seed, y * freq + (seed / 2));

        // h=0.5 で谷部を反転する
        height -= 0.5f;
        if (height < 0f)
        {
            height *= -1;
        }

        height *= -1;
        height += 0.5f;

        // パーリンノイズを加工 0.35以下をすべて0.35にする。
        if (height < 0.35f) height = 0.35f;

        return height  *scale;
    }

壁がくっきりとせり出し、なんとか、ダンジョンっぽい雰囲気になったのではないでしょうか。
フラット部は若干ゆるやかなので、スロープを介して乗り越えられる部分もあると想います。

部屋感が増したでしょうか

4) もうひとつのパーリンノイズと合成してみる。

 とりあえずダンジョン感は増したかもしれませんが、パーリンノイズの周期性の特徴から生じる、各部屋はつながらないという特徴が強くでてしまっています。
 テレインをレタッチすれば済む話はであるのですが、ここも乱数でなんとかできるでしょうか。
 というわけでシード値の異なる、つまり「ずらした」パーリンノイズと組み合わせ、空間同士を結ぶ道ができる雰囲気ができるか試してみます。

// 隘路追加サンプル   
    private float perlinNoiseHeight(int x, int y)
    {
        // パーリンノイズから高さのベースを算出
        float height = Mathf.PerlinNoise(x * freq + seed, y * freq + (seed / 2));

        // h=0.5 で谷部を反転する
        height -= 0.5f;
        if (height < 0f)
        {
            height *= -1;
        }

        height *= -1;
        height += 0.5f;

        // パーリンノイズを加工 0.35以下をすべて0.35にする。
        if (height < 0.35f) height = 0.35f;

        // もうひとつのパーリンノイズを作りとある範囲となった場合はフラットにする
        float roadWavet = Mathf.PerlinNoise(x * freq/3 +seed, y * freq/3 +(seed/2));
        if (roadWavet > 0.45f && roadWavet < 0.5f) height = 0.35f;

        return height * scale;
    }

 かなり強引ですが、通路によって、部屋がいくつかつながったようです。
 乱数を重ねるという使い方は、いろいろと応用が効くのではないでしょうか。

乱暴ではあるが、道はできたようです。

5) 離散化(デジタル化)してみる

 最後は、terrainの高さを離散化してみます。離散化するとスロープがすべて垂直になります。サンプルは無加工のパーリンノイズ地形を離散化しています。

// 離散化サンプル
    private float perlinNoiseHeight(int x, int y)
    {
        // パーリンノイズから高さのベースを算出
        float height = Mathf.PerlinNoise(x * freq + seed, y * freq + (seed / 2));

        // height を0.05ごとに離散化する
        float risansize = 0.05f;
        int risan = (int)(height  / risansize);
        height = height - (height - risan* risansize);

        return height * scale;
    }
離散化すると段々畑のようになります

3.(おまけ)前回スクリプトの補足

 前回は、スロープの中腹をフラットにし、自然な「道」を作ることを目指していました。
 また、前回の道には高さのうねりを与えています。これは周期のゆるやかなパーリンノイズを加算することで行っています。
 これも、乱数の調整のひとつですね。
 部屋をつなぐ道の生成は、この応用となります。このような道と、今回の「部屋」を組わせることで「地形」がより豊かになるいいなと思います。

4.これからの改造案

 サンプルのなかで、ダンジョンにつけた道は、場合によってはトンネルとしたいところもあります。
 テレインに穴をあけ、ポリゴンによる地形と組み合わせることは、できなくはないので、試してみるのも面白いかもしれません。Diggerなどの有料アセットだと簡単でしょうか。(exにリンクをつけておきます)
 あとは、とりあえずは、ソースコード上でマジックナンバーとなっている値を調整し、もうすこし現実味のある通路を作ることができるかもしれません。 
 形を整えるだけならば、テレインの編集機能で直接調整しちゃうのも手っ取り早いと思います。
 逆に、破壊できる岩や動く岩、あるいは扉を置くという仕掛けにこじつけることで回避するのも、ゲームらしくてよいかもしれませんね。

 ほかには、前の記事にある道路の高さに地形をフィットさせるなんていうのも、お互いの座標を確認できる方法があれば、それほど難しくなく実現できそうです。

5.技術的な補足

 今回は、ローグタイプのようなきちんとした空間の把握によらず、パーリンノイズから生じる自然な起伏を使って、部屋らしいものを作ってみました。
 もちろん各部屋へ到達できることを保証できないため、ローグタイプのゲームにそのまま使うことはできないという欠点があります。
 逆に、事前に用意するステージとするのならば、テレインとして起伏を調整したり、テクスチャで飾ってみたり、木を生やしたり、水場を作ったりと、いろいろな工夫をするたたき台としては使うと、時間短縮になるかもしれません。
 いずれにせよ、テレインという地形、ステージをプロシージャル的な手法で、ダイナミックにいじるのは楽しいので、いろいろな応用をしていただけるのなら嬉しいなと思います。 

ではまた。ゲーム制作者魂がともにあらんことを。(k1t)

ex.参考となりそうな記事

2022年10月17日 離散化サンプル部追加・山頂部先鋭化サンプル修正

この記事が参加している募集

ゲームの作り方

この記事が気に入ったらサポートをしてみませんか?