kamihi のブログ

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

UnityでMinecraft風ゲームを作る その2「チャンクの作成」

今回はブロックをたくさん置いて、余計な面を削って、チャンクを作成してみましょう。

①CriateChunk.csを作成します。

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

//UnityでMinecraft風ゲームを作る その2「チャンクの作成」
namespace Sono2
{
    //方向の列挙型
    public enum Direction
    {
        Top = 0, Bottom, Right, Left, Front, Back, Last
    }

    public class CriateChunk : MonoBehaviour
    {
        //チャンクの幅
        static public int chunkWidth = 8;
        //チャンクの高さ
        static public int maxHeight = 8;
        //チャンクのブロック情報
        List<int> blocks = new List<int>();

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

        void Start()
        {
            GameObject obj = new GameObject("Chunk");
            obj.AddComponent<MeshRenderer>();
            obj.GetComponent<MeshRenderer>().material = cubeMaterial;
            obj.AddComponent<MeshFilter>();
            obj.GetComponent<MeshFilter>().mesh = BuildVisualMesh();

        }

        public Mesh BuildVisualMesh()
        {
            //先にリスト「blocks」を埋めておく
            SetBlock();

            MeshCreater();

            Mesh mesh = new Mesh();

            mesh.Clear();

            mesh.SetVertices(verticeList);

            mesh.SetTriangles(faceList, 0);

            mesh.RecalculateNormals();

            return mesh;
        }
        //リスト「blocks」に値を追加する
        void SetBlock()
        {
            for (int x = 0; x < chunkWidth; x++)
            {
                for (int z = 0; z < chunkWidth; z++)
                {
                    //yを後にしているのは次回パーリンノイズを使用するため
                    for (int y = 0; y < maxHeight; y++)
                    {
                        //0はブロックなし、1はブロックあり
                        blocks.Add(1);
                        //変更用
                        //blocks.Add(Random.Range(0, 2));
                    }
                }
            }
        }
        //メッシュ作成
        void MeshCreater()
        {
            for (int x = 0; x < chunkWidth; x++)
            {
                for (int y = 0; y < maxHeight; y++)
                {
                    for (int z = 0; z < chunkWidth; z++)
                    {
                        int rawIndex = ConvertBlockIndex(x, y, z);
                        if(blocks[rawIndex] == 1)
                        {
                            MeshCreater_Cube(x, y, z);
                        }
                    }
                }
            }

        }
        //キューブのメッシュ作成
        void MeshCreater_Cube(int x, int y, int z)
        {
            //隣のブロック情報を参照し、面の表示非表示を判定
            if (CheckAdjacent(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(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(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(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(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(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 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(ブロックなし)の時、true(面を表示)を返す
            if (adjacentBlockType == 0)
            {
                return true;
            }
            //false(ブロックあり)の時、見えない面になるので面を表示しな
            return false;
        }
        public int GetBlockType(int x, int y, int z)
        {
            //範囲外の0(ブロックなし)を返す
            if (x < 0)
            {
                return 0;
            }
            else if (x >= chunkWidth)
            {
                return 0;
            }
            else if (y < 0)
            {
                return 0;
            }
            else if (y >= maxHeight)
            {
                return 0;
            }
            else if (z < 0)
            {
                return 0;
            }
            else if (z >= chunkWidth)
            {
                return 0;
            }
            //範囲内の時、ブロックの情報を返す
            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 * chunkWidth * maxHeight + z * maxHeight + y;
        }

    }

}

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

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

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

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


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

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


f:id:zekutasiki:20190102085057p:plain

大きめのキューブが作成されましたね

補足

f:id:zekutasiki:20190111184948p:plain

上の図のように見えない面を作らないようにして頂点数を減らしています(頂点情報と面情報をListに書き込む前にCheckAdjacentで隣はキューブかどうか判定し、キューブなら見えない面が生まれるので作らないようにしています)

StatsのVertsが約1500(1.5k)≒4×8×8×6(キューブひとつの面×チャンクの幅×チャンクの高さ×チャンクの面の数)ですね

これをもし余計な面を削らないとすると

約13000になりますね(24×8×8×8(キューブひとつ×チャンクの幅×チャンクの幅×チャンクの高さ))

このように余計な面を表示しないことは非常に重要になってくると思います。


        //チャンクのブロック情報
        List<int> blocks = new List<int>();

Listを多次元(x,y,z)にせず1次元にしている理由は、その方が高速だからです。


0はブロックなし、1はブロックありにしたので、ソースのvoid SetBlock()のblocks.Add(1);をblocks.Add(Random.Range(0,2));とかにするとこうなります。

f:id:zekutasiki:20190102091127p:plain

次回はチャンクマネージャーを作ってさらにチャンクを沢山作り、キューブをたくさん置いてみましょう(・v・)