用ECS做HexMap:高地与阶梯

31 篇文章 2 订阅
31 篇文章 3 订阅

最终效果图

准备工作

如果大佬对ECS版的海克斯无限地图感兴趣,不妨参与进来,欢迎Star/Folk源码
0下载Unity编辑器(2019.1.12f1 or 更新的版本),if(已经下载了)continue;
1克隆:git clone https://github.com/cloudhu/HexMapMadeInUnity2019ECS.git --recurse下载Zip压缩包
2如果下载的是压缩包,需要先将压缩包解压。然后将HexMapMadeInUnity2019ECS添加到Unity Hub项目中;
3用Unity Hub打开的开源项目:HexMapMadeInUnity2019ECS,等待Unity进行编译工作;
4打开项目后,启动场景在Scenes目录下,打开AdvancedHexMap场景(优化重构的版本)。

高地与阶梯连接

今天的计算非常复杂,不过还是尽量表述清楚,要创建高地就需要新增数据字段来保存高地的海拔参数。打开Data脚本:

    +++在UpdateData中新增
    /// <summary>
    /// 海拔
    /// </summary>
    public int Elevation;
    +++++在Cell中新增
    /// <summary>
    /// 当前单元的海拔
    /// </summary>
    public int Elevation;
    //六个方向相邻单元的海拔
    public int NEElevation;
    public int EElevation;
    public int SEElevation;
    public int SWElevation;
    public int WElevation;
    public int NWElevation;

这样我们需要使用的海拔数据字段都有了,我们可以先随机生成一些海拔数据给六边形单元,看看效果。打开CellSpawnSystem脚本,添加一个数组来保存随机生成的海拔,然后把这些数据交给每一个单元,这是在Job中完成的,非常高效:

            //保存单元颜色的原生数组
            NativeArray<Color> Colors=new NativeArray<Color>(Height * Width, Allocator.Temp);
            //保存单元海拔的原生数组
            NativeArray<int> Elevations = new NativeArray<int>(Height * Width, Allocator.Temp);
            //后面将从服务器获取这些数据,现在暂时随机生成
            for (int i = 0; i < Height* Width; i++)
            {
                Colors[i]= new Color(random.NextFloat(), random.NextFloat(), random.NextFloat());
                Elevations[i]= random.NextInt(6);
            }
            +++在循环生成单元的过程中把随机生成的数据交给单元
            
                    //3.计算阵列对应的六边形单元坐标
                    float _x = (x + z * 0.5f - z / 2) * (HexMetrics.InnerRadius * 2f);
                    float _y = Elevations[i] * HexMetrics.elevationStep;
                    float _z = z * (HexMetrics.OuterRadius * 1.5f);
                    //5.设置每个六边形单元的数据
                    CommandBuffer.SetComponent(index, instance, new Cell
                    {
                        Index=i,
                        Color = color,
                        Position= new Vector3(_x, _y, _z),
                        NE=blendColors[0],
                        E=blendColors[1],
                        SE=blendColors[2],
                        SW=blendColors[3],
                        W=blendColors[4],
                        NW= blendColors[5],
                        NEIndex=directions[0],
                        EIndex = directions[1],
                        SEIndex = directions[2],
                        SWIndex = directions[3],
                        WIndex = directions[4],
                        NWIndex = directions[5],
                        Elevation=Elevations[i],
                        NEElevation=Elevations[directions[0]],
                        EElevation = Elevations[directions[1]],
                        SEElevation = Elevations[directions[2]],
                        SWElevation = Elevations[directions[3]],
                        WElevation = Elevations[directions[4]],
                        NWElevation = Elevations[directions[5]]
                    });

这个时候我们的六边形单元就有了海拔高度了,效果如图所示:
带海拔的地图
通过上图,我们发现单元和单元之间因为海拔高度的差距而脱节,整个地图变得支离破碎,我们需要让它们重新连接起来。

桥面倾斜连接

支离破碎的原因是桥断了,这需要到CellSystem脚本中处理接收到的海拔数据,只需把相邻单元的海拔更新到算法中即可:

                    //添加外围桥接区域的顶点3和顶点4
                    Vector3 bridge = (HexMetrics.GetBridge(j));
                    Vector3 vertex3 = (vertex1 + bridge);
                    Vector3 vertex4 = (vertex2 + bridge);
                    //获取相邻单元的海拔
                    int neighborElevation = elevations[j];
                    //顶点3和顶点4在相邻单元上,海拔应与其同高
                    vertex3.y = vertex4.y = neighborElevation * HexMetrics.elevationStep;

桥面的顶点
顶点3和顶点4是桥的另一端,只需更新它们,使其与相邻单元同高,这样在连接单元的时候,桥就不会断掉了:
重连后的桥面

阶梯状的桥连接

桥面已经修复了,这个时候我们产生了新的需求:斜坡的桥面应该有适当的阶梯,使得玩家可以步行。
阶梯
如上图所示,我们先设计一个桥面由两个阶梯组成,把这种设计添加到HexMetrics脚本中:

    /// <summary>
    /// 海拔步长
    /// </summary>
    public const float elevationStep = 5f;

    /// <summary>
    /// 每个斜坡上的阶梯数
    /// </summary>
    public const int terracesPerSlope = 2;

    /// <summary>
    /// 阶梯步长
    /// </summary>
    public const int terraceSteps = terracesPerSlope * 2 + 1;

    /// <summary>
    /// 水平阶梯步长
    /// </summary>
    public const float horizontalTerraceStepSize = 1f / terraceSteps;

    /// <summary>
    /// 垂直阶梯步长
    /// </summary>
    public const float verticalTerraceStepSize = 1f / (terracesPerSlope + 1);

    /// <summary>
    /// 阶梯插值
    /// </summary>
    /// <param name="a">向量a</param>
    /// <param name="b">向量b</param>
    /// <param name="step">步数</param>
    /// <returns>渐变坡度</returns>
    public static Vector3 TerraceLerp(Vector3 a, Vector3 b, int step)
    {
        float h = step * HexMetrics.horizontalTerraceStepSize;
        a.x += (b.x - a.x) * h;
        a.z += (b.z - a.z) * h;
        float v = ((step + 1) / 2) * HexMetrics.verticalTerraceStepSize;
        a.y += (b.y - a.y) * v;
        return a;
    }

    /// <summary>
    /// 颜色插值
    /// </summary>
    /// <param name="a">颜色A</param>
    /// <param name="b">颜色B</param>
    /// <param name="step">步长</param>
    /// <returns>渐变色</returns>
    public static Color TerraceLerp(Color a, Color b, int step)
    {
        float h = step * HexMetrics.horizontalTerraceStepSize;
        return Color.Lerp(a, b, h);
    }

我们不但定义了斜坡阶梯数,还计算了阶梯步数、水平步长、垂直步长、阶梯插值、颜色插值,插值计算是为了产生渐变效果。
为了实现桥面的阶梯,我们需要更改CellSystem中与桥面相关的代码:

                        //插值计算第一个三角的两个顶点
                        Vector3 vertex6 = HexMetrics.TerraceLerp(vertex1, vertex3, 1);
                        Vector3 vertex7 = HexMetrics.TerraceLerp(vertex2, vertex4, 1);
                        /(First Step)/
                        bridgeColor = HexMetrics.TerraceLerp(currCellColor, neighborColor, 1);
                        colorBuffer.Add(currCellColor);
                        vertexBuffer.Add(vertex1);

                        colorBuffer.Add(bridgeColor);
                        vertexBuffer.Add(vertex6);

                        colorBuffer.Add(currCellColor);
                        vertexBuffer.Add(vertex2);

                        colorBuffer.Add(currCellColor);
                        vertexBuffer.Add(vertex2);

                        colorBuffer.Add(bridgeColor);
                        vertexBuffer.Add(vertex6);

                        colorBuffer.Add(bridgeColor);
                        vertexBuffer.Add(vertex7);
                        ///(Middle Steps)///
                        for (int i = 2; i < HexMetrics.terraceSteps; i++)
                        {
                            Vector3 stepVertex1 = vertex6;
                            Vector3 stepVertex2 = vertex7;
                            Color c1 = bridgeColor;
                            vertex6 = HexMetrics.TerraceLerp(vertex1, vertex3, i);
                            vertex7 = HexMetrics.TerraceLerp(vertex2, vertex4, i);
                            bridgeColor = HexMetrics.TerraceLerp(currCellColor, neighborColor, i);
                            colorBuffer.Add(c1);
                            vertexBuffer.Add(stepVertex1);

                            colorBuffer.Add(bridgeColor);
                            vertexBuffer.Add(vertex6);

                            colorBuffer.Add(c1);
                            vertexBuffer.Add(stepVertex2);

                            colorBuffer.Add(c1);
                            vertexBuffer.Add(stepVertex2);

                            colorBuffer.Add(bridgeColor);
                            vertexBuffer.Add(vertex6);

                            colorBuffer.Add(bridgeColor);
                            vertexBuffer.Add(vertex7);
                        }
                        ///(Last Step)///
                        //bridgeColor = HexMetrics.TerraceLerp(currCellColor, neighbor, HexMetrics.terraceSteps);
                        colorBuffer.Add(bridgeColor);
                        vertexBuffer.Add(vertex6);

                        colorBuffer.Add(neighborColor);
                        vertexBuffer.Add(vertex3);

                        colorBuffer.Add(bridgeColor);
                        vertexBuffer.Add(vertex7);

                        colorBuffer.Add(bridgeColor);
                        vertexBuffer.Add(vertex7);

                        colorBuffer.Add(neighborColor);
                        vertexBuffer.Add(vertex3);

                        colorBuffer.Add(neighborColor);
                        vertexBuffer.Add(vertex4);

代码看起来十分冗余,很多地方可以进行提炼,但是我不想到处传递参数,传参如果不够简洁,还不如复制粘贴。这里如果提炼成方法,会有一大堆参数传递,即使这样其实也应该提炼成方法,我有点后悔没有这么做了,太冗余了。
虽然不够简洁,但是效果仍然实现了,后面优化的时候再进行代码提炼,先看看效果:
带阶梯的地图

阶梯合理化

虽然效果实现了,但是上图中又产生了许多不合理的东西,例如桥面之间的三角桥洞补丁,例如相邻单元处于同一海拔高度的水平面,这时就不需要阶梯了,所以需要继续写代码来改进这些bug。
打开静态类脚本HexMetrics,我们来定义桥面的类型,已经判断方法:

    /// <summary>
    /// 单元边界类型
    /// </summary>
    public enum HexEdgeType {
        Flat, Slope, Cliff
    }


    /// <summary>
    /// 获取边缘的类型
    /// </summary>
    /// <param name="elevation1">海拔1</param>
    /// <param name="elevation2">海拔2</param>
    /// <returns>边缘类型</returns>
    public static HexEdgeType GetEdgeType(int elevation1, int elevation2)
    {
        if (elevation1 == elevation2)
        {
            return HexEdgeType.Flat;
        }
        int delta = elevation2 - elevation1;
        if (delta == 1 || delta == -1)
        {
            return HexEdgeType.Slope;
        }
        return HexEdgeType.Cliff;
    }

所以我们可以通过判断桥面类型来确定是否需要阶梯化,如果是斜坡,则添加阶梯,于是我们对阶梯方法进行提炼:

Job之内
   #region 桥面
   //判断当前单元与相邻单元的海拔高低差,如果是斜坡,则添加阶梯,平面和峭壁则无需阶梯
   if (HexMetrics.GetEdgeType(elevation, neighborElevation) == HexMetrics.HexEdgeType.Slope)
   {
       TriangulateEdgeTerraces(vertex1,vertex2,currCellColor,vertex3,vertex4,neighborColor,ref colorBuffer, ref vertexBuffer);
   }
   else
   {
       Color bridgeColor = (currCellColor + neighborColor) * 0.5f;
       AddQuad(vertex1, currCellColor,vertex2, bridgeColor, vertex3, bridgeColor, vertex4,neighborColor,ref colorBuffer,ref vertexBuffer);
   }

   #endregion
Job之外
//三角化桥面阶梯
void TriangulateEdgeTerraces(Vector3 beginLeft, Vector3 beginRight, Color beginColor, Vector3 endLeft, Vector3 endRight, Color endColor,ref DynamicBuffer<ColorBuffer> colorBuffer, ref DynamicBuffer<VertexBuffer> vertexBuffer)
{
    Vector3 vertex3 = HexMetrics.TerraceLerp(beginLeft, endLeft, 1);
    Vector3 vertex4 = HexMetrics.TerraceLerp(beginRight, endRight, 1);
    /(First Step)/
    Color bridgeColor = HexMetrics.TerraceLerp(beginColor, endColor, 1);
    AddQuad(beginLeft, beginColor, beginRight, beginColor,vertex3, bridgeColor, vertex4, bridgeColor, ref colorBuffer, ref vertexBuffer);
    ///(Middle Steps)///
    for (int i = 2; i < HexMetrics.terraceSteps; i++)
    {
        Vector3 stepVertex1 = vertex3;
        Vector3 stepVertex2 = vertex4;
        Color c1 = bridgeColor;
        vertex3 = HexMetrics.TerraceLerp(beginLeft, endLeft, i);
        vertex4 = HexMetrics.TerraceLerp(beginRight, endRight, i);
        bridgeColor = HexMetrics.TerraceLerp(beginColor, endColor, i);
        AddQuad(stepVertex1, c1,stepVertex2, c1,vertex3, bridgeColor, vertex4, bridgeColor, ref colorBuffer, ref vertexBuffer);
    }
    ///(Last Step)///
    AddQuad(vertex3, bridgeColor, vertex4, bridgeColor, endLeft, endColor, endRight, endColor, ref colorBuffer, ref vertexBuffer);
}

同时也对添加四平面和添加三角进行提炼:

        //添加矩形三角顶点和颜色
        void AddQuad(Vector3 v1,Color c1, Vector3 v2,Color c2, Vector3 v3, Color c3, Vector3 v4, Color c4, ref DynamicBuffer<ColorBuffer> colorBuffer, ref DynamicBuffer<VertexBuffer> vertexBuffer)
        {
            colorBuffer.Add(c1);
            vertexBuffer.Add(v1);

            colorBuffer.Add(c3);
            vertexBuffer.Add(v3);

            colorBuffer.Add(c2);
            vertexBuffer.Add(v2);

            colorBuffer.Add(c2);
            vertexBuffer.Add(v2);

            colorBuffer.Add(c3);
            vertexBuffer.Add(v3);

            colorBuffer.Add(c4);
            vertexBuffer.Add(v4);
        }
        //添加三角顶点与颜色
        void AddTriangle(Vector3 v1, Color bottomColor, Vector3 v2, Color leftColor, Vector3 v3,Color rightColor, ref DynamicBuffer<ColorBuffer> colorBuffer, ref DynamicBuffer<VertexBuffer> vertexBuffer)
        {
            colorBuffer.Add(bottomColor);
            vertexBuffer.Add(v1);

            colorBuffer.Add(leftColor);
            vertexBuffer.Add(v2);

            colorBuffer.Add((bottomColor + leftColor + rightColor) / 3f);
            vertexBuffer.Add(v3);
        }

提炼之后,之前的Job变得简洁多了:

	/// <summary>
    /// 计算六边形单元的顶点和颜色
    /// </summary>
    struct CalculateJob : IJobForEachWithEntity<Cell,NewDataTag> {
        public EntityCommandBuffer.Concurrent CommandBuffer;
        [BurstCompile]
        public void Execute(Entity entity, int index,[ReadOnly] ref Cell cellData,[ReadOnly]ref NewDataTag tag)
        {
            //0.获取单元索引,Execute的index不可靠,添加动态缓存
            int cellIndex = cellData.Index;
            DynamicBuffer<ColorBuffer> colorBuffer = CommandBuffer.AddBuffer<ColorBuffer>(index, entity);
            DynamicBuffer<VertexBuffer> vertexBuffer = CommandBuffer.AddBuffer<VertexBuffer>(index, entity);
            //1.获取当前单元的中心位置
            Vector3 currCellCenter = cellData.Position;
            //缓存当前单元的颜色
            Color currCellColor = cellData.Color;
            保存需要混合的颜色,使用数组[]是为了方便循环
            Color[] blendColors = new Color[6];
            blendColors[0] = cellData.NE;
            blendColors[1] = cellData.E;
            blendColors[2] = cellData.SE;
            blendColors[3] = cellData.SW;
            blendColors[4] = cellData.W;
            blendColors[5] = cellData.NW;
            //前3个方向相邻单元的索引
            int[] directions = new int[3];
            directions[0] = cellData.NEIndex;
            directions[1] = cellData.EIndex;
            directions[2] = cellData.SEIndex;
            //前三个方向相邻单元的海拔
            int[] elevations = new int[3];
            elevations[0] = cellData.NEElevation;
            elevations[1] = cellData.EElevation;
            elevations[2] = cellData.SEElevation;
            //添加六边形单元六个方向的顶点、三角和颜色
            for (int j = 0; j < 6; j++)
            {
                //1.添加中心区域的3个顶点
                Vector3 vertex1 = (currCellCenter + HexMetrics.SolidCorners[j]);
                Vector3 vertex2 = (currCellCenter + HexMetrics.SolidCorners[j + 1]);
                AddTriangle(currCellCenter, currCellColor, vertex1, currCellColor, vertex2, currCellColor, ref colorBuffer, ref vertexBuffer);

                //Connection Between 2 cells
                #region  Bridge=//桥只连接前三个方向相邻的单元,从而避免重复连接
                if (j <= 2)
                {
                    if (directions[j] == 0)
                    {//如果没有相邻的单元,则跳过循环
                        continue;
                    }
                    Color neighborColor = blendColors[j];
                    //添加外围桥接区域的顶点
                    Vector3 bridge = (HexMetrics.GetBridge(j));
                    Vector3 vertex3 = (vertex1 + bridge);
                    Vector3 vertex4 = (vertex2 + bridge);

                    //当前单元的海拔
                    int elevation = cellData.Elevation;
                    //邻居单元的海拔
                    int neighborElevation = elevations[j];
                    //顶点3和顶点4在相邻单元上,海拔应与其同高
                    vertex3.y = vertex4.y = neighborElevation * HexMetrics.elevationStep;

                    #region 桥面
                    //判断当前单元与相邻单元的海拔高低差,如果是斜坡,则添加阶梯,平面和峭壁则无需阶梯
                    if (HexMetrics.GetEdgeType(elevation, neighborElevation) == HexMetrics.HexEdgeType.Slope)
                    {
                        TriangulateEdgeTerraces(vertex1,vertex2,currCellColor,vertex3,vertex4,neighborColor,ref colorBuffer, ref vertexBuffer);
                    }
                    else
                    {
                        Color bridgeColor = (currCellColor + neighborColor) * 0.5f;
                        AddQuad(vertex1, currCellColor,vertex2, bridgeColor, vertex3, bridgeColor, vertex4,neighborColor,ref colorBuffer,ref vertexBuffer);
                    }

                    #endregion
                    ///TODO:处理桥洞
                }

                #endregion

            }
            //4.turn off cell system by remove NewDataTag
            CommandBuffer.RemoveComponent<NewDataTag>(index,entity);
        }

完成后的效果如下:
阶梯
从上图看得出,桥洞的三角补丁不见了,那是因为我将其剪切到记事本上了,为了排除干扰,而且那段代码非常冗余。

制作三角补丁

下面开始制作三角补丁,它的样子如下图所示:
三角补丁
三角补丁位于三个六边形单元之间,我们把最低的单元称为底部(Bottom,简称B),然后按照B=》L(左边Left)=》R(右边Right)的顺序来进行处理,这里不仅要画三角,而且需要做阶梯化处理,方法如下:

        //桥洞阶梯化
        void TriangulateCornerTerraces(Vector3 begin, Color beginColor, Vector3 left, Color leftColor, Vector3 right, Color rightColor, ref DynamicBuffer<ColorBuffer> colorBuffer,ref DynamicBuffer<VertexBuffer> vertexBuffer)
        {
            First Triangle
            Vector3 v3 = HexMetrics.TerraceLerp(begin, left, 1);
            Vector3 v4 = HexMetrics.TerraceLerp(begin, right, 1);
            Color c3 = HexMetrics.TerraceLerp(beginColor, leftColor, 1);
            Color c4 = HexMetrics.TerraceLerp(beginColor, rightColor, 1);
            AddTriangle(begin,beginColor,v3,c3,v4,c4,ref colorBuffer,ref vertexBuffer);
            ///Middle Steps
            for (int i = 2; i < HexMetrics.terraceSteps; i++)
            {
                Vector3 v1 = v3;
                Vector3 v2 = v4;
                Color c1 = c3;
                Color c2 = c4;
                v3 = HexMetrics.TerraceLerp(begin, left, i);
                v4 = HexMetrics.TerraceLerp(begin, right, i);
                c3 = HexMetrics.TerraceLerp(beginColor, leftColor, i);
                c4 = HexMetrics.TerraceLerp(beginColor, rightColor, i);
                AddQuad(v1,c1,v2,c2,v3,c3, v4, c4, ref colorBuffer, ref vertexBuffer);
            }
            /Last Step
            AddQuad(v3,c3,v4,c4,left,leftColor,right,rightColor, ref colorBuffer, ref vertexBuffer);
        }

如下图所示,阶梯化的三角补丁正好弥补三个单元之间的三角裂缝,但是不是所有的情况都是这样的。
阶梯化三角补丁
三角的左右两边可能是悬崖,也有可能是水平的,下面需要对所有三角补丁的情况进行判断:

 //判断相邻的三个六边形单元的高低关系,按照最低(Bottom),左(Left),右(Right)的顺序进行三角化处理
   if (elevation <= neighborElevation)
   {
       if (elevation <= nextElevation)
       {
           //当前单元海拔最低
           TriangulateCorner(vertex2, currCellColor, vertex4, neighborColor, vertex5, blendColors[next],ref colorBuffer,ref vertexBuffer,elevation, neighborElevation, nextElevation);
       }
       else
       {
           TriangulateCorner(vertex5, blendColors[next], vertex2, currCellColor, vertex4, neighborColor, ref colorBuffer, ref vertexBuffer, nextElevation, elevation, neighborElevation);
       }
   }
   else if (neighborElevation <= nextElevation)
   {
       TriangulateCorner(vertex4, neighborColor, vertex5, blendColors[next], vertex2, currCellColor, ref colorBuffer, ref vertexBuffer, neighborElevation, nextElevation, elevation);
   }
   else
   {
       TriangulateCorner(vertex5, blendColors[next], vertex2, currCellColor, vertex4, neighborColor, ref colorBuffer, ref vertexBuffer, nextElevation, elevation, neighborElevation);
   }

以上代码只是确定了B=》L=》R的处理顺序,下面才是判断阶梯类型的代码:

///桥洞三角化
void TriangulateCorner(Vector3 bottom, Color bottomColor, Vector3 left, Color leftColor, Vector3 right, Color rightColor, ref DynamicBuffer<ColorBuffer> colorBuffer, ref DynamicBuffer<VertexBuffer> vertexBuffer, int bottomElevation, int leftElevation, int rightElevation)
{
    HexMetrics.HexEdgeType leftEdgeType = HexMetrics.GetEdgeType(bottomElevation, leftElevation);
    HexMetrics.HexEdgeType rightEdgeType = HexMetrics.GetEdgeType(bottomElevation, rightElevation);
    if (leftEdgeType == HexMetrics.HexEdgeType.Slope)
    {
        if (rightEdgeType == HexMetrics.HexEdgeType.Slope)
        {
            TriangulateCornerTerraces(bottom, bottomColor, left, leftColor, right, rightColor, ref colorBuffer, ref vertexBuffer);
        }else if (rightEdgeType == HexMetrics.HexEdgeType.Flat)
        {
            TriangulateCornerTerraces(left, leftColor, right, rightColor, bottom, bottomColor, ref colorBuffer, ref vertexBuffer);
        }
        else
        {
            TriangulateCornerTerracesCliff(bottom, bottomColor, left, leftColor, right, rightColor, ref colorBuffer, ref vertexBuffer, bottomElevation, leftElevation, rightElevation);
        }
    }
    else if (rightEdgeType == HexMetrics.HexEdgeType.Slope)
    {
        if (leftEdgeType == HexMetrics.HexEdgeType.Flat)
        {
            TriangulateCornerTerraces(right, rightColor, bottom, bottomColor, left, leftColor, ref colorBuffer, ref vertexBuffer);
        }
        else
        {
            TriangulateCornerCliffTerraces(bottom, bottomColor, left, leftColor, right, rightColor, ref colorBuffer, ref vertexBuffer, bottomElevation, leftElevation, rightElevation);
        }
    }
   else if (HexMetrics.GetEdgeType(leftElevation, rightElevation) == HexMetrics.HexEdgeType.Slope)
    {
        if (leftElevation < rightElevation)
        {
            TriangulateCornerCliffTerraces(right, rightColor, bottom, bottomColor, left, leftColor, ref colorBuffer, ref vertexBuffer, rightElevation, bottomElevation, leftElevation);
        }
        else
        {
            TriangulateCornerTerracesCliff( left, leftColor, right, rightColor, bottom, bottomColor, ref colorBuffer, ref vertexBuffer,leftElevation, rightElevation, bottomElevation);
        }
    }
    else
    {
        两个单元处于同一平面,填充桥三角补丁,添加桥三角的3个顶点
        AddTriangle(bottom,bottomColor,left,leftColor,right,rightColor,ref colorBuffer,ref vertexBuffer);
    }

}

有很多种情况,斜坡(Slope)+斜坡(S)+平面(Flat),也可能是SFS、FSS、FFS、SFF,这还没有考虑到悬崖(Cliff),所以还得额外判断C的情况,大概有27种排列组合,不过我们尽量精简判断:

//三角化陡峭的阶梯
void TriangulateCornerTerracesCliff(Vector3 begin, Color beginCellColor, Vector3 left, Color leftCellColor, Vector3 right, Color rightCellColor, ref DynamicBuffer<ColorBuffer> colorBuffer, ref DynamicBuffer<VertexBuffer> vertexBuffer, int bottomElevation, int leftElevation, int rightElevation)
{
  float b = 1f / (rightElevation - bottomElevation);
  if (b < 0)
  {
      b = -b;
  }
  Vector3 boundary = Vector3.Lerp(begin, right, b);
  Color boundaryColor = Color.Lerp(beginCellColor, rightCellColor, b);

  TriangulateBoundaryTriangle(begin,beginCellColor,left,leftCellColor,boundary,boundaryColor,ref colorBuffer,ref vertexBuffer);
  if (HexMetrics.GetEdgeType(leftElevation,rightElevation) == HexMetrics.HexEdgeType.Slope)
  {
      TriangulateBoundaryTriangle(left, leftCellColor, right, rightCellColor, boundary, boundaryColor, ref colorBuffer, ref vertexBuffer);
  }
  else
  {
      AddTriangle(left,leftCellColor,right,rightCellColor,boundary,boundaryColor,ref colorBuffer,ref vertexBuffer);
  }
}

///峭壁镜像处理:左右对调
void TriangulateCornerCliffTerraces(Vector3 begin, Color beginCellColor, Vector3 left, Color leftCellColor, Vector3 right, Color rightCellColor, ref DynamicBuffer<ColorBuffer> colorBuffer, ref DynamicBuffer<VertexBuffer> vertexBuffer, int bottomElevation, int leftElevation, int rightElevation)
{
  float b = 1f / (leftElevation - bottomElevation);
  if (b < 0)
  {
      b = -b;
  }
  Vector3 boundary = Vector3.Lerp(begin, left, b);
  Color boundaryColor = Color.Lerp(beginCellColor, leftCellColor, b);

  TriangulateBoundaryTriangle(right, rightCellColor, begin, beginCellColor, boundary, boundaryColor, ref colorBuffer, ref vertexBuffer);
  if (HexMetrics.GetEdgeType(leftElevation, rightElevation) == HexMetrics.HexEdgeType.Slope)
  {
      TriangulateBoundaryTriangle(left, leftCellColor, right, rightCellColor, boundary, boundaryColor, ref colorBuffer, ref vertexBuffer);
  }
  else
  {
      AddTriangle(left, leftCellColor, right, rightCellColor, boundary, boundaryColor, ref colorBuffer, ref vertexBuffer);
  }
}

对于我来说,这个非常复杂,我花了十多个小时来消化这些内容,好歹实现了最终效果:
最终效果
这里没有讲海拔编辑器,因为那是最简单的内容,相信难不倒诸君,所以就省略了。如果有什么疑惑,可以先看看源码,或者直接在评论区留言。下一篇会跟着Jasper Flick的教程Unity Hex Map Tutorial走,这里只不过将其ECS化而已。

ECS专题目录

ECS更新计划

作者的话

Alt

如果喜欢可以点赞支持一下,谢谢鼓励!如果有什么疑问可以给我留言,有错漏的地方请批评指证!
如果有技术难题需要讨论,可以加入开发者联盟:566189328(付费群)为您提供有限的技术支持,以及,心灵鸡汤!
当然,不需要技术支持也欢迎加入进来,劈柴、遛狗、聊天、撸猫!( ̄┰ ̄*)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CloudHu1989

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值