Unityで動的に地形ポリゴン生成&Collider生成

 

目標

いわゆる穴掘りゲームで、3Dポリゴン表示かつ、いい感じに掘りたい。操作性は2Dでいい。いやこれMinecraftじゃん。 

f:id:kametaro49:20201215154241p:plain

イメージ

掘ると何が起こる?

ポリゴンの構成要素である頂点、面(辺は面に付属する)から考えると、穴を掘って洞窟的になると頂点も面も増えます。一般的な街づくりゲームとかの地形編集は、頂点を上下させるだけなので、洞窟は作れません。

f:id:kametaro49:20201215144734p:plain

左:単純に頂点を上下 右:頂点・面を増加

In Unity

ブロック的な地形になるので、キューブを大量に配置するだけでも一応作れます。しかし、大量のオブジェクトを扱うと重いです。そのためオブジェクトとしては1つで、そのポリゴンを編集することで表現したいですね。

Unityはどのようにポリゴンを扱っている?

コンポーネントとして、MeshFilter、MeshRendererがありますね。MeshFilterがMesh(ポリゴン)のデータを保持し、MeshRendererがそのレンダリング設定を担当しているようです。つまりMeshFilterが保持しているMeshを編集すればよいわけですね。

Meshには、まず頂点配列であるVertices、面を頂点番号から保持しているtriangles(すべて三角面)、面がどちらを向いているかを示す法線のnormals、そしてテクスチャマッピングに使うuvがあります。これらを編集です…(多いわ)

実装(どうやった?)

最初は実際の見た目と同じように、最小限の頂点で構成しようとしていました。しかし、そうすると掘るたびに頂点を追加し、面も貼らなくてはなりません。頂点番号と面の対応付けが難しくて実装できんかった。

頂点

イメージの通り、奥行きは手前と奥の2段階です。そのため2層のグリッド状に頂点を事前に配置することにしました。使わない頂点には面を付けなければ問題ありません。掘るたびに頂点を更新する必要もなくなります。uvは頂点と対応しているのでついでにめちゃくちゃを設定しておきます。

        List<Vector3vertices = new List<Vector3>(4 * Size * Size);
        List<Vector2uvs = new List<Vector2>();

        for (int d = 0d < 4d++)
        {
            for (int y = 0y < Sizey++)
            {
                for (int x = 0x < Sizex++)
                {
                    vertices.Add(vectormap[dxy]);
                    uvs.Add(new Vector2(Random.valueRandom.value));
                }
            }
        }
        mesh.SetVertices(vertices);
        mesh.SetUVs(0uvs);

vectormapには単純に頂点座標のVector3が入っています。4層になっているのは後述。

面貼りには、四角形の面を貼りたいところですが、全て三角形でなければなりません。meshに四角形で指定すると三角形に変換して設定してくれるメソッドがあるので使います。

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

        for (int y = 0y <= RealSizey++)
        {
            for (int x = 0x <= RealSizex++)
            {
                if (IsDigged(xy))
                {
                    meshing.Add(Indexer(1xy));
                    meshing.Add(Indexer(1x + 1y));
                    meshing.Add(Indexer(1x + 1y + 1));
                    meshing.Add(Indexer(1xy + 1));

                    if (!IsDigged(x - 1y))
                    {
                        meshing.Add(Indexer(3xy));
                        meshing.Add(Indexer(3xy + 1));
                        meshing.Add(Indexer(2xy + 1));
                        meshing.Add(Indexer(2xy));
                    }
                    if (!IsDigged(xy - 1))
                    {
             省略
                    }
                }
                else
                {
             省略

                    if (IsDigged(x - 1y))
                    {
             省略
                    }
                    if (IsDigged(xy - 1))
                    {
             省略
                    }

                }
            }
        }

        mesh.SetIndices(meshing.ToArray(), MeshTopology.Quads0);

まさにゴリ押し。IsDiggedとIndexerは自作関数です。Indexerは頂点番号が帰ってきますね。側面があるかどうかには非常に多くの場合分けが発生します…

法線

法線は、便利なメソッドを実行すればおk 

mesh.RecalculateNormals();

なぜ4層?

f:id:kametaro49:20201215161742p:plain

2層で表現・4層で表現

1つの場所に2つの頂点を配置し、側面と使い分けています。こうするとスムージングが働かなくなるようです。ちなみに、Unityのデフォルトキューブも8頂点ではなく24頂点。

Collider生成

残念ながら、いい感じにCollider2Dを自動生成してくれるものがありません!(collider3Dは知らない)

Colliderも大量生成すると重いので、まとめてColliderを生成したいと思います。EdgeCollider2Dを使用。これも頂点配列をセットすることで実現できます。EdgeCollider1つにつき一本の線なので、まとまりごとに1つのColliderですね。迷路を解くときみたいに右手方を実装しました。16通り場合分け…

line = gameObject.AddComponent<EdgeCollider2D>();
List<Vector2points = new List<Vector2>();
省略
line.points = points.ToArray();

 

完成

f:id:kametaro49:20201215163927p:plain

f:id:kametaro49:20201215163955p:plain

今回の方法だと右下ちょっとバグる

f:id:kametaro49:20201215164045p:plain

ちゃんと3D

 Unity2019.2.4f1