マインクラフト風の地形

【Unity】地形のメッシュを編集する

Unityでマインクラフト風の地形を作り、STYLYへアップロードするまでの手順を解説します。 本記事は、マインクラフト風の地形を作りSTYLYへアップロードする ー前編ー の続きです。  

前回までのまとめ

前編では地形の見た目を作成したので、後編はSTYLYにアップロードし公開するための仕上げ作業になります。 仕上げ作業は以下の3ステップで進めます。

  • メッシュの結合によるパフォーマンスの改善
  • 自動生成したメッシュの保存
  • 保存したメッシュのprefab化とアップロード

それでは、上記の手順を詳しく説明します。  

メッシュの結合によるパフォーマンスの改善

準備

  • 地形生成スクリプトの修正

「VoxelGround.cs」を以下のように修正します。

using UnityEngine;

public class VoxelGround : MonoBehaviour
{

    private float sizeX = 50f;
    private float sizeY = 10f;
    private float sizeZ = 50f;
    private float sizeW = 17f;

    private void Awake()
    {
        var material = this.GetComponent<MeshRenderer>().material;
        for (float x = 0; x < sizeX; x++)
        {
            for (float z = 0; z < sizeZ; z++)
            {
                GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
                cube.transform.SetParent(transform);
                cube.GetComponent<MeshRenderer>().material = material;

                float noise = Mathf.PerlinNoise(x / sizeW, z / sizeW);
                float y = Mathf.Round(sizeY * noise);
                cube.transform.localPosition = new Vector3(x, y, z);

                SetUV(cube);
            }
        }
        Combine();
        transform.localPosition = new Vector3(-sizeX / 2, 0, -sizeZ / 2);
    }

    private void SetUV(GameObject cube)
    {
        cube.GetComponent<MeshFilter>().mesh.uv = GetBlockUVs(2, 15);

        if (cube.transform.position.y > sizeY * 0.3f)
        {
            cube.GetComponent<MeshFilter>().mesh.uv = GetBlockUVs(0, 15); //山
        }
        else if (cube.transform.position.y > sizeY * 0.2f)
        {
            cube.GetComponent<MeshFilter>().mesh.uv = GetBlockUVs(1, 2); //水面
        }
        else if (cube.transform.position.y > sizeY * 0.1f)
        {
            cube.GetComponent<MeshFilter>().mesh.uv = GetBlockUVs(15, 0); //溶岩
        }
    }

    private Vector2[] GetBlockUVs(float tileX, float tileY)
    {

        float pixelSize = 16;
        float tilePerc = 1 / pixelSize;

        float umin = tilePerc * tileX;
        float umax = tilePerc * (tileX + 1);
        float vmin = tilePerc * tileY;
        float vmax = tilePerc * (tileY + 1);

        Vector2[] blockUVs = new Vector2[24];

        //-X
        blockUVs[2] = new Vector2(umax, vmax);
        blockUVs[3] = new Vector2(umin, vmax);
        blockUVs[0] = new Vector2(umax, vmin);
        blockUVs[1] = new Vector2(umin, vmin);

        //+Y
        blockUVs[4] = new Vector2(umin, vmin);
        blockUVs[5] = new Vector2(umax, vmin);
        blockUVs[8] = new Vector2(umin, vmax);
        blockUVs[9] = new Vector2(umax, vmax);

        //-Z
        blockUVs[23] = new Vector2(umax, vmin);
        blockUVs[21] = new Vector2(umin, vmax);
        blockUVs[20] = new Vector2(umin, vmin);
        blockUVs[22] = new Vector2(umax, vmax);

        //+Z
        blockUVs[19] = new Vector2(umax, vmin);
        blockUVs[17] = new Vector2(umin, vmax);
        blockUVs[16] = new Vector2(umin, vmin);
        blockUVs[18] = new Vector2(umax, vmax);

        //-Y
        blockUVs[15] = new Vector2(umax, vmin);
        blockUVs[13] = new Vector2(umin, vmax);
        blockUVs[12] = new Vector2(umin, vmin);
        blockUVs[14] = new Vector2(umax, vmax);

        //+X
        blockUVs[6] = new Vector2(umin, vmin);
        blockUVs[7] = new Vector2(umax, vmin);
        blockUVs[10] = new Vector2(umin, vmax);
        blockUVs[11] = new Vector2(umax, vmax);

        return blockUVs;
    }

    private void Combine()
    {
        //メッシュ統合
        MeshFilter[] meshFilters = GetComponentsInChildren<MeshFilter>();
        CombineInstance[] combine = new CombineInstance[meshFilters.Length];

        int i = 0;
        while (i < meshFilters.Length)
        {
            combine[i].mesh = meshFilters[i].sharedMesh;
            combine[i].transform = meshFilters[i].transform.localToWorldMatrix;
            meshFilters[i].gameObject.SetActive(false);
            i++;
        }

        transform.GetComponent<MeshFilter>().mesh = new Mesh();
        transform.GetComponent<MeshFilter>().mesh.CombineMeshes(combine, true);
        transform.gameObject.SetActive(true);
    }

}

 

結果確認

「Ctrl+P」を押して、エディター画面に以下のような地形が生成されれば成功です。

2500個のキューブを一つのメッシュに結合

2500個のキューブを一つのメッシュに結合

 

解説

  • スクリプトの修正について
    private void Combine()
    {
        //メッシュ結合
        MeshFilter[] meshFilters = GetComponentsInChildren<MeshFilter>();              //Groundオブジェクト配下にできたキューブのメッシュ
        CombineInstance[] combine = new CombineInstance[meshFilters.Length];           //結合するメッシュを格納用する配列

        int i = 0;
        while (i < meshFilters.Length)
        {
            combine[i].mesh = meshFilters[i].sharedMesh;                               //各キューブのメッシュを結合用メッシュにコピーする
            combine[i].transform = meshFilters[i].transform.localToWorldMatrix;        //各キューブの座標を結合用メッシュにコピーする
            meshFilters[i].gameObject.SetActive(false);                                //不要になったキューブオブジェクトを無効化しておく
            i++;
        }

        transform.GetComponent<MeshFilter>().mesh = new Mesh();                        //Groundオブジェクトに新規メッシュを作成
        transform.GetComponent<MeshFilter>().mesh.CombineMeshes(combine, true);        //メッシュを結合する
        transform.gameObject.SetActive(true);                                          //Groundオブジェクトを有効化する
    }

ここでは生成した2500個のキューブのメッシュを一つに結合します。 実装内容の詳細はコメントに記入した通りです。 上記スクリプトにより、2500個のキューブオブジェクトを一つのオブジェクトに統合します。  

自動生成したメッシュの保存

準備

  • MeshSaverの導入

以下のリポジトリからMeshSaverというツールを導入します。 Unity-MeshSaver 取込ステップは簡単に3ステップで

  1. スクリプトフォルダ内に「Editor」というフォルダーを作成します。(注意:Editor以外の名前にすると、STYLYへのアップロードに失敗します。)
  2. Editorフォルダ内にMeshSaverEditorという名前のスクリプトを作成します。
  3. 以下のURLからソースコードをコピペします。

MeshSaverEditor

MeshSaverの導入

MeshSaverの導入

 

  • メッシュの保存

「Ctrl+P」を押して、Unityエディター上に地形を生成し、地形をクリックで選択します。

地形を選択

地形を選択

  選択中のオブジェクトの「Ground(Mesh Filter)」コンポーネントのオプションの中から「Save Mesh」を選択します。   メッシュの保存先を指定します。(ここではGround.Assetとして保存します。)

メッシュの保存先を指定

メッシュの保存先を指定

 

結果確認

一旦Unityエディタを停止し、Groundオブジェクトを選択後、 Cube(Mesh Filter)のMeshプロパティ指定ボタンをクリックします。

Meshプロパティを開く

Meshプロパティを開く

  先ほど保存した「Ground.Asset」を選択すると、Unityエディター上に地形メッシュが表示されます。 Unityエディターが停止中でもメッシュが表示できていれば成功です。

保存した地形メッシュを指定する

保存した地形メッシュを指定する

 

保存したメッシュのprefab化とアップロード

準備

  • Prefab化するゲームオブジェクトの作成

Prefabにするキューブを一つ作成します。

Prefabにするキューブ

Prefabにするキューブ

  作成したキューブを選択し、以下の2点を修正します。

  1. Cube(Mesh Filter)コンポーネントのMeshプロパティを「Ground」に変更する
  2. Mesh RendererコンポーネントのMaterialプロパティを「terrain 6.26.56 PM」に変更する
メッシュとマテリアルを変更する

メッシュとマテリアルを変更する

 

  • ゲームオブジェクトのPrefab化

作成したオブジェクトをPrefabsフォルダにドラッグアンドドロップします。

キューブをPrefab化する

キューブをPrefab化する

 

  • PrefabのSTYLYへのアップロード

PrefabをSTYLYにアップロードするには、アカウントの設定やプラグインのインストール等の準備が必要です。 詳細は以下のリンク先を参照して下さい。 UnityからSTYLYにアセットをアップロードする方法  

結果確認

STYLYエディターのMyModelsフォルダを見ると、アップロードしたPrefabが選択可能です。

アップロードしたPrefabの確認

アップロードしたPrefabの確認

  最後に、デフォルトの地形表示をOFF → Cubeの表示位置を調整すれば、完成です。 本記事通りの手順で作成した地形であれば、X:-25、Y:-7.5、Z:-25で以下のように配置可能です。

STYLYに取り込んだMinecraft風の地形

STYLYに取り込んだマインクラフト風の地形

 

まとめ

以上、マインクラフト風の地形を作りSTYLYへアップロードする方法の紹介でした。 ここからさらに、 TiltBrushで洞窟や動物や植物を描いてみたり、 花火を上げてみたり、スーパーボールを爆発させてみたり、 STYLYで解説されてきた様々なアセットを組み合わせることで、さらに面白い世界が作れると思います。   Minecraft 公式製品ではありません。Mojang から承認されておらず、Mojang とは関係ありません。