kamihi のブログ

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

UnityでMinecraft風ゲームを作る その6「ブロックを置いたり消したりする」

今回はブロックを置いたり消したりできるようにしたいと思います。

原理はレイを飛ばして当たった位置のチャンクのブロック情報を変更したらチャンクのMeshを毎回再生成しています。


ソースや設定方法は長くなるので、Googleドライブに保存しておきました。 そこからダウンロードしてみてください。

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


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

左クリックでブロックを消す、右クリックでブロックを置けます。 置くブロックは土ブロック固定です。


スクリプトの主な変更は以下の通りです

  • Chunk.cs ブロックの変更処理とメッシュの更新処理を追加
  • ChunkManager.cs 座標計算系を追加しました。
  • EnumData.cs 変更なし
  • PlayerControl.cs 変更なし
  • TargetControl.cs カメラからレイを放ち、当たった箇所にブロック置くor消すの処理を追加

TargetControl.cs

        //画面の中心からレイを放ち、当たった位置から、選択されたブロックの座標、隣のブロックの座標を計算
        void ChangeRayHitBlock()
        {

            RaycastHit cameraHit;
            Vector3 center = new Vector3(Screen.width / 2, Screen.height / 2);
            Ray ray = Camera.main.ScreenPointToRay(center);

            if (Physics.Raycast(ray , out cameraHit, 6))
            {
                //レイの当たった方向を取得
                Direction hitDirection = CalculateFaceDirectionHit(cameraHit);
                //選択されたブロックの座標を計算
                Vector3 hitPoint = new Vector3(0, 0, 0);
                hitPoint.x = (int)Math.Round(cameraHit.point.x, MidpointRounding.AwayFromZero);
                hitPoint.y = (int)Math.Round(cameraHit.point.y, MidpointRounding.AwayFromZero);
                hitPoint.z = (int)Math.Round(cameraHit.point.z, MidpointRounding.AwayFromZero);
                switch (hitDirection)
                {
                    case Direction.Right:
                        //この計算は、中途半端な位置でもちゃんと座標を計算できるようにしている。今はあまり関係ない。
                        hitPoint.x = (int)Math.Round(cameraHit.point.x + 0.99f, MidpointRounding.AwayFromZero);
                        hitPoint.x--;
                        break;
                    case Direction.Front:
                        hitPoint.z = (int)Math.Round(cameraHit.point.z + 0.99f, MidpointRounding.AwayFromZero);
                        hitPoint.z--;
                        break;
                    case Direction.Top:
                        hitPoint.y = (int)Math.Round(cameraHit.point.y + 0.99f, MidpointRounding.AwayFromZero);
                        hitPoint.y--;
                        break;
                    case Direction.Left:
                        hitPoint.x = (int)Math.Round(cameraHit.point.x - 0.99f, MidpointRounding.AwayFromZero);
                        hitPoint.x++;
                        break;
                    case Direction.Back:
                        hitPoint.z = (int)Math.Round(cameraHit.point.z - 0.99f, MidpointRounding.AwayFromZero);
                        hitPoint.z++;
                        break;
                    case Direction.Bottom:
                        hitPoint.y = (int)Math.Round(cameraHit.point.y - 0.99f, MidpointRounding.AwayFromZero);
                        hitPoint.y++;
                        break;
                }
                //選択したブロックの位置からそのブロックが所属するチャンクを取得
                Chunk selectChunk = ChunkManager.GetChunk_World(hitPoint.x, hitPoint.z);
                //ヒットポイント(ワールド座標)からチャンクのローカルな座標に変換
                Vector3 selectIndex = ChunkManager.RestrictIndex(hitPoint);

                //隣接するブロック位置を取得
                Vector3 ajactWorldIndex = ChunkManager.GetAdjacentIndex(hitPoint, hitDirection);
                //隣接するブロックが所属するチャンクを取得
                Chunk ajactChunk = ChunkManager.GetChunk_World(ajactWorldIndex.x, ajactWorldIndex.z);
                //隣のブロック位置(ワールド座標)からチャンクのローカルな座標に変換
                Vector3 ajactIndex = ChunkManager.RestrictIndex(ajactWorldIndex);

                if (Input.GetMouseButtonDown(0))
                {
                    //選択した位置を1(空)にする、ブロックを消す
                    selectChunk.ChangeBlock((int)selectIndex.x, (int)selectIndex.y, (int)selectIndex.z, 1);
                }

                if (Input.GetMouseButtonDown(1))
                {
                    //プレイヤーから近い時、ブロックを置けない
                    if(1f < Vector3.Distance(transform.position, ajactWorldIndex))
                    {
                        //3(土ブロック)を隣接した位置に置く
                        ajactChunk.ChangeBlock((int)ajactIndex.x, (int)ajactIndex.y, (int)ajactIndex.z, 3);
                    }
                }
            }
        }

        //レイの当たった方向を計算
        Direction CalculateFaceDirectionHit(RaycastHit data)
        {
            float threshhold = 0.25f;

            if (data.normal.x > threshhold)
                return Direction.Right;
            else if (data.normal.x < -threshhold)
                return Direction.Left;
            else if (data.normal.z > threshhold)
                return Direction.Front;
            else if (data.normal.z < -threshhold)
                return Direction.Back;
            else if (data.normal.y > threshhold)
                return Direction.Top;
            else if (data.normal.y < -threshhold)
                return Direction.Bottom;

            return Direction.Last;
        }

レイから位置を計算する箇所です。


ChunkManager.cs

        //ワールド座標からチャンクを取得
        static public Chunk GetChunk_World(float worldX, float worldZ)
        {

            int cx = (int)(Math.Floor(worldX / chunkWidth));
            int cz = (int)(Math.Floor(worldZ / chunkWidth));

            return GetChunk(cx, cz);
        }
        //ワールド座標からチャンクのローカル座標に変換
        static public Vector3 RestrictIndex(Vector3 oldIndex)
        {
            int tmpX = (int)oldIndex.x % chunkWidth;
            if (tmpX < 0)
            {
                tmpX += chunkWidth;
            }
            int tmpZ = (int)oldIndex.z % chunkWidth;
            if (tmpZ < 0)
            {
                tmpZ += chunkWidth;
            }
            return new Vector3(tmpX, oldIndex.y, tmpZ);
        }
        //方向に対する座標の計算
        static public Vector3 GetAdjacentIndex(Vector3 index, Direction direction)
        {

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

座標計算系あたりの修正です。


Chunk.cs

        //指定の座標のブロックを変える
        public void ChangeBlock(int x, int y, int z, int blockIndex)
        {
            //ブロックリストのインデックスに変換
            int tmpint = ConvertBlockIndex(x, y, z);
            //ブロックを変更する
            blocks[tmpint] = blockIndex;
            //メッシュの更新
            RefreshMesh();

            //チャンクの隅の時、隣のチャンクもリフレッシュする
            if (x == ChunkManager.chunkWidth - 1)
            {
                Chunk tmp = ChunkManager.GetChunk(indexX + 1, indexZ);
                if (tmp != null)
                {
                    ChunkManager.GetChunk(indexX + 1, indexZ).RefreshMesh();
                }
            }
            if (x == 0)
            {
                Chunk tmp = ChunkManager.GetChunk(indexX - 1, indexZ);
                if (tmp != null)
                {
                    ChunkManager.GetChunk(indexX - 1, indexZ).RefreshMesh();
                }
            }
            if (z == ChunkManager.chunkWidth - 1)
            {
                Chunk tmp = ChunkManager.GetChunk(indexX, indexZ + 1);
                if (tmp != null)
                {
                    ChunkManager.GetChunk(indexX, indexZ + 1).RefreshMesh();
                }
            }
            if (z == 0)
            {
                Chunk tmp = ChunkManager.GetChunk(indexX, indexZ - 1);
                if (tmp != null)
                {
                    ChunkManager.GetChunk(indexX, indexZ - 1).RefreshMesh();
                }
            }
        }
        //メッシュの更新
        public void RefreshMesh()
        {
            Mesh tmpmesh = BuildVisualMesh();
            gameObject.GetComponent<MeshFilter>().mesh = tmpmesh;
            gameObject.GetComponent<MeshCollider>().sharedMesh = tmpmesh;
        }

ブロックの更新処理とメッシュの更新処理の箇所です。

次回は高さ制限を無くしていこうと思います。