simplestarの技術ブログ

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

AIに身体性を与えるためのマイクロワールドの構築16.5:水のロジック

Unity でマインクラフトライクなゲーム制作の記録です。

前回用意したプラグイン機構を使って、水ブロックのロジックを考えてみました。
ゆっくり移動するのですが、早送りするとそれらしい振る舞いに見えなくもないかな…

f:id:simplestar_tech:20180116235107g:plain

動画はこちら
www.youtube.com

今回は水圧というものを加えてみました。
隣接する空気ブロックへ移動した場合は水圧は0になり、水面の水ブロックも水圧は0になります。
水圧は、水ブロックが自らの逃げ場を失った時に上昇し、上に積みあがった水ブロックの圧力よりも1大きく、上に水が積みあがっていない場合は周囲のブロックの中で最も高い圧力と同じになります。

移動に関しては、直下のブロックが空気だった場合はそのブロックと交換し、直下が空気でない場合は側面を調べ、最も圧力の高い隣接ブロックの反対の空気ブロックと交換し
移動方向が決まらない場合はランダムな側面の空気ブロックへと交換します。

実装コードは次の通り

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

namespace SimpleHexWorld
{
    public class WaterLogic : IBlockLogic
    {
        public int BlockId()
        {
            return (int)MaterialID.Water;
        }

        public void Work(int widthIndex, int depthIndex, int heightIndex, IHexWorldUtility worldUtility)
        {
            double randomValue = _random.NextDouble();

            if (0.1f < randomValue)
            {
                // return;
            }
            BlockData myBlockData = worldUtility?.GetBlockData(widthIndex, depthIndex, heightIndex) ?? BlockData.Identity;
            BlockData bottomBlockData = worldUtility?.GetBlockData(widthIndex, depthIndex, heightIndex - 1) ?? BlockData.Identity;
            if ((ushort)MaterialID.Air == bottomBlockData.blockId)
            {
                worldUtility?.SetBlockData(widthIndex, depthIndex, heightIndex, bottomBlockData);
                myBlockData.stress = 0;
                worldUtility?.SetBlockData(widthIndex, depthIndex, heightIndex - 1, myBlockData);
            }
            else
            {
                byte maxSideStress = 0;
                byte maxBackStress = 0;
                int sideAirIndex = -1;
                List<int> sideAirIndices = new List<int>();
                for (int sideIndex = 0; sideIndex < 6; sideIndex++)
                {
                    int sideWidthIndex = widthIndex;
                    int sideDepthIndex = depthIndex;
                    worldUtility?.GetSideBlockIndex(widthIndex, depthIndex, sideIndex, out sideWidthIndex, out sideDepthIndex);

                    BlockData sideBlockData = worldUtility?.GetBlockData(sideWidthIndex, sideDepthIndex, heightIndex) ?? BlockData.Identity;
                    if ((ushort)MaterialID.Air == sideBlockData.blockId)
                    {
                        sideAirIndices.Add(sideIndex);
                        int backIndex = sideIndex + 3;
                        if (6 <= backIndex)
                        {
                            backIndex -= 6;
                        }
                        worldUtility?.GetSideBlockIndex(widthIndex, depthIndex, backIndex, out sideWidthIndex, out sideDepthIndex);
                        BlockData backBlockData = worldUtility?.GetBlockData(sideWidthIndex, sideDepthIndex, heightIndex) ?? BlockData.Identity;
                        if (maxBackStress < backBlockData.stress)
                        {
                            maxBackStress = backBlockData.stress;
                            sideAirIndex = sideIndex;
                        }
                        
                    }
                    else if ((ushort)MaterialID.Water == sideBlockData.blockId)
                    {
                        if (maxSideStress < sideBlockData.stress)
                        {
                            maxSideStress = sideBlockData.stress;
                        }
                    }
                }
                if (-1 != sideAirIndex)
                {
                    _MoveSideBlock(widthIndex, depthIndex, worldUtility, sideAirIndex, heightIndex, ref myBlockData);
                }
                else if (0 < sideAirIndices.Count)
                {
                    _MoveSideBlock(widthIndex, depthIndex, worldUtility, sideAirIndices[_random.Next(0, sideAirIndices.Count)], heightIndex, ref myBlockData);
                }
                else
                {
                    byte lastStress = myBlockData.stress;
                    BlockData topBlockData = worldUtility?.GetBlockData(widthIndex, depthIndex, heightIndex + 1) ?? BlockData.Identity;
                    if ((ushort)MaterialID.Water == topBlockData.blockId)
                    {
                        myBlockData.stress = (byte)Math.Min((1 << 6 - 1), topBlockData.stress + 1);
                    }
                    else if ((ushort)MaterialID.Air == topBlockData.blockId)
                    {
                        myBlockData.stress = 0;
                    }
                    else
                    {
                        myBlockData.stress = maxSideStress;
                    }
                    if (lastStress != myBlockData.stress)
                    {
                        worldUtility?.SetBlockData(widthIndex, depthIndex, heightIndex, myBlockData);
                    }
                }
            }
        }

        private void _MoveSideBlock(int widthIndex, int depthIndex, IHexWorldUtility worldUtility, int sideAirIndex, int heightIndex, ref BlockData myBlockData)
        {
            int sideWidthIndex = widthIndex;
            int sideDepthIndex = depthIndex;
            worldUtility?.GetSideBlockIndex(widthIndex, depthIndex, sideAirIndex, out sideWidthIndex, out sideDepthIndex);
            BlockData sideBlockData = worldUtility?.GetBlockData(sideWidthIndex, sideDepthIndex, heightIndex) ?? BlockData.Identity;
            worldUtility?.SetBlockData(widthIndex, depthIndex, heightIndex, sideBlockData);
            myBlockData.stress = 0;
            worldUtility?.SetBlockData(sideWidthIndex, sideDepthIndex, heightIndex, myBlockData);
        }

        private Random _random = new Random();
    }
}
#endif