kamihi のブログ

作成したゲームの情報や作り方を掲載します

UnityでMinecraft風ゲームを作る その3「チャンクマネージャーの作成」

今回はチャンクマネージャーを追加し、チャンクを沢山生成し、一気にパーリンノイズをかけたいと思います。

①ChunkManager.csを作成します

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

//UnityでMinecraft風ゲームを作る その3「チャンクマネージャーの作成」
namespace Sono3
{
    //方向の定義
    public enum Direction
    {
        Top = 0, Bottom, Right, Left, Front, Back, Last
    }

    /// <summary>
    /// クラス「チャンクマネージャー」
    /// </summary>
    public class ChunkManager : MonoBehaviour
    {
        static public int chunkWidth = 8;
        //チャンクの高さ8→32に変更
        static public int maxHeight = 32;
        //パーリンノイズで使用するシード値
        static public int worldSeed = 1234567;
        //チャンクディクショナリー、全てのチャンクが格納される
        static public Dictionary<string, Chunk> allChunkData = new Dictionary<string, Chunk>();
        //チャンクの表示距離
        [System.NonSerialized]
        public int chunkDrawDistance = 4;

        public Material cubeMaterial;

        void Start()
        {
            //チャンククラスを生成しブロックをセットする
            for (int x = -chunkDrawDistance; x <= chunkDrawDistance; x++)
            {
                for (int z = -chunkDrawDistance; z <= chunkDrawDistance; z++)
                {
                    Chunk chunk = new Chunk(x, z);
                    allChunkData.Add(x + "_" + z, chunk);
                }
            }
            //チャンクのメッシュを生成する
            for (int x = -chunkDrawDistance; x <= chunkDrawDistance; x++)
            {
                for (int z = -chunkDrawDistance; z <= chunkDrawDistance; z++)
                {
                    Chunk chunk = GetChunk(x, z);
                    GameObject obj = new GameObject("Chunk");
                    obj.AddComponent<MeshRenderer>();
                    obj.GetComponent<MeshRenderer>().material = cubeMaterial;
                    obj.AddComponent<MeshFilter>();
                    obj.GetComponent<MeshFilter>().mesh = chunk.BuildVisualMesh();

                    obj.transform.position = new Vector3(x * chunkWidth, 0, z * chunkWidth);
                }
            }
        }

        //全てのチャンクより必要なチャンクを返す
        static public Chunk GetChunk(int x, int z)
        {
            string tmpstr = x + "_" + z;
            if (allChunkData.ContainsKey(tmpstr))
            {
                return allChunkData[tmpstr];
            }
            return null;
        }
    }

    /// <summary>
    /// クラス「チャンク」
    /// </summary>
    public class Chunk
    {
        //チャンクの位置
        public int indexX, indexZ;

        List<int> blocks = new List<int>();

        List<Vector3> verticeList = new List<Vector3>();
        List<int> faceList = new List<int>();
        int faceCount;

        //チャンク生成時にindex(チャンクの位置)を格納
        public Chunk(int x, int z)
        {
            indexX = x;
            indexZ = z;

            SetBlock();
        }
        //ブロックの設定
        void SetBlock()
        {
            for (int x = 0; x < ChunkManager.chunkWidth; x++)
            {
                for (int z = 0; z < ChunkManager.chunkWidth; z++)
                {
                    //パーリンノイズを使用
                    float tmpx = (x + indexX * ChunkManager.chunkWidth) + ChunkManager.worldSeed;
                    float tmpz = (z + indexZ * ChunkManager.chunkWidth) + ChunkManager.worldSeed;
                    //32(変化するタイミング、値が大きいと徐々に変化),12(地形の高さ、値が大きいと高低差が激しくなる)はハードコーディングしてしまっています、値を調節してみてください。
                    float noise = Mathf.PerlinNoise(tmpx / 32, tmpz / 32) * 12;

                    for (int y = 0; y < ChunkManager.maxHeight; y++)
                    {
                        //0は未定義、1は空気、2はキューブ
                        //今回は空気に触れた面だけ表示するようにする
                        if (noise < y)
                        {
                            blocks.Add(1);
                        }
                        else
                        {
                            blocks.Add(2);
                        }
                    }

                }
            }
        }

        public Mesh BuildVisualMesh()
        {
            MeshCreater();

            Mesh mesh = new Mesh();

            mesh.Clear();

            mesh.SetVertices(verticeList);

            mesh.SetTriangles(faceList, 0);

            mesh.RecalculateNormals();

            return mesh;
        }

        void MeshCreater()
        {
            int rawIndex;

            for (int x = 0; x < ChunkManager.chunkWidth; x++)
            {
                for (int y = 0; y < ChunkManager.maxHeight; y++)
                {
                    for (int z = 0; z < ChunkManager.chunkWidth; z++)
                    {
                        rawIndex = ConvertBlockIndex(x, y, z);
                        //今後、水などを追加したときの為に変更、自身のブロックナンバーを送る
                        MeshCreater_Cube(blocks[rawIndex], x, y, z);
                    }
                }
            }

        }
        //表示する面の情報を計算
        void MeshCreater_Cube(int blockType, int x, int y, int z)
        {
            //0は未定義、1は空気、2はキューブ
            if (blockType == 2)
            {
                if (CheckAdjacent(blockType, x, y, z, Direction.Front))
                {
                    verticeList.Add(new Vector3(x - 0.5f, y - 0.5f, z + 0.5f));
                    verticeList.Add(new Vector3(x + 0.5f, y - 0.5f, z + 0.5f));
                    verticeList.Add(new Vector3(x + 0.5f, y + 0.5f, z + 0.5f));
                    verticeList.Add(new Vector3(x - 0.5f, y + 0.5f, z + 0.5f));
                    AddFaceList();
                }

                if (CheckAdjacent(blockType, x, y, z, Direction.Back))
                {
                    verticeList.Add(new Vector3(x + 0.5f, y - 0.5f, z - 0.5f));
                    verticeList.Add(new Vector3(x - 0.5f, y - 0.5f, z - 0.5f));
                    verticeList.Add(new Vector3(x - 0.5f, y + 0.5f, z - 0.5f));
                    verticeList.Add(new Vector3(x + 0.5f, y + 0.5f, z - 0.5f));
                    AddFaceList();
                }

                if (CheckAdjacent(blockType, x, y, z, Direction.Top))
                {
                    verticeList.Add(new Vector3(x - 0.5f, y + 0.5f, z + 0.5f));
                    verticeList.Add(new Vector3(x + 0.5f, y + 0.5f, z + 0.5f));
                    verticeList.Add(new Vector3(x + 0.5f, y + 0.5f, z - 0.5f));
                    verticeList.Add(new Vector3(x - 0.5f, y + 0.5f, z - 0.5f));
                    AddFaceList();
                }

                if (CheckAdjacent(blockType, x, y, z, Direction.Bottom))
                {
                    verticeList.Add(new Vector3(x - 0.5f, y - 0.5f, z - 0.5f));
                    verticeList.Add(new Vector3(x + 0.5f, y - 0.5f, z - 0.5f));
                    verticeList.Add(new Vector3(x + 0.5f, y - 0.5f, z + 0.5f));
                    verticeList.Add(new Vector3(x - 0.5f, y - 0.5f, z + 0.5f));
                    AddFaceList();
                }

                if (CheckAdjacent(blockType, x, y, z, Direction.Left))
                {
                    verticeList.Add(new Vector3(x - 0.5f, y - 0.5f, z - 0.5f));
                    verticeList.Add(new Vector3(x - 0.5f, y - 0.5f, z + 0.5f));
                    verticeList.Add(new Vector3(x - 0.5f, y + 0.5f, z + 0.5f));
                    verticeList.Add(new Vector3(x - 0.5f, y + 0.5f, z - 0.5f));
                    AddFaceList();
                }

                if (CheckAdjacent(blockType, x, y, z, Direction.Right))
                {
                    verticeList.Add(new Vector3(x + 0.5f, y - 0.5f, z + 0.5f));
                    verticeList.Add(new Vector3(x + 0.5f, y - 0.5f, z - 0.5f));
                    verticeList.Add(new Vector3(x + 0.5f, y + 0.5f, z - 0.5f));
                    verticeList.Add(new Vector3(x + 0.5f, y + 0.5f, z + 0.5f));
                    AddFaceList();
                }
            }
        }

        //隣接するブロックより面を表示するかどうか計算
        bool CheckAdjacent(int blockType, int x, int y, int z, Direction direction)
        {

            switch (direction)
            {
                case Direction.Bottom:
                    y--;
                    break;
                case Direction.Top:
                    y++;
                    break;
                case Direction.Left:
                    x--;
                    break;
                case Direction.Right:
                    x++;
                    break;
                case Direction.Back:
                    z--;
                    break;
                case Direction.Front:
                    z++;
                    break;
            }

            int adjacentBlockType = GetBlockType(x, y, z);

            //0は未定義、1は空気、2はキューブ
            //空気に触れていたら表示
            if (adjacentBlockType == 1)
            {
                return true;
            }

            return false;
        }

        //隣接するブロックタイプを取得
        //チャンクの隅のブロックの時、隣接するチャンクのブロック情報も取得する
        public int GetBlockType(int x, int y, int z)
        {
            if (x < 0)
            {
                Chunk tmp = ChunkManager.GetChunk(indexX - 1, indexZ);
                if (tmp == null)
                {
                    return 0;
                }
                else
                {
                    return tmp.GetBlockType(x + ChunkManager.chunkWidth, y, z);
                }
            }
            else if (x >= ChunkManager.chunkWidth)
            {
                Chunk tmp = ChunkManager.GetChunk(indexX + 1, indexZ);
                if (tmp == null)
                {
                    return 0;
                }
                else
                {
                    return tmp.GetBlockType(x - ChunkManager.chunkWidth, y, z);
                }
            }
            else if (y < 0)
            {
                return 0;
            }
            else if (y >= ChunkManager.maxHeight)
            {
                return 0;
            }
            else if (z < 0)
            {
                Chunk tmp = ChunkManager.GetChunk(indexX, indexZ - 1);
                if (tmp == null)
                {
                    return 0;
                }
                else
                {
                    return tmp.GetBlockType(x, y, z + ChunkManager.chunkWidth);
                }
            }
            else if (z >= ChunkManager.chunkWidth)
            {
                Chunk tmp = ChunkManager.GetChunk(indexX, indexZ + 1);
                if (tmp == null)
                {
                    return 0;
                }
                else
                {
                    return tmp.GetBlockType(x, y, z - ChunkManager.chunkWidth);
                }
            }
            return GetBlockTypeSimple(x, y, z);
        }

        void AddFaceList()
        {
            faceList.Add(faceCount + 0);
            faceList.Add(faceCount + 1);
            faceList.Add(faceCount + 3);
            faceList.Add(faceCount + 1);
            faceList.Add(faceCount + 2);
            faceList.Add(faceCount + 3);

            faceCount += 4;
        }

        public int GetBlockTypeSimple(int x, int y, int z)
        {
            return blocks[ConvertBlockIndex(x, y, z)];
        }

        public static int ConvertBlockIndex(int x, int y, int z)
        {
            return x * ChunkManager.chunkWidth * ChunkManager.maxHeight + z * ChunkManager.maxHeight + y;
        }
    }
}

②空のゲームオブジェクトをシーンに作成してChunkManager.csをアタッチしてください

③マテリアルが必要なので右クリック->Create->Materialを選択し、名前をCubeMaterialとしてください。Materialの設定はそのままで大丈夫です。

④CubeMaterialをChunkManager.csにアタッチしてください

④シーンに存在するMain CameraのClear FlagsをSolid Color、Directional LightのShadow TypeをNo Shadowsにしましょう


ここまで作成したものをパッケージとし、Googleドライブに保存しておきました。 そこからダウンロードしてみてください。

UnityでMinecraft風ゲームを作る - Google ドライブ


f:id:zekutasiki:20190102132804p:plain
実行結果


前回よりclass「ChunkManager」が追加され、class「ChunkManager」よりclass「Chunk」を生成しています

パーリンノイズを使用しているので Minecraft風の地形が見えてきましたね。

0は未定義、1は空気、2はキューブにしました。 今回は空気に触れた面だけ表示するようにしました。

class「Chunk」のGetBlockTypeが変更になりました。隣接するチャンクのブロック情報も取得するよう変更しました。

今回は以上になります。 次回はこれを無限マップにしようと思います。