simplestarの技術ブログ

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

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

天地創造

ここのところメインスレッドが処理落ちしないよう、バックグラウンド処理でメッシュ構築を行う仕組みを作ってきました。
こちらが機能するようになったので、今回はいよいよブロックのロジックに入ります。

さっそく適当な天地創造プラグインを書いて結果を見てみました。

f:id:simplestar_tech:20180113215541g:plain

youtube 版はこちら
www.youtube.com


インタフェースは仮ですが、プラグインの実装はこの65行になります。

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

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

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

        public void WorldStart(int seed)
        {
            _seed = seed;
        }

        public void ChunkStart(int positionX, int positionZ, int width, int depth, int height)
        {
            _random = new Random(DateTime.Now.Millisecond);
            for (int i = 0; i < 45; i++)
            {
                _points.Add(new float[] { _random.Next(0, width - 1), _random.Next(0, width - 1) });
            }
        }

        public int BlockId(int widthIndex, int depthIndex, int heightIndex)
        {
            _heightMax = _random.Next(20, 45);
            if (_heightMax > heightIndex)
            {
                for (int i = 0; i < _points.Count; i++)
                {
                    if (_points[i][0] == widthIndex && _points[i][1] == depthIndex)
                    {
                        return 1;
                    }
                }
            }
            return 0;
        }

        public void ChunkEnd()
        {
            
        }

        public void WorldEnd()
        {
            
        }

        private List<float[]> _points = new List<float[]>();
        private int _seed = 0;
        private Random _random;
        private int _heightMax = 0;
    }
}
#endif

まだ仮ですけど、プラグインのインタフェースを解説します。

    public interface IWorldCreation
    {
        bool IsInstantiater();
        IWorldCreation Instantiate(int positionX, int positionZ);
        void WorldStart(int seed);
        void ChunkStart(int positionX, int positionZ, int width, int depth, int height);
        int BlockId(int widthIndex, int depthIndex, int heightIndex);
        void ChunkEnd();
        void WorldEnd();
    }
  • IsInstantiater 創造主フラグ

プラグインの実装が一つの場合は必ず true を返します。
世界各地に応じてプラグイン実装をいくつも用意した時、それらの実装をインスタンス化して振り分けるプラグインなのかどうかを返事します。

単一処理のステートレスなプラグイン実装ならば this を返してください。
ただし、今回のように乱数生成オブジェクトを持つような(ステートフルな)プラグイン実装の場合は新しいインスタンスを作成して返さなければなりません。
なぜならば、複数のスレッドから同時にプラグインの機能が呼び出されることがあり、あるチャンク処理中に別のチャンクの処理が始まり、創造結果においては創造主の期待と異なる結果を生む場合があるからです。
positionX, positionZ はチャンクの位置インデックスで、それぞれ経度、緯度を意味します。
世界各地におけるプラグイン実装をいくつも用意した場合は、ここでそれらの実装を振り分けるようにインスタンス化して返してください。

  • WorldStart 世界構築前に一回だけ呼び出される

seed にはゲームメニューから設定された値が入ります。
乱数基準に作られる世界だったとしても、同じ種から生み出された世界は、同じ形となるべきです。
インスタンス化される側のプラグイン実装の場合は呼ばれないので注意

  • ChunkStart チャンク処理開始前に一回だけ呼び出される

positionX, positionZ はチャンクの位置インデックスで、それぞれ経度、緯度を意味します。
width, depth, height はチャンクのそれぞれのブロック数を示します。
今は 16 x 16 x 256 ブロックで構成されるチャンクになっています。

  • BlockId ブロックのマテリアルIDを決める

チャンク内のブロックのインデックスが引数で渡されますので、そのブロックがどのマテリアルになるべきかを返します。
今は 0 が風で、1が土、それ以外のマテリアルが存在しませんので、おいおいマテリアルID表を作成して公開します。
今回作ったプラグインは45本の柱について、高さ20まで積んだ後、高さ45までランダムに間引くように作られています。

  • ChunkEnd チャンク処理終了後に一回だけ呼び出される

チャンク処理が終わった後の終了処理をここで行います。

  • WorldEnd 世界を閉じる前に一回だけ呼び出される

終了処理を行うタイミングの一つとして認識してください。
インスタンス化されたプラグイン実装の場合は呼ばれないので注意