程序洞穴生成二(Procedural Cave Generation)

接着上一遍的文章接着讲程序洞穴生成。

后来我去Unity的官网看到了一篇也是讲程序洞穴生成的,官网教程的链接https://unity3d.com/cn/learn/tutorials/s/procedural-cave-generation-tutorial,我想视频作者应该是参考了官网的文章然后制作了视频,本次的内容是视频的2,3两课。

进入正题,之前我们用方块点的形式生成了随机的洞穴,这次我们将地图的信息转换成网格的形式生成到Scene视图中去。新建一个脚本MeshGenetaror,挂载在MapGenerator同一个对象下,并且为对象添加MehsFilter和MeshRenderer组件,并且创建一个材质赋值给Renderer。

那么首先,我们上一篇中的Map的二维数组一怎么样的规则转换成Mesh所需要的顶点信息和三角信息呢。这里先定义四个类,用于转换和储存Map的信息。先贴代码,然后根据代码一点一点的讲。

    /// <summary>
    /// 方形网格
    /// </summary>
    public class SquareGrid
    {
        public Square[,] squares;

        public SquareGrid(int[,] map, float squareSize)
        {
            //计算出控制点的x方向的数量和y方向的数量,其实就是二维数组的长宽
            int nodeCountX = map.GetLength(0);
            int nodeCountY = map.GetLength(1);

            //根据传进来的方形的边长计算出所有的点构成的面长和高(这里的长指的是x方向的长度,高指的是z方向的纵深)
            float mapWidth = nodeCountX * squareSize;
            float mapHeight = nodeCountY * squareSize;

            //创建控制点的二维数组
            ControlNode[,] controlNodes = new ControlNode[nodeCountX, nodeCountY];

            for (int x = 0; x < nodeCountX; x++)
            {
                for (int y = 0; y < nodeCountY; y++)
                {
                    //计算控制点的位置,并且做了偏移,让整体的中心在原点
                    Vector3 pos = new Vector3(-mapWidth * .5f + x * squareSize + squareSize * .5f, 0, -mapHeight * .5f  + y * squareSize + squareSize * .5f);
                    //如果是1说明是墙,就将active设置成true,不是则为false
                    controlNodes[x, y] = new ControlNode(pos, map[x, y] == 1, squareSize);
                }
            }

            //由于一个四边是有四个点构成,square的总量是nodeCountX - 1 * nodeCoungY - 1, 这里在纸上点出几个点再画四边形就能理解啦。
            squares = new Square[nodeCountX - 1, nodeCountY - 1];

            for (int x = 0; x < nodeCountX -1; x++)
            {
                for (int y = 0; y < nodeCountY - 1; y++)
                {
                    //初始化要按顺序传入topLeft, topRight, _bottomLeft, _bottomRight;
                    squares[x, y] = new Square(controlNodes[x, y + 1], controlNodes[x + 1, y + 1], controlNodes[x, y], controlNodes[x + 1, y]);
                }
            }
        }
    }

    /// <summary>
    /// 方形区域,记录包块四个角的控制点,和每条边的中心的点
    /// </summary>
    public class Square
    {
        public ControlNode topLeft, topRight, bottomRight, bottomLeft;
        public Node centreTop, centreRight, centreBottom, centreLeft;
        public int configuration;

        public Square(ControlNode _topLeft, ControlNode _topRight, ControlNode _bottomLeft, ControlNode _bottomRight)
        {
            topLeft = _topLeft;
            topRight = _topRight;
            bottomLeft = _bottomLeft;
            bottomRight = _bottomRight;

            centreTop = topLeft.right;
            centreLeft = bottomLeft.above;
            centreBottom = bottomLeft.right;
            centreRight = bottomRight.above;

            if (topLeft.active)
                configuration += 8;

            if (topRight.active)
                configuration += 4;

            if (bottomRight.active)
                configuration += 2;

            if (bottomLeft.active)
                configuration += 1;
        }
    }

    /// <summary>
    /// 点的基础类
    /// </summary>
    public class Node
    {
        public Vector3 position;
        public int vertexIndex = -1;

        public Node(Vector3 _position)
        {
            position = _position;
        }
    }

    /// <summary>
    /// 控制点(一个方形区域的四角)
    /// </summary>
    public class ControlNode : Node
    {
        public bool active;
        public Node above, right;

        public ControlNode(Vector3 _position, bool _active, float squareSize) : base(_position)
        {
            active = _active;
            above = new Node(position + Vector3.forward * squareSize * .5f);
            right = new Node(position + Vector3.right * squareSize * .5f);
        }
    }

先从Node类开始讲,Node类就是一个点的最基础的类,记录了点的位置和点作为mesh顶点的索引,初始值设置成-1,可以通过判断是否为-1来判断当前点是否一定被赋值过。(本人不会Ps所以很多图都是用画图工具画的,不好看请谅解)然后解释ControlNode,ControlNode也是继承自Node,因为它也需要最基本的点的信息,active用来记录这个点的激活状态,above和right两个点用来记录控制点上方和右边的点,每个控制点都伴随着两个普通点。

如上图所示,一个控制点伴随着一个位于控制点上方的above(above = 控制点的position + Vector3.forward * 方形的边长 / 2)和位于控制点右边的right(right = 控制点的position.right * 方形的边上 / 2)。每个控制点都是相同的规则,空间拓展一下,其他三个控制点也是如此。

然后就是Square类,类中定义了,四个ControlNode,四个普通的Node,他们的关系如下

SuqareGrid的作用就是将所有的控制点组成一个个Square,具体过程代码的注释写的还是比较的清楚的。

此时运行出来的结果是这样的:

放大的细节,黑色(active = false)和白色(active = true)就是ControlNode,灰色是Node                                               

既然将所有的信息都转换到了Square上,那么就要将Square上点的active状态来绘制mesh,如果一个Square的active = true,那么就认为是二进制中的1,如果active = false,就认为是二进制中的0,我们自己定义topLeft是二进制的第四位,topRight是第三位,bottomRight是第二位,bottomLeft是第一位,这样四个点组合起来就会有16种情况,我们来举其中一情况来讲,其他的情况可以自己延伸的。

(该图截自视频)

此时topLeft和bottomRight的active = false,topRight和bottomLeft的active = true,二进制转换成十进制就是5,那么就要绘制中见灰色的mesh,unity中绘制mesh是要绘制三角形,并且是需要用到的是左手定则,也就是需要顺时针的画三角形,所以要画出三角形ABC,ACD,ADE,AEF,所以我们就要按当前ABCDEF的顺序记录。其实只要是顺时针的记录就行,这六个点无论哪个是起始点都无所谓。所以我们用二级制的方式来记录控制点的激活状况

            if (topLeft.active)
                configuration += 8;

            if (topRight.active)
                configuration += 4;

            if (bottomRight.active)
                configuration += 2;

            if (bottomLeft.active)
                configuration += 1;

根据configuration的值,就知道当前的点的激活状况,然后根据configuration来计算顶点和三角信息。

下面放出所有的代码:

public class MeshGenerator : MonoBehaviour {

    public SquareGrid squareGrid;
    List<Vector3> vertces;
    List<int> triangles;

    /// <summary>
    /// 创建生成网格
    /// </summary>
    /// <param name="map"></param>
    /// <param name="squareSize"></param>
    public void GenerateMesh(int[,] map, float squareSize)
    {
        squareGrid = new SquareGrid(map, squareSize);

        vertces = new List<Vector3>();
        triangles = new List<int>();

        for (int x = 0; x < squareGrid.squares.GetLength(0); x++)
        {
            for (int y = 0; y < squareGrid.squares.GetLength(1); y++)
            {
                TriangulateSquare(squareGrid.squares[x, y]);
            }
        }

        Mesh mesh = new Mesh();
        GetComponent<MeshFilter>().mesh = mesh;

        mesh.vertices = vertces.ToArray();
        mesh.triangles = triangles.ToArray();
        mesh.RecalculateNormals();
    }

    /// <summary>
    /// 将方形的信息转换成三角信息
    /// </summary>
    /// <param name="square"></param>
    void TriangulateSquare(Square square)
    {
        switch (square.configuration)
        {
            case 0:
                break;

            // 只有一个点激活的情况
            case 1:
                MeshFromPoints(square.centreBottom, square.bottomLeft, square.centreLeft);
                break;
            case 2:
                MeshFromPoints(square.centreRight, square.bottomRight, square.centreBottom);
                break;
            case 4:
                MeshFromPoints(square.centreTop, square.topRight, square.centreRight);
                break;
            case 8:
                MeshFromPoints(square.topLeft, square.centreTop, square.centreLeft);
                break;

            // 两个点激活的情况
            case 3:
                MeshFromPoints(square.centreRight,square.bottomRight, square.bottomLeft, square.centreLeft);
                break;

            case 6:
                MeshFromPoints(square.centreTop, square.topRight, square.bottomRight, square.centreBottom);
                break;

            case 9:
                MeshFromPoints(square.topLeft, square.centreTop, square.centreBottom, square.bottomLeft);
                break;

            case 12:
                MeshFromPoints(square.topLeft, square.topRight, square.centreRight, square.centreLeft);
                break;

            case 5:
                MeshFromPoints(square.centreTop, square.topRight, square.centreRight, square.centreBottom, square.bottomLeft, square.centreLeft);
                break;

            case 10:
                MeshFromPoints(square.topLeft, square.centreTop, square.centreRight, square.bottomRight, square.centreBottom, square.centreLeft);
                break;

            // 三个点激活的情况
            case 7:
                MeshFromPoints(square.centreTop, square.topRight, square.bottomRight, square.bottomLeft, square.centreLeft);
                break;

            case 11:
                MeshFromPoints(square.topLeft, square.centreTop, square.centreRight, square.bottomRight, square.bottomLeft);
                break;

            case 13:
                MeshFromPoints(square.topLeft, square.topRight, square.centreRight, square.centreBottom, square.bottomLeft);
                break;

            case 14:
                MeshFromPoints(square.topLeft, square.topRight, square.bottomRight, square.centreBottom, square.centreLeft);
                break;
            // 四个点激活的情况

            case 15:
                MeshFromPoints(square.topLeft, square.topRight, square.bottomRight, square.bottomLeft);
                break;
        }
    }

    /// <summary>
    /// 通过顶点来生成Mesh的相关信息
    /// </summary>
    /// <param name="points"></param>
    void MeshFromPoints(params Node[] points)
    {
        AssignVertices(points);

        //这里我一开始也是看不明白为什么要这么写,然后发现这么写实在是太巧妙了。
        //从起始点出发,如果只有三个点就只需要画一个三角形,如果是四个点就会进入第二个判断,这两就能画出两个三角形,一次类推。
        //相当于三个点就就是012, 如果是四个点就是012,023, 五个点就是画012,023,034

        if (points.Length >= 3)
            CreateTriangle(points[0], points[1], points[2]);

        if (points.Length >= 4)
            CreateTriangle(points[0], points[2], points[3]);

        if (points.Length >= 5)
            CreateTriangle(points[0], points[3], points[4]);

        if (points.Length >= 6)
            CreateTriangle(points[0], points[4], points[5]);
    }

    /// <summary>
    /// 分配顶点
    /// </summary>
    /// <param name="points"></param>
    void AssignVertices(Node[] points)
    {
        for (int i = 0; i < points.Length; i++)
        {
            if (points[i].vertexIndex == -1)
            {
                //让index = count 就能够做到从0到最大值-1
                points[i].vertexIndex = vertces.Count;
                vertces.Add(points[i].position);
            }
        }
    }

    /// <summary>
    /// 根据三点创建三角面
    /// </summary>
    /// <param name="a"></param>
    /// <param name="b"></param>
    /// <param name="c"></param>
    void CreateTriangle(Node a, Node b, Node c)
    {
        ///将顶点的关系存进三角信息列表中
        triangles.Add(a.vertexIndex);
        triangles.Add(b.vertexIndex);
        triangles.Add(c.vertexIndex);
    }

    /// <summary>
    /// 用于观察生成的方形网格信息的绘制函数
    /// </summary>
    private void OnDrawGizmos()
    {
        //if (squareGrid != null)
        //{
        //    for (int x = 0; x < squareGrid.squares.GetLength(0); x++)
        //    {
        //        for (int y = 0; y < squareGrid.squares.GetLength(1); y++)
        //        {
        //            Gizmos.color = (squareGrid.squares[x, y].topLeft.active) ? Color.black : Color.white;
        //            Gizmos.DrawCube(squareGrid.squares[x, y].topLeft.position, Vector3.one * .4f);

        //            Gizmos.color = (squareGrid.squares[x, y].topRight.active) ? Color.black : Color.white;
        //            Gizmos.DrawCube(squareGrid.squares[x, y].topRight.position, Vector3.one * .4f);

        //            Gizmos.color = (squareGrid.squares[x, y].bottomRight.active) ? Color.black : Color.white;
        //            Gizmos.DrawCube(squareGrid.squares[x, y].bottomRight.position, Vector3.one * .4f);

        //            Gizmos.color = (squareGrid.squares[x, y].bottomLeft.active) ? Color.black : Color.white;
        //            Gizmos.DrawCube(squareGrid.squares[x, y].bottomLeft.position, Vector3.one * .4f);

        //            Gizmos.color = Color.grey;
        //            Gizmos.DrawCube(squareGrid.squares[x, y].centreTop.position, Vector3.one * .15f);
        //            Gizmos.DrawCube(squareGrid.squares[x, y].centreRight.position, Vector3.one * .15f);
        //            Gizmos.DrawCube(squareGrid.squares[x, y].centreBottom.position, Vector3.one * .15f);
        //            Gizmos.DrawCube(squareGrid.squares[x, y].centreLeft.position, Vector3.one * .15f);
        //        }
        //    }
        //}
    }

    /// <summary>
    /// 方形网格
    /// </summary>
    public class SquareGrid
    {
        public Square[,] squares;

        public SquareGrid(int[,] map, float squareSize)
        {
            //计算出控制点的x方向的数量和y方向的数量,其实就是二维数组的长宽
            int nodeCountX = map.GetLength(0);
            int nodeCountY = map.GetLength(1);

            //根据传进来的方形的边长计算出所有的点构成的面长和高(这里的长指的是x方向的长度,高指的是z方向的纵深)
            float mapWidth = nodeCountX * squareSize;
            float mapHeight = nodeCountY * squareSize;

            //创建控制点的二维数组
            ControlNode[,] controlNodes = new ControlNode[nodeCountX, nodeCountY];

            for (int x = 0; x < nodeCountX; x++)
            {
                for (int y = 0; y < nodeCountY; y++)
                {
                    //计算控制点的位置,并且做了偏移,让整体的中心在原点
                    Vector3 pos = new Vector3(-mapWidth * .5f + x * squareSize + squareSize * .5f, 0, -mapHeight * .5f  + y * squareSize + squareSize * .5f);
                    //如果是1说明是墙,就将active设置成true,不是则为false
                    controlNodes[x, y] = new ControlNode(pos, map[x, y] == 1, squareSize);
                }
            }

            //由于一个四边是有四个点构成,square的总量是nodeCountX - 1 * nodeCoungY - 1, 这里在纸上点出几个点再画四边形就能理解啦。
            squares = new Square[nodeCountX - 1, nodeCountY - 1];

            for (int x = 0; x < nodeCountX -1; x++)
            {
                for (int y = 0; y < nodeCountY - 1; y++)
                {
                    //初始化要按顺序传入topLeft, topRight, _bottomLeft, _bottomRight;
                    squares[x, y] = new Square(controlNodes[x, y + 1], controlNodes[x + 1, y + 1], controlNodes[x, y], controlNodes[x + 1, y]);
                }
            }
        }
    }

    /// <summary>
    /// 方形区域,记录包块四个角的控制点,和每条边的中心的点
    /// </summary>
    public class Square
    {
        public ControlNode topLeft, topRight, bottomRight, bottomLeft;
        public Node centreTop, centreRight, centreBottom, centreLeft;
        public int configuration;

        public Square(ControlNode _topLeft, ControlNode _topRight, ControlNode _bottomLeft, ControlNode _bottomRight)
        {
            topLeft = _topLeft;
            topRight = _topRight;
            bottomLeft = _bottomLeft;
            bottomRight = _bottomRight;

            centreTop = topLeft.right;
            centreLeft = bottomLeft.above;
            centreBottom = bottomLeft.right;
            centreRight = bottomRight.above;

            if (topLeft.active)
                configuration += 8;

            if (topRight.active)
                configuration += 4;

            if (bottomRight.active)
                configuration += 2;

            if (bottomLeft.active)
                configuration += 1;
        }
    }

    /// <summary>
    /// 点的基础类
    /// </summary>
    public class Node
    {
        public Vector3 position;
        public int vertexIndex = -1;

        public Node(Vector3 _position)
        {
            position = _position;
        }
    }

    /// <summary>
    /// 控制点(一个方形区域的四角)
    /// </summary>
    public class ControlNode : Node
    {
        public bool active;
        public Node above, right;

        public ControlNode(Vector3 _position, bool _active, float squareSize) : base(_position)
        {
            active = _active;
            above = new Node(position + Vector3.forward * squareSize * .5f);
            right = new Node(position + Vector3.right * squareSize * .5f);
        }
    }
}

只需要在之前的MapGenerator的GenerateMap函数下面去获取到MeshGenerator脚本然后调用GenerateMesh函数。下面再放效果图:

 

这次是两个视频的内容,其实将的内容还是比较多的,希望我写的注释和文章能对大家有帮助。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值