見出し画像

【Unity】【2D】パーリンノイズを利用した地形生成を行ってみる。

やりたいこと

作成中のゲームの地形を作りたい。
ランダムに生成された洞窟の地形を自機が進んでいく2Dのゲーム。

イメージは下の画像の、上下のぐにゃぐにゃ部分(?)みたいな感じ。今は事前に用意した画像を利用しているけれど。

パーリンノイズで地形生成をしようと思ったきっかけ

たまたまFPSのカメラの手振れ処理の実装の記事を見たときに、パーリンノイズ知ったから使いたくなった。
あと、ちょうど地形生成処理に悩んでたから。

パーリンノイズって?

Mathf.PerlinNoise(float x,float y)

滑らかな凸凹の平面を作って、引数で渡した座標の高さを取る…みたいなイメージ(厳密ではない)。帰ってくるのはfloat型で0~1の値。
特定の座標から一次元のように扱えば凸凹地形を作れるぞ。

一応リファレンスにも一次元パーリンノイズについて言及がある。コードもある。

Although the noise plane is two-dimensional, it is easy to use just a single one-dimensional line through the pattern, say for animation effects.

https://docs.unity3d.com/ja/2019.4/ScriptReference/Mathf.PerlinNoise.html
using UnityEngine;

public class Example : MonoBehaviour
{
    // "Bobbing" animation from 1D Perlin noise.

    // Range over which height varies.
    float heightScale = 1.0f;

    // Distance covered per second along X axis of Perlin plane.
    float xScale = 1.0f;


    void Update()
    {
        float height = heightScale * Mathf.PerlinNoise(Time.time * xScale, 0.0f);
        Vector3 pos = transform.position;
        pos.y = height;
        transform.position = pos;
    }
}

試しに、LineRendererで一次元パーリンノイズを描画してみた。
↓こんな感じにランダムな波が生成できる

ちなみに、引数は0以上1以下でないといけないっぽい?(ほんまか?)
これに気づかずにずっと一直線が生成されて頭を抱えてた。

地形生成の具体的な手法

意外と2Dの動的地形生成の記事が見つからない。

ひとしきり悩んだ挙句、SpriteShapeを利用することにした。
画像とかをいい感じに曲線にしたり色々できる2Dゲームの地形作成に向いてそうなコンポーネント。最近のUnityのバージョンなら最初から入ってるっぽい。
詳しくは↓のURL

実際のコード

結構やっつけだから参考にする程度にとどめてほしい。ただ、コピペしても動きはする。

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

public class CaveGenerator : MonoBehaviour
{
    // 基準の座標
    [SerializeField]
    float offsetX = 960;
    [SerializeField]
    float offsetY = 1020;

    // 高さ調整用
    float heightScale = 1000.0f;
    // パーリンノイズの読み取り間隔
    float xScale = 0.06f;
    // Unity状で実際に配置する間隔
    float xSpace = 30.0f;

    // 上下反転。
    public bool yReverse;
    // SpriteShapeController
    SpriteShapeController spriteShapeController;

    // 生成する頂点数
    [SerializeField]
    int PointCount = 1000;

    // 初期化
    void Awake()
    {
        spriteShapeController = GetComponent<SpriteShapeController>();
        spriteShapeController.spline.Clear();
    }

    // 洞窟生成
    void Start()
    {
        // パーリンノイズ読み取り開始位置をランダムで設定。
        // ないと毎回同じ形になる。
        var randomXoffset = Random.Range(0, 200);
        var randomXoffset2 = Random.Range(0, 200);

        Spline spline = spriteShapeController.spline;

        // 必要なら上下反転
        if (yReverse)
        {
            offsetY = -offsetY;
        }

        // 0個目のSplineのポイントを生成
        spline.InsertPointAt(0, new Vector2(-offsetX, -offsetY));

        // Splineのポイントを配置していく。
        for (int i = 1; i < PointCount; i++)
        {
            // パーリンノイズから高さを生成
            float height = heightScale * Mathf.PerlinNoise(randomXoffset + i * xScale, 0.0f);
            // 一応二重にパーリンノイズから高さを取ってみる(意味ないかも)。
            height += heightScale * Mathf.PerlinNoise(randomXoffset2 + i * xScale, 0.0f);

            if (yReverse)
            {
                height = -height;
            }

            // Splineのポイントの配置間隔に揺らぎを与える。
            var randomSpace = Random.Range(0, 15);
            // Splineのポイントを実際にUnity上で配置する位置を決める。
            Vector2 pos = new Vector2((i * xSpace + randomSpace) - offsetX, height - offsetY);
            // Splineのポイントを追加
            spline.InsertPointAt(i, pos);
        }
        // 0個目のSplineのポイントとつながって地面を塗りつぶす 
        spline.InsertPointAt(PointCount, new Vector2(PointCount * xSpace - offsetX, -offsetY));
        spriteShapeController.RefreshSpriteShape();
    }
}

Editorでコンポーネントをこんな感じに配置してる。RectTransformはTransformでも大丈夫…なはず。

実際に生成されたもの

こんな感じになった。やっつけの割にはいい感じ。

上も生成してみる。

ちなみに、頂点はこんな感じ。多分もっといい方法はある。

ゲーム画面

まとめ

案外簡単だった。1Dパーリンノイズは、今回みたいな地形生成以外にも値に緩やかな揺らぎを与える使い方ができるだろうし、知ってると簡潔に書ける処理は結構ありそう。

そういえばライトの点滅をパーリンノイズで制御してる記事もあったな…。


気分が乗れば、今回生成した洞窟の壁に装飾付けたりする記事も書く。

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