用ECS做HexMap:六边形单元的颜色混合

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

颜色混合

在开始之前先纠正上一篇的错误,首先逐个打印颜色:

	 //打印六个方向的颜色:NE, E, SE, SW, W, NW
     if (i == 0)
     {
         Debug.Log("自身颜色:" + color + " |  NE方向颜色:" + neighbor);
     }

然后分别得到第一个六边形单元的六个方向的颜色值:

HexCellColorRGBA(0.098, 0.902, 0.934, 1.000) | NE Direction ColorRGBA(0.638, 0.136, 0.585, 1.000)
HexCellColorRGBA(0.098, 0.902, 0.934, 1.000) | E Direction ColorRGBA(0.239, 0.273, 0.753, 1.000)
HexCellColorRGBA(0.098, 0.902, 0.934, 1.000) | SE Direction ColorRGBA(0.098, 0.902, 0.934, 1.000)
HexCellColorRGBA(0.098, 0.902, 0.934, 1.000) | SW Direction ColorRGBA(0.098, 0.902, 0.934, 1.000)
HexCellColorRGBA(0.098, 0.902, 0.934, 1.000) | W Direction ColorRGBA(0.098, 0.902, 0.934, 1.000)
HexCellColorRGBA(0.098, 0.902, 0.934, 1.000) | NW Direction ColorRGBA(0.098, 0.902, 0.934, 1.000)

如下图所示,第0个六边形单元的SE、SW、W、NW这四个方向的颜色应该与自身颜色一致才对。实际上打印出来的颜色也是如此,那为什么还会渲染出其他颜色来呢?
第0个六边形单元
我调整矩阵的宽和高,使其只产生4个单元,终于发现了真相:
单元位置颠倒
把最后一个放到第0个,然后依照顺序递推,就能得到正确的结果。为什么会产生这种问题的本质原因不明,但是逻辑上已经找到了蛛丝马迹,愚蠢的解决办法是按照其逻辑反递推,把最后一个插入到第0个,代码很简单:

	 //把最后一个放到第0顺位,后面的依此类推
      Vector3 center;
      if (i+1== vertices.Length)
      {
          center = vertices[0];
      }
      else
      {
          center = vertices[i+1];
      }

本质原因是实体在Job中的执行顺序颠倒了,正常顺位应该是:0->1->2->3,但实际上变成了:3->1->2->0。
虽然不知道底层为什么要这样做,但是困扰我一夜的bug总算搞定了。
正确的颜色表现
如上图所示,问题修复了!之前做了减法(单元数量4),现在做加法看看有没有问题:
六边形单元的颜色混合
好了,问题解决了,代码已推送Github!

颜色平均化

其实只需改动少量的代码即可完成这个功能,我们在HexCellData六边形单元数据中已经有字段来保存颜色数据了:

neighbor = (color + neighbor) * 0.5f;

颜色平均化效果
可以看到,和最终效果还有一定距离,每3个相邻的单元就有四种颜色:
相邻单元
接下来就进行三个方向的颜色混合:

 //保存需要混合的颜色
 Color[] blendColors = new Color[6];
  //把六个相邻单元的颜色添加到这个数组中,代码省略
	………………
   //三向混合
   for (int j = 0; j < 6; j++)
   {
       int prev = (j - 1)< 0 ? 5 : j;
       int next = (j + 1) > 5 ? 0 : j;
       Colors.Add(color);
       Colors.Add((color+blendColors[prev]+blendColors[j])/3F);
       Colors.Add((color + blendColors[next] + blendColors[j]) / 3F);
   }

效果如下,边缘的地方开始变得模糊,但是仍然明显。
三向颜色混合

区域混合

把每个六边形单元进行区域划分,中心区域保持本色,外围区域进行颜色混合,如下图所示:
中心保持本色

在常量脚本HexMetrics中新增两个常量,用来计算中心本色区域和外围混合区域:

     /// <summary>
    /// 六边形单元中心本色区域占比
    /// </summary>
    public const float solidFactor = 0.75f;

    /// <summary>
    /// 六边形单元外围混合区域占比
    /// </summary>
    public const float blendFactor = 1f - solidFactor;

六边形的六个角因此受影响,与其相乘得出中心区域的顶点

    /// <summary>
    /// 六边形的六个角组成的数组
    /// </summary>
    public readonly static Vector3[] corners = {
		new Vector3(0f, 0f, outerRadius)*solidFactor,
		new Vector3(innerRadius, 0f, 0.5f * outerRadius)*solidFactor,
		new Vector3(innerRadius, 0f, -0.5f * outerRadius)*solidFactor,
		new Vector3(0f, 0f, -outerRadius)*solidFactor,
		new Vector3(-innerRadius, 0f, -0.5f * outerRadius)*solidFactor,
		new Vector3(-innerRadius, 0f, 0.5f * outerRadius)*solidFactor,
		new Vector3(0f, 0f, outerRadius)*solidFactor
    };

其他代码不用修改,我们就可以得到中心区域,如下图所示:
六边形单元的中心区域
做了减法之后,我们需要做加法,把周边区域画出来。和我们画六边形一样,我们也要从顶点到三角,一步一步来。

三角化混合区域

三角拆分
画六边形的时候我们将其拆分成了六个三角,现在我们再来拆分这六个三角,将其分为中心区域和外围区域。
中心区域已经有了,外围是一个等腰梯形,它有四个顶点(Vertex),接下来我们就先计算它们:

            //为每个六边形单元添加顶点、三角和颜色
            for (int j = 0; j < 6; j++)
            {
                Vector3 V1 = (center + HexMetrics.SolidCorners[j]);
                Vector3 V2 = (center + HexMetrics.SolidCorners[j + 1]);
                int vertexIndex = Vertices.Length;
                //添加中心区域的3个顶点
                Vertices.Add(center);
                Vertices.Add(V1);
                Vertices.Add(V2);
                Colors.Add(color);
                Colors.Add(color);
                Colors.Add(color);
                //添加中心区域的三角
                Triangles.Add(vertexIndex);
                Triangles.Add(vertexIndex + 1);
                Triangles.Add(vertexIndex + 2);
                vertexIndex += 3;
                //添加外围梯形区域的4个顶点
                Vector3 V3 = (center + HexMetrics.Corners[j]);
                Vector3 V4 = (center + HexMetrics.Corners[j + 1]);
                Vertices.Add(V1);
                Vertices.Add(V2);
                Vertices.Add(V3);
                Vertices.Add(V4);
                //添加外围区域的三角
                Triangles.Add(vertexIndex);
                Triangles.Add(vertexIndex + 2);
                Triangles.Add(vertexIndex + 1);
                Triangles.Add(vertexIndex + 1);
                Triangles.Add(vertexIndex + 2);
                Triangles.Add(vertexIndex + 3);
                //添加外圈区域三向颜色混合
                int prev = (j - 1) < 0 ? 5 : j;
                int next = (j + 1) > 5 ? 0 : j;
                Colors.Add(color);
                Colors.Add(color);
                Colors.Add((color + blendColors[prev] + blendColors[j]) / 3F);
                Colors.Add((color + blendColors[next] + blendColors[j]) / 3F);
            }

效果如下图所示:
区域混合效果
稍微提示下外围区域的代码,把等腰梯形拆分成两个三角,如下图所示:
外围的等腰梯形
这时每个六边形单元都像模像样了,只是有些细节还不是很好看,需要进一步平滑处理。

边界连接桥

为了使边界的颜色看起来更自然,我们把等腰梯形裁剪成矩形,作为连接单元之间的桥,如下图所示:
桥

利用桥重新计算V3和V4:

      //添加外围梯形区域的4个顶点
      Vector3 bridge=(HexMetrics.GetBridge(j));
      Vector3 V3 = (V1 + bridge);
      Vector3 V4 = (V2 + bridge);

桥接效果

填充间隙

如上图所示,在六边形之间还有一些三角空隙,是我们裁剪掉的。这个空隙叫做桥洞好了,先填充左边桥洞:

    /// <summary>
    /// 画个三角补丁来填补桥洞
    /// </summary>
	 //填充桥左边三角
	  vertexIndex += 4;
	  //添加桥三角的3个顶点
	  Vertices.Add(V1);
	  Vertices.Add(center+ HexMetrics.Corners[j]);
	  Vertices.Add(V3);
	
	  //添加桥洞区域的三角
	  Triangles.Add(vertexIndex);
	  Triangles.Add(vertexIndex + 1);
	  Triangles.Add(vertexIndex + 2);
	  Colors.Add(color);
	  Colors.Add((color + blendColors[prev] + blendColors[j]) / 3F);
	  Colors.Add(bridgeColor);

填充左桥洞
Ok,左边的桥洞已经填充了,再画一个三角补丁来填充右边的桥洞:

//填充桥右边三角
 vertexIndex += 3;
 //添加桥洞三角的3个顶点
 Vertices.Add(V2);
 Vertices.Add(V4);
 Vertices.Add(center + HexMetrics.Corners[j+1]);

 //添加桥洞区域的三角
 Triangles.Add(vertexIndex);
 Triangles.Add(vertexIndex + 1);
 Triangles.Add(vertexIndex + 2);
 Colors.Add(color);
 Colors.Add(bridgeColor);
 Colors.Add((color + blendColors[next] + blendColors[j]) / 3F);

依样画葫芦,桥洞都被三角补丁填充好了!
填充完毕
原作者说这已经是颜色混合的极限了,看起来不是很平滑,边界模糊。后面的教程会有改善,知乎大佬沈琰翻译了一些。有兴趣可以去看看详细的教程,和原作者几乎一致,我们这里用ECS实现会比较麻烦一些,后面再做优化吧。

边界合并

现在六边形单元之间的边界结构如下图所示:
单元之间的边界结构
这个图画得很明白了,两个六边形单元是通过两个矩形的桥连在一起的,三个单元之间又通过三个小三角形链接。
三角线框
放到线框中观察,会发现三角其实挺多的,三角和顶点的数量会直接影响游戏的性能,因此我们来做一些优化。
首先优化六边形单元之间的桥,原来是两个矩形,现在合并成一个矩形:

    /// <summary>
    /// 获取相邻六边形单元之间的矩形桥
    /// </summary>
    /// <param name="cellIndex">六边形单元索引</param>
    /// <returns>矩形桥</returns>
    public  static Vector3 GetBridge(int cellIndex)
    {
        //return (Corners[cellIndex] + Corners[cellIndex + 1]) * 0.5f * BlendFactor;
        //这里把原来的0.5去掉即可
        return (Corners[cellIndex] + Corners[cellIndex + 1]) * BlendFactor;
    }

现在得到的桥是原来的两倍,所以单元之间就会出现重叠的桥,因此我们限制桥生成的数量:

                if (j<=2)
                {
                    vertexIndex += 3;
                    //添加外围梯形区域的4个顶点
                    Vector3 bridge = (HexMetrics.GetBridge(j));
                    Vector3 V3 = (V1 + bridge);
                    Vector3 V4 = (V2 + bridge);
                    Vertices.Add(V1);
                    Vertices.Add(V2);
                    Vertices.Add(V3);
                    Vertices.Add(V4);
                    //添加外围区域的三角
                    Triangles.Add(vertexIndex);
                    Triangles.Add(vertexIndex + 2);
                    Triangles.Add(vertexIndex + 1);
                    Triangles.Add(vertexIndex + 1);
                    Triangles.Add(vertexIndex + 2);
                    Triangles.Add(vertexIndex + 3);
                }

于是我们得到了下图所示的限量版的桥,我们发现桥尽管限制了数量,但是边缘部分还是多出来了。
限制的桥
接下来裁剪掉这些多余的桥,只需加一个判断即可:

//如果没有相邻的单元,则跳过循环
if (blendColors[j]==color)
{//自身颜色和混合颜色相同,说明不需要混合,进一步说明没有相邻的单元
    continue;
}

裁剪后如下图所示:
裁剪后的桥
再次填充桥之间的大三角形,这次我们直接打一个大补丁,由于三个单元之间只需要打一个三角补丁,我们还限制了补丁的数量,从而避免三角补丁的重合:

      int next = (j + 1) > 5 ? 0 : (j + 1);
      if (j<=1 && blendColors[next] != color)
      {
          //填充桥三角
          vertexIndex += 4;
          //添加桥三角的3个顶点
          Vertices.Add(V2);
          Vertices.Add(V4);
          Vector3 V5 = (V2 + HexMetrics.GetBridge(next));
          Vertices.Add(V5);
          //添加桥洞区域的三角
          Triangles.Add(vertexIndex);
          Triangles.Add(vertexIndex + 1);
          Triangles.Add(vertexIndex + 2);
          Colors.Add(color);
          Colors.Add((color + blendColors[next] + blendColors[j]) / 3F);
          Colors.Add(bridgeColor);
      }

渲染效果如下图所示:
三角补丁
再通过线框图,我们发现减少了许多顶点,大概是36*47个顶点。
线框图
三角形也大量减少了,但是我们的图也看起来不完整了,下一篇将填补周边缺少的边缘区域。

更新计划

Mon 12 Mon 19 Mon 26 Mon 02 Mon 09 Mon 16 Mon 23 1. ForEach 2. IJobForEach 3. IJobChunk 4. SubScene 5. SpawnFromMonoBehaviour 6. SpawnFromEntity 7. SpawnAndRemove 休息 修正更新计划 参加表哥婚礼 进阶:FixedTimestepWorkaround 进阶:BoidExample 初级:SceneSwitcher 我是休息时间 资源整合 部署服务器 启动流程 登录流程 MegaCity 选人流程 游戏主世界 UnityMMO网络同步 我是休息时间 ECS and HexMap:自动生成地图系统 ECS:利用RenderMesh绘制六边形 ECS:利用RenderMesh为六边形涂色 ECS and HexMap:六边形单元的颜色混合 待计划 待计划 待计划 待计划 待计划 待计划 我是休息时间 待计划 待计划 待计划 待计划 待计划 我是休息时间 读取Excel自动生成Entity 读取Excel自动生成Component 读取数据库自动生成Entity 读取数据库自动生成Component ESC LuaFrameWork Skynet DOTS 官方示例学习笔记 -----休息----- 基于ECS架构开发MMO学习笔记 休息----- ECS自动生成地图 LuaFrameWork学习笔记 -----休息----- 基于Skynet架构开发服务器学习笔记 制作代码自动生成工具 总结 基于Unity2019最新ECS架构开发MMO游戏笔记

作者的话

Alt

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

ECS系列目录

ECS官方示例1:ForEach

ECS官方案例2:IJobForEach

ECS官方案例3:IJobChunk

ECS官方案例4:SubScene

ECS官方案例5:SpawnFromMonoBehaviour

ECS官方案例6:SpawnFromEntity

ECS官方案例7:SpawnAndRemove

ECS进阶:FixedTimestepWorkaround

ECS进阶:Boids

ECS进阶:场景切换器

ECS进阶:MegaCity0

ECS进阶:MegaCity1

UnityMMO资源整合&服务器部署

UnityMMO选人流程

UnityMMO主世界

UnityMMO网络同步

用ECS做HexMap:自动生成地图系统

用ECS做HexMap:利用RenderMesh绘制六边形

用ECS做HexMap:利用RenderMesh为六边形涂色

用ECS做HexMap:六边形单元的颜色混合

用ECS做HexMap:重构地图系统

用ECS做HexMap:鼠标点击六边形单元涂色

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

CloudHu1989

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

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

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

打赏作者

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

抵扣说明:

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

余额充值