1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

[Unity] キューブ状のメッシュ生成をパスに沿って行う

Last updated at Posted at 2023-02-19

スクリーンショット_20230220_005057.png
ゲーム制作の過程でパスに沿ったオブジェクトを作る必要があったため記事にします。
少し掘り下げてメッシュ生成の基本となる記事も書いたのでよかった見てください。

パスに沿った頂点の位置

パスに沿ってメッシュを生成する場合、最初に課題となるのが 「頂点をどのようにとるか」 になります。
スクリーンショット_20230219_220944.png
図のように白い球がパスの頂点だとして、灰色の球の位置に頂点を置くことが理想です。
一番簡単な考え方としてはパスの頂点を中心に左右にオフセットした位置を頂点に置くことです。

            Vector3 lowerLeft = position + Vector3.left;
            Vector3 lowerRight = position + Vector3.right;

ただし、この場合は真っすぐ進む分には問題ありませんが、90度以上曲がった場合破綻してしまいます。
つまり、回転を考慮してあげなくてはいけません。

2点間の向きはQuaternion.LookRotationを使って求めることができます。

            Vector3 direction = (secondPos - firstPos).normalized;
            Quaternion rot = Quaternion.LookRotation(direction);

            Vector3 lowerLeft = positions[i] + rot * Vector3.left;
            Vector3 lowerRight = positions[i] + rot * Vector3.right;

こうすることで白い球がどのような位置にあっても図の灰色の球のように頂点をとることができました。

GeneratePathCube.cs
using System.Collections.Generic;
using UnityEngine;

public class GeneratePathCube : MonoBehaviour
{
    class Square
    {
        public Vector3 lowerLeft;
        public Vector3 upperLeft;
        public Vector3 lowerRight;
        public Vector3 upperRight;
    }

    [SerializeField] Transform[] PathPosition;
    [SerializeField] Material Material;
    [SerializeField] float Width = 1f;
    [SerializeField] float Height = 2f;
    [SerializeField] bool IsDrawGizmo = false;

    private Square[] PathSquares()
    {
        int length = PathPosition.Length;
        var positions = new Vector3[length];
        var squares = new Square[length];

        for( int i = 0; i < length; i++)
        {
            positions[i] = PathPosition[i].position;
        }

        for (int i = 0; i < length; i++)
        {
            Vector3 firstPos, secondPos;

            if (i == 0)
            {
                firstPos = positions[0];
                secondPos = positions[1];
            }
            else
            {
                firstPos = positions[i - 1];
                secondPos = positions[i];
            }

            Vector3 direction = (secondPos - firstPos).normalized;
            Quaternion rot = Quaternion.LookRotation(direction);

            Vector3 lowerLeft = positions[i] + rot * Vector3.left * Width;
            Vector3 lowerRight = positions[i] + rot * Vector3.right * Width;

            var square = new Square()
            {
                lowerLeft = lowerLeft,
                lowerRight = lowerRight,
                upperLeft = lowerLeft + Vector3.up * Height,
                upperRight = lowerRight + Vector3.up * Height,
            };

            squares[i] = square;
        }

        return squares;
    }

    private void OnDrawGizmos()
    {
        if((PathPosition == null) || (PathPosition.Length < 2) || !IsDrawGizmo)
        {
            return;
        }

        var squares = PathSquares();

        for (int i = 0; i < squares.Length; i++)
        {
            Gizmos.DrawSphere(squares[i].lowerLeft, 0.2f);
            Gizmos.DrawSphere(squares[i].lowerRight, 0.2f);
            Gizmos.DrawSphere(squares[i].upperLeft, 0.2f);
            Gizmos.DrawSphere(squares[i].upperRight, 0.2f);
        }
    }

パスに沿った頂点

キューブを作る考え方とあまり変わりませんが、正面と背面以外は今の頂点ひとつ前の頂点を結んだ面を作るだけです。
スクリーンショット_20230219_223838.png

        for (int i = 0; i < squares.Length; i++)
        {
            int currrentLowerLeft = (i * 4) + 0;
            int currrentLowerRight = (i * 4) + 1;
            int currrentUpperLeft = (i * 4) + 2;
            int currrentUpperRight = (i * 4) + 3;

            if (i == 0)
            {
                // 正面
                triangles.AddRange(SquareToTriangles(currrentLowerLeft, currrentLowerRight, currrentUpperLeft, currrentUpperRight));
            }
            else
            {
                int lastLowerLeft = ((i - 1) * 4) + 0;
                int lastLowerRight = ((i - 1) * 4) + 1;
                int lastUpperLeft = ((i - 1) * 4) + 2;
                int lastUpperRight = ((i - 1) * 4) + 3;

                // 左側面
                triangles.AddRange(SquareToTriangles(currrentLowerLeft, lastLowerLeft, currrentUpperLeft, lastUpperLeft));
                // 右側面
                triangles.AddRange(SquareToTriangles(lastLowerRight, currrentLowerRight, lastUpperRight, currrentUpperRight));
                // 上側面
                triangles.AddRange(SquareToTriangles(lastUpperLeft, lastUpperRight, currrentUpperLeft, currrentUpperRight));
                // 下側面
                triangles.AddRange(SquareToTriangles(lastLowerRight, lastLowerLeft, currrentLowerRight, currrentLowerLeft));

                if (i == squares.Length - 1)
                {
                    // 背面
                    triangles.AddRange(SquareToTriangles(currrentLowerRight, currrentLowerLeft, currrentUpperRight, currrentUpperLeft));
                }
            }
        }

トライアングルとメッシュ生成の全体像は以下になります。

GeneratePathCube.cs
    void Start()
    {
        GenerateMesh();
    }

    public void GenerateMesh()
    {
        var squares = PathSquares();
        var mesh = new Mesh();

        var vertices = new List<Vector3>();
        var triangles = new List<int>();
        var uvs = new List<Vector2>();

        for (int i = 0; i < squares.Length; i++)
        {
            vertices.Add(squares[i].lowerLeft);
            vertices.Add(squares[i].lowerRight);
            vertices.Add(squares[i].upperLeft);
            vertices.Add(squares[i].upperRight);

            uvs.Add(new Vector2(0, 0));
            uvs.Add(new Vector2(1, 0));
            uvs.Add(new Vector2(0, 1));
            uvs.Add(new Vector2(1, 1));

            int currrentLowerLeft = (i * 4) + 0;
            int currrentLowerRight = (i * 4) + 1;
            int currrentUpperLeft = (i * 4) + 2;
            int currrentUpperRight = (i * 4) + 3;

            if (i == 0)
            {
                // 正面
                triangles.AddRange(SquareToTriangles(currrentLowerLeft, currrentLowerRight, currrentUpperLeft, currrentUpperRight));
            }
            else
            {
                int lastLowerLeft = ((i - 1) * 4) + 0;
                int lastLowerRight = ((i - 1) * 4) + 1;
                int lastUpperLeft = ((i - 1) * 4) + 2;
                int lastUpperRight = ((i - 1) * 4) + 3;

                // 左側面
                triangles.AddRange(SquareToTriangles(currrentLowerLeft, lastLowerLeft, currrentUpperLeft, lastUpperLeft));
                // 右側面
                triangles.AddRange(SquareToTriangles(lastLowerRight, currrentLowerRight, lastUpperRight, currrentUpperRight));
                // 上側面
                triangles.AddRange(SquareToTriangles(lastUpperLeft, lastUpperRight, currrentUpperLeft, currrentUpperRight));
                // 下側面
                triangles.AddRange(SquareToTriangles(lastLowerRight, lastLowerLeft, currrentLowerRight, currrentLowerLeft));

                if (i == squares.Length - 1)
                {
                    // 背面
                    triangles.AddRange(SquareToTriangles(currrentLowerRight, currrentLowerLeft, currrentUpperRight, currrentUpperLeft));
                }
            }
        }

        mesh.SetVertices(vertices);
        mesh.SetTriangles(triangles, 0);
        mesh.SetUVs(0, uvs);
        mesh.RecalculateNormals();

        var obj = new GameObject();
        obj.name = "GenerateMesh";
        obj.transform.parent = transform;
        var meshFilter = obj.AddComponent<MeshFilter>();
        var meshRenderer = obj.AddComponent<MeshRenderer>();
        var meshCollider = obj.AddComponent<MeshCollider>();

        meshFilter.mesh = mesh;
        meshCollider.sharedMesh = mesh;
        meshRenderer.material = Material;
    }

    private List<int> SquareToTriangles(int lowerLeft, int lowerRight, int upperLeft, int upperRight)
    {
        var triangles = new List<int>();

        triangles.Add(lowerLeft);   // 0
        triangles.Add(upperLeft);   // 2
        triangles.Add(lowerRight);  // 1

        triangles.Add(upperLeft);   // 2
        triangles.Add(upperRight);  // 3
        triangles.Add(lowerRight);  // 1

        return triangles;
    }
1
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?