2026/9/4 20:04:50

Unity与Blender构建六边形星球生成引擎:从网格数据到玩法落地

Unity与Blender构建六边形星球生成引擎:从网格数据到玩法落地 Unity 与 Blender 中构建六边形星球生成引擎最值得先看的不是“球很圆”“瓦片很多”而是能不能把美术资产和玩法数据串起来。这个组合适合想做星球探索、低多边形策略、回合制地图、程序化世界生成或者对六边形网格感兴趣的开发者。简单说Blender 负责产出可复用的模型资产Unity 负责在运行时把这些资产拼成一个带逻辑网格的六边形星球。上篇先不做完整的游戏循环先把最小闭环跑通从平面六边形网格、球面修正、Blender 导入规范到 Unity 网格脚本与场景验证。这篇文章会按照实际项目的落地顺序来写不是把功能列表抄一遍。你可以把它当成一份“从零开始做六边形星球的上手路线”其中会反复强调几个判断标准什么时候可以继续往下做什么时候必须先回头检查数据哪些问题看起来是脚本问题实际上却是 Blender 资产或坐标系问题。1. 先搞懂你要的是“六边形外观”还是“六边形玩法”1.1 表面贴皮式的六边形星球不是引擎很多项目里六边形星球只是美术风格。做法也简单在 Blender 里做一个 UV 球体生成六边形棋盘格纹理或者直接复制六边形块贴在球体表面。画面效果确实能达到模型也能过。但这类方案的问题在于贴图和模型只是表面状态。游戏里如果要做移动、寻路、区域占领、资源分配就必须知道“玩家当前站在哪个格子上”“这个格子的邻居是谁”“攻击范围能否覆盖到某个格子”。这些东西贴图给不了。所以做生成引擎之前先想清楚你的项目到底需要什么只要视觉上是六边形星球用 Blender 建模外加 Unity 材质贴图成本最低。玩法要围绕格子展开必须把六边形格子当成“数据结构”保存每一步生成都要记录中心点、邻居、归属、类型。游戏星球很大或要随机生成不能每个格子都创建一个 GameObject性能和内存会炸。标题里的“引擎”通常是第三种情况。它要处理的不只是渲染还包括生成逻辑、数据层和资源管理。上篇只需要先建立正确的数据思维后面再做玩法和性能优化才不会反复推倒重来。1.2 一个反直觉的几何事实正六边形无法铺满整个球面六边形平面铺没问题但球面不一样。如果每个格子都必须是正六边形并且球面封闭最终一定会出现少量五边形。很多资深的六边形地图实现里会保留少量“五边形格子”用来闭合球面。这个点看起来小但会直接影响你的玩法设计。如果玩家绕星球一圈走到一个“不是六边形”的位置视觉上可能只是一个短边格子但寻路算法里它就是一个特殊节点。如果前期没有记录格子类型后面很容易出现路径异常或者生成 UI 高亮时漏格。上篇可以先不把五边形逻辑写死但一定要在数据模型里留一个“格子类型”字段。等你把平面六边形方案跑稳再处理球面闭合时就不需要重构所有代码。1.3 上篇的目标不是“做完美”而是做最小闭环我做这类项目时有个习惯第一次测试只追求三个结果。能生成一颗看起来是低多边形星球的 Mesh。每个格子有独立数据能从任意坐标查到它所在格子。输入新种子后地形和颜色会变化。能跑到这一步说明生成链路是通的。之后再补美术资源、光影、玩法逻辑都有明确入口。如果第一步就直接去追求“自研高性能星球引擎”很容易卡在数学细节上项目反而半年跑不出东西。2. 技术链路与开发环境准备2.1 Unity 和 Blender 的分工Unity 是运行时环境。它接收输入参数执行生成算法创建 Mesh渲染场景处理玩家输入。Blender 则承担“资产制作”的工作负责提供模型、贴图、顶点色和必要的参考体。要注意不是所有内容都必须从 Blender 做出来。运行时生成的星球主体正确的做法通常是在 Unity 里用脚本创建 Mesh。因为六边形星球的核心特点是“可控和随机”用脚本生成才能根据种子、半径、高度、地景类型实时变化。如果每个星球都在 Blender 里手工捏你做不了随机世界也没法在游戏里动态编辑地形。Blender 真正适合做的是下面的内容六边形地砖的基础形状。树、石头、建筑等占位资产。需要的贴图、法线贴图、粗糙度贴图。或者作为设计原型用来验证瓦片比例和拼接效果。只要程序生成的 Mesh 里每个格子的中心、边、角足够清晰Blender 资产只是用来装饰这些格子的组件。2.2 开发环境与最低条件先把你本机的软件环境确认清楚再开始写代码。很多人卡在“为什么同样的脚本别人能跑我跑不了”大概率不是脚本问题是 Unity 版本、Blender 导出格式或者项目模板不一致。Unity建议用 LTS 版本。新建项目时选择 3D 模板不一定要上 URP/HDRP。如果只做调试内置渲染管线反而省事。Blender任意较新的版本都可以建议至少是 2.8 之后的界面。只有建模和导出Blender 对机器要求不高。硬件低多边形星球网格量不大普通办公电脑都能生成。但你如果打算跑 1 万个格子以上仍需关注内存和 GC后文会专门说。开发语言C# 是 Unity 主语言。Blender 这边也可以用 Python 做资产生成但上篇不展开。基础技能需要能看懂 Unity 的 GameObject、MeshFilter、MeshRenderer也会在 Blender 里创建基本模型并导出。不要一上来就引入 Burst、Jobs、ECS 这类高性能方案。先让项目可运行再根据实际瓶颈做优化。程序化生成项目最怕的是一开始就把架构架得很大结果连一个球都跑不出来。3. 先做平面六边形网格再考虑球面3.1 逻辑坐标建议用 Cube 坐标而不是 Offset 坐标做六边形玩法时最常用的是 Cube 坐标。它把平面六边形映射成三维整数坐标三个轴q r s 0。虽然多了一个维度但计算邻居非常稳定偏移逻辑也不容易出错。Offset 坐标更像矩形格子初学者好理解但你处理奇偶行时很容易写错。如果你要在球面上做邻居关系Cube 坐标也更好做旋转和距离计算。上篇如果只是要生成一个能看的球坐标系统可以先简化。但只要你想继续做“格子玩法”我建议从第一天就把每个格子的逻辑坐标存下来。否则后面一堆数据要重新迁移。3.2 单个六边形 Mesh 生成的本质一个六边形平面可以分解成 6 个三角形。最偷懒的写法是中心一个顶点外圈 6 个顶点然后按中心到外圈相邻两个顶点的方式生成三角形。下面这段是示意代码只展示“Mesh 三角形索引”的最小顺序。它不是完整的球体生成脚本也不是唯一的实现方式。using System.Collections.Generic; using UnityEngine; public class HexPlaneExample : MonoBehaviour { public MeshFilter targetMesh; public float radius 1f; void Start() { Mesh mesh new Mesh(); ListVector3 vertices new ListVector3(); Listint triangles new Listint(); // 中心点 int centerIndex vertices.Count; vertices.Add(Vector3.zero); // 外圈 6 个顶点角度按 60 度递增 for (int i 0; i 6; i) { float angle Mathf.Deg2Rad * (60f * i - 30f); float x Mathf.Cos(angle) * radius; float z Mathf.Sin(angle) * radius; vertices.Add(new Vector3(x, 0, z)); } // 中心 相邻外圈顶点组成三角形 for (int i 0; i 6; i) { triangles.Add(centerIndex); triangles.Add(centerIndex 1 i); triangles.Add(centerIndex 1 (i 1) % 6); } mesh.vertices vertices.ToArray(); mesh.triangles triangles.ToArray(); mesh.RecalculateNormals(); mesh.RecalculateBounds(); targetMesh.mesh mesh; } }这段代码只能画一个平面的六边形。真正做星球时外圈顶点不能永远放在水平面上而是要围绕球面方向分布。但基础思路是同一套每个格子由中心点、多个角点、三角形索引三部分组成。3.3 先跑小样再铺全量运行时生成 Mesh 最容易犯的错是一次性生成几千上万个顶点然后发现黑面、朝向错误或者 UV 混乱。我更建议先把下面的测试做完生成单个六边形确认能渲染出平整六边形面。生成一圈六边形检查相邻瓦片是否共边或重叠。在编辑器里保存一下顶点数量看看是否和预期一致。只要这三个检查通过再进入球面部分。如果单个六边形都没法显示就不要继续加噪声、加颜色、加随机。否则你会同时面对很多错误源排查成本成倍上升。4. 球面化改造与五边形补位4.1 不是所有六边形都能等大排列把二维六边形网格直接投射到球面上极区附近会出现严重畸变。常见解决方案有几种先生成球面上的格子中心点再用这些点生成六边形或五边形。用二十面体或截角二十面体作为基础框架再细分表面。使用类似 Voronoi 的方式让每个格子围绕中心点生长。这些方案数学量都不小。上篇阶段不用追求理论完美但你要明白为什么不能简单写一个平面网格然后裹到球面上。因为球面的曲率和经纬线的密度变化会让靠近两极的格子形状、面积、邻居关系都发生改变。如果只是做一个低多边形演示可以把平面瓦片中心点投影到球面上再让每个瓦片的角点围绕瓦片中心计算。视觉效果能用但不能拿来直接做精确寻路。要在正式玩法里用至少得给每个格子记录面积和邻居关系或者从多面体框架生成。4.2 五边形不是 Bug是闭合球面的必要条件真要做可用的六边形星球要接受一个事实球面上必然存在五边形。从实现角度讲你可以在生成格子时把“五边形格子”当成特殊格子处理。它的数据模型和六边形类似只是邻居数量从 6 变成 5三角形数量从 6 变成 5。不要把五边形删掉否则球面闭合不了。判断是否闭合的方法很简单模型有没有露出缝隙。如果有缝隙先别急着调浮点精度优先检查是否存在缺失的五边形或重复生成的顶点。4.3 地形高度用格子中心点不要一开始就做精细顶点位移球面基础形状出来之后第二件事是加地形起伏。对于策略型玩法我更建议先按“格子高度”做整体抬升而不是让每个角点独立做高精度噪声。实现思路对每个格子的中心点采样噪声。把格子中心点沿球心方向向外移动。格子的所有角点和内部顶点也按相同比例移动。根据高度改变格子颜色例如低于海平面显示深色高海拔显示浅色。这样做的好处是相邻格子之间不容易出现撕裂。如果你一开始就按每个顶点采样边缘的顶点两侧可能高度不同相邻格子之间就会露出缝隙。你想做更平滑的地形也必须共享边界顶点并保证相邻格子对同一个顶点使用相同的位移。噪音函数可以使用 Unity 的Mathf.PerlinNoise也可以引入更成熟的噪声库。用 Perlin 时先固定种子方便复现同一种地形。5. Blender 侧应该准备什么资产5.1 不要直接做一整颗完整的星球模型有些朋友打开 Blender第一反应是建一个很大的球体然后把六边形块一个个摆上去。这不是完全不能做但只适合做宣传图不适合做可变地图。在 Blender 里做“一个完整的六边形星球模型”等于把所有变化都焊死在模型上。Unity 脚本想要根据种子生成新星球必须重新建模、重新导入流程非常笨重。Blender 侧的正确职责是“做通用零件”。比如你做一个简单的六边形底座导出为 prefab 的一部分放进步行装饰、村庄建筑、资源点。程序生成星球时再根据数据把这些零件放到对应格子上。5.2 三个最容易出问题的工程点单位Blender 默认以米为单位Unity 也以米为单位。但导入导出时仍然要对 Scale 做一次确认。如果一份模型单位是厘米另一份是米生成出来会忽大忽小。轴向Blender 默认 Z 轴朝上Unity 世界坐标通常 Y 轴朝上。FBX 或 glTF 导入时轴向设置不一样模型可能翻转 90 度。这看起来是“模型方向错了”实际上只是轴约定不一致。面朝向法线翻转时模型在 Blender 里可能正常导入 Unity 后却出现黑面或半透明。建议导出前检查法线让所有表面朝向物体外侧。实际项目里这三个问题最容易在“Blender 建模没问题Unity 里瑕疵一堆”时爆发。排查顺序应该是先看模型尺寸再看旋转再看法线最后才是材质。5.3 简化材质和颜色方案上篇不建议把贴图画得太复杂。程序生成的星球颜色变化很多你很难为每种地形都接一套复杂 PBR 材质。一个很省事的方案是用顶点色。在 Unity 脚本里生成格子时根据高度设置colors数组。这样不需要额外创建材质球也能快速区分海洋、平原、山地、雪线。需要贴图时可以为一类格子共用一张基础纹理然后叠加顶点色做随机变化。这样既保留美术细节又不会让每帧渲染压力过大。6. Unity 落地把“能跑”变成“能玩”6.1 场景结构怎么摆我建议用两层结构PlanetObject挂一个控制脚本和一个空的 MeshFilter/MeshRenderer。运行时生成的子节点只存数据不要每个格子都创建独立对象。如果每个格子都建立一个 GameObject格子数量一旦上千编辑器层级会非常卡。正确做法是所有格子共用一个动态 Mesh或者按区块分成几个 Mesh 子对象。一个格子只是数据不是场景对象。你可以用一个数据结构来保存格子public class HexNode { public int id; public Vector3 center; public Vector3 normal; public ListHexNode neighbors; public int type; // 海洋、陆地、山脉等 public Vector3Int cubeCoord; }这里先把类型和邻居存好后面寻路、生成建筑、点击高亮都有数据基础。6.2 一个最小输出位置让相机能看到结果程序生成星球后如果 Mesh 生成了但场景一片黑先检查两件事相机是否在星球外部视角是否朝向星球。场景中是否有有效光源。如果只是调试直接在场景里放一个平行光再放一个默认第一人称相机预置体即可。生成成功后球体中心放在世界原点半径从 5 到 20 都可以。我一般会用Debug.Log输出计划生成格子的数量、实际生成顶点数、三角形数、耗时。数字对得上才说明生成逻辑没丢批次。如果生成很久没结果先看控制台和帧率再决定是否要改参数。6.3 大数量生成要避免卡死主线程如果一个星球需要生成很多格子不要在一次Start里把所有 Mesh 操作跑完。最简单的方式是分帧处理把“擦网格、算顶点、生成三角形、设置 Mesh、更新颜色”拆成多个阶段。通过协程或定时器每一帧只做有限数量的格子。这样做牺牲一点速度但能让编辑器保持响应。真正做大型项目时可以再换成 Jobs 和 Burst。上篇能先把协程跑顺就已经赢过了很多直接堆顶点卡死项目的同学。不要一上来就开一万个格子的全量生成。先用 100 个格子验证流程。跑通后把数量调到 500观察一次完整的生成耗时。生成时间超过 2 秒就要考虑分帧或降低格数。6.4 增加可视化 Debug 是最高效的地图调试手段格子玩法开发里数据结构是否正确很难直接看。建议在编辑器里开启 Debug 模式当鼠标点到某个格子时高亮该格子的 Mesh 或多边形边线同时输出它的坐标、邻居数量、类型。也可以给每个格子渲染一个中心点小球。每生成一个节点就创建一个透明的小球占位只为了观察分布。正式构建前再删除这些调试对象。这样判断邻居关系是否正确比盯着日志数字舒服得多。7. 常见报错和排查顺序7.1 生成结果是黑面但模型看着没少优先查法线和三角形绕序。Unity Mesh 的三角形索引默认需要逆时针从正面看过去。如果三角形顺序反了Unity 可能只渲染背面或从另一侧看到面。排查方式是先关掉光源用默认材质检查球面内外哪一面显示正常如果是内面正常就把三角形三条索引顺序反转。7.2 瓦片之间出现裂缝或穿插除非你确认共享了顶点否则不要直接提高网格精度。出现裂缝通常是两个相邻格子各自生成边界顶点位置浮点误差不一致。排查顺序检查顶点坐标是否有四舍五入。检查相邻格子是否共用同一个中心点和角点。检查高度位移是否从同一个格子中心计算。检查五边形格子周围是否漏面。如果只是表现层可以用大一点的重叠面做补贴。但玩法需要精确边界裂缝必须从数据上解决。7.3 Blender 模型导入 Unity 后尺寸或朝向不对不要先怀疑 Unity 脚本。先打开模型资源检查 Import Settings 里的 Scale Factor以及是否做了自动轴转换。然后回到 Blender 查看模型的实际尺寸和旋转状态。建议在 Blender 里统一执行一次“清除父级并应用变换”把 scale、rotation 都归一化。导出格式不乱换。FBX 和 glTF 的轴设置不同同一个工程不要两种格式混用。7.4 生成时编辑器卡死先看是不是无限循环或死循环。运行中按暂停看脚本停在哪个循环里。常见原因邻居关系递归没有结束条件。某个格子在球面上找不到相邻关系。网格顶点和三角形索引数量不一致。每帧都在 new Mesh没有清理旧 Mesh。如果脚本停在噪声采样上检查传入坐标是否为极值比如在Mathf.PerlinNoise里传入了非常大的整数导致结果同值。对坐标做模运算或者先乘一个缩放因子能避开很多边界问题。7.5 同一个种子生成结果不稳定如果打开两次游戏星球完全不一样十有八九是“随机顺序”不稳定。比如用Dictionary或HashSet遍历格子顺序可能不固定导致每次赋值给格子的数据位置不同。解决办法是遍历之前先对格子 id 或坐标排序保证每次生成的顺序一致。只有固定种子加固定顺序才能真正复现同一种地形。8. 上篇先做到哪里后续再扩展什么这篇文章对应的“上篇”最适合有一个明确收尾能显示一个按种子生成的六边形星球格子数据可以通过鼠标点击查询。如果你的项目已经跑到这一步建议先停下来整理项目资料。把生成参数提炼到 Inspector 或 ScriptableObject 里方便反复测试。不要急着加一堆资源点、单位、战斗逻辑。试想一下如果连格子数据查询都会错后面做寻路和单位移动的难度会更高。到这一步我个人的建议清单是固定种子方便重复定位问题。控制台记录格子数量、区块数量、生成耗时。开启一个简单的点击高亮验证格子中心点和邻居是否准确。Blender 侧只做 3 到 5 个基础零件不要陷入高精度建模。把“隐藏坑”提前写进文档五边形格子、极区畸变、坐标轴旋转、法线翻转。之后再扩展大概率会碰到的方向包括格子寻路和移动范围计算。地形类型与资源点生成。更平滑的多层 LOD 显示。分区块加载减少运行内存。Unity Jobs Burst 把大数量生成压到几十毫秒内。这些内容每一块都可以单独写一篇上篇先把球和数据结构立住就好。做这类程序化生成项目最忌讳的是“功能看着能跑就行”。六边形星球系统视觉能跑只算完成了三成数据层一致、可复现、可查询才是后续玩法稳定的基础。如果你在运行自己项目时也碰到黑面、裂缝、轴问题、卡死这些情况别急着一口气改代码先从环境、导入、数据顺序和 Mesh 索引查起大部分问题都能在这条排查链上找到答案。