simplestarの技術ブログ

目的を書いて、思想と試行、結果と考察、そして具体的な手段を記録します。

AIに身体性を与えるためのマイクロワールドの構築15:WorldCreation2

UnityでMinecraftライクゲーム作ってます。前回作った天地創造プラグインにパーリンノイズで高さを決定する機能を入れてみました。
こんな感じでみなさんの自由なアイディアをゲームに取り入れられます。

f:id:simplestar_tech:20180114104815j:plain

www.youtube.com

プラグインの実装はこちら↓
ノイズの関数を呼ぶとはいえ、わずかプラグインの13 行のプログラムでこの世界が形作られているとは、自分でも驚きです。

#if !USE_PLUGINS
using System;
using System.Collections.Generic;

namespace SimpleHexWorld
{
    public class WorldCreation2 : IWorldCreation {
        public bool IsInstantiater()
        {
            return true;
        }

        public IWorldCreation Instantiate(int positionX, int positionZ)
        {
            return new WorldCreation2();
        }

        public void WorldStart(int seed)
        {
            
        }
        public void ChunkStart(int positionX, int positionZ, int width, int depth, int height)
        {
            _perlin = new Perlin(32);
            _positionX = positionX;
            _positionZ = positionZ;
            _width = width;
            _depth = depth;
            _height = height;
        }

        public int BlockId(int widthIndex, int depthIndex, int heightIndex)
        {
            if (_lastWidth != widthIndex || _lastDepth != depthIndex)
            {
                _lastWidth = widthIndex;
                _lastDepth = depthIndex;
                double x = (_width * _positionX + widthIndex) / (256.0 * _width) * 32;
                double z = (_depth * _positionZ + depthIndex) / (256.0 * _depth) * 32;
                _heightNoise = _perlin.OctavePerlin(x, 0, z, 6, 0.5);
            }
            if (_heightNoise * _height > heightIndex)
            {
                return 1;
            }
            return 0;
        }

        public void ChunkEnd()
        {
        }

        public void WorldEnd()
        {
        }

        private Perlin _perlin;
        private int _positionX;
        private int _positionZ;
        private int _lastWidth = -1;
        private int _lastDepth = -1;
        private int _lastHeight;
        private int _width;
        private int _depth;
        private int _height;
        private double _heightNoise;
    }
}
#endif

パーリンノイズの実装は以前にも紹介しました次の記事
postd.cc
のものを使わせていただきました。
本当に素晴らしい記事、ありがとうございます。

public class Perlin
{
    public Perlin(int repeat = -1)
    {
        this._repeat = repeat;
    }

    public double OctavePerlin(double x, double y, double z, int octaves, double persistence)
    {
        double total = 0;
        double frequency = 1;
        double amplitude = 1;
        double maxValue = 0;            // Used for normalizing result to 0.0 - 1.0

        for (int i = 0; i < octaves; i++)
        {
            total += perlin(x * frequency, y * frequency, z * frequency) * amplitude;
            maxValue += amplitude;
            amplitude *= persistence;
            frequency *= 2;
        }
        return total / maxValue;
    }

    public double perlin(double x, double y, double z)
    {
        if (_repeat > 0)
        {                                   // If we have any repeat on, change the coordinates to their "local" repetitions
            x = x % _repeat;
            y = y % _repeat;
            z = z % _repeat;
        }

        int xi = (int)x & 255;                              // Calculate the "unit cube" that the point asked will be located in
        int yi = (int)y & 255;                              // The left bound is ( |_x_|,|_y_|,|_z_| ) and the right bound is that
        int zi = (int)z & 255;                              // plus 1.  Next we calculate the location (from 0.0 to 1.0) in that cube.

        double xf = x - (int)x;                             // We also fade the location to smooth the result.
        double yf = y - (int)y;
        double zf = z - (int)z;
        double u = fade(xf);
        double v = fade(yf);
        double w = fade(zf);

        int aaa, aba, aab, abb, baa, bba, bab, bbb;

        aaa = _p[_p[_p[xi] + yi] + zi];
        aba = _p[_p[_p[xi] + inc(yi)] + zi];
        aab = _p[_p[_p[xi] + yi] + inc(zi)];
        abb = _p[_p[_p[xi] + inc(yi)] + inc(zi)];
        baa = _p[_p[_p[inc(xi)] + yi] + zi];
        bba = _p[_p[_p[inc(xi)] + inc(yi)] + zi];
        bab = _p[_p[_p[inc(xi)] + yi] + inc(zi)];
        bbb = _p[_p[_p[inc(xi)] + inc(yi)] + inc(zi)];

        double x1, x2, y1, y2;

        x1 = lerp(grad(aaa, xf, yf, zf),                // The gradient function calculates the dot product between a pseudorandom
                    grad(baa, xf - 1, yf, zf),              // gradient vector and the vector from the input coordinate to the 8
                    u);                                     // surrounding points in its unit cube.
        x2 = lerp(grad(aba, xf, yf - 1, zf),                // This is all then lerped together as a sort of weighted average based on the faded (u,v,w)
                    grad(bba, xf - 1, yf - 1, zf),              // values we made earlier.
                      u);
        y1 = lerp(x1, x2, v);

        x1 = lerp(grad(aab, xf, yf, zf - 1),
                    grad(bab, xf - 1, yf, zf - 1),
                    u);

        x2 = lerp(grad(abb, xf, yf - 1, zf - 1),
                      grad(bbb, xf - 1, yf - 1, zf - 1),
                      u);
        y2 = lerp(x1, x2, v);
        return (lerp(y1, y2, w) + 1) / 2;                       // For convenience we bound it to 0 - 1 (theoretical min/max before is -1 - 1)
    }

    public int inc(int num)
    {
        num++;
        if (_repeat > 0) num %= _repeat;
        return num;
    }

    public static double grad(int hash, double x, double y, double z)
    {
        int h = hash & 15;                                  // Take the hashed value and take the first 4 bits of it (15 == 0b1111)
        double u = h < 8 /* 0b1000 */ ? x : y;              // If the most significant bit (MSB) of the hash is 0 then set u = x.  Otherwise y.
        double v;                                           // In Ken Perlin's original implementation this was another conditional operator (?:).  I

        // expanded it for readability.

        if (h < 4 /* 0b0100 */)                             // If the first and second significant bits are 0 set v = y
            v = y;
        else if (h == 12 /* 0b1100 */ || h == 14 /* 0b1110*/)// If the first and second significant bits are 1 set v = x
            v = x;
        else                                                // If the first and second significant bits are not equal (0/1, 1/0) set v = z
            v = z;
        return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v); // Use the last 2 bits to decide if u and v are positive or negative.  Then return their addition.
    }

    public static double fade(double t)
    {
        // Fade function as defined by Ken Perlin.  This eases coordinate values
        // so that they will "ease" towards integral values.  This ends up smoothing
        // the final output.
        return t * t * t * (t * (t * 6 - 15) + 10);         // 6t^5 - 15t^4 + 10t^3
    }

    public static double lerp(double a, double b, double x)
    {
        return a + x * (b - a);
    }

    static Perlin()
    {
        _p = new int[512];
        for (int x = 0; x < 512; x++)
        {
            _p[x] = permutation[x % 256];
        }
    }

    private static readonly int[] permutation = { 151,160,137,91,90,15,					// Hash lookup table as defined by Ken Perlin.  This is a randomly
		131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,	// arranged array of all numbers from 0-255 inclusive.
		190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
        88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
        77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
        102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
        135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
        5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
        223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
        129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
        251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
        49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
        138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180
    };

    public int _repeat;
    private static readonly int[] _p;                                                    // Doubled permutation to avoid overflow
}

このPerlinクラスの使い方を簡単に示します。

コンストラクタの引数で指定するのは循環値で、今回は 32 を入力しましたが、その場合は OctavePerlin で指定する座標値は 0~32 の範囲の実数で与えること。
これによって、ちょうど世界が循環するチャンク0 番付近でノイズも循環するので不自然な切断面ができなくなります。

仮に、0~32 で実数を与えているのに、コンストラクタに 64 とか設定しちゃうとこんな感じで、とても不自然な切り立った面ができちゃいます。
f:id:simplestar_tech:20180114111502j:plain

3次元座標を指定すると、浮遊島や入り組んだ洞窟も作れます。
プラグインの実装を次のように変更してみました。

        public int BlockId(int widthIndex, int depthIndex, int heightIndex)
        {
            double x = (_width * _positionX + widthIndex) / (256.0 * _width) * 256;
            double z = (_depth * _positionZ + depthIndex) / (256.0 * _depth) * 256;
            double y = heightIndex / (double)(_height) * 32;
            _perlinNoise = _perlin.OctavePerlin(x, y, z, 6, 0.5);
            if (_perlinNoise < 0.4)
            {
                return 1;
            }
            return 0;
        }

結果は次の通りです。
f:id:simplestar_tech:20180114115143j:plain

いろんな角度から見た絵はこちら
www.youtube.com

最初の Height 指定と合わせて、土の中はこんな感じの 3D ノイズで穴を掘ったり
まばらに溶岩ブロックを置くとそれらしい初期状態が作れそうですね。