大家好!

如果你曾经尝试在Unity UI中构建分支技能树或进化树,你可能会发现原生组件(如HorizontalLayoutGroupScrollRect)在处理非线性路径和多个父节点时会很快失效。

在我们的怪物驯服游戏《Mokomon: Five Elements》中,我需要一个进化树,让生物能够动态分支(例如Geolam进化为Tsunodon、Terranite或Bouldox),同时避免线条交叉成难以辨认的混乱局面。

以下是关于数学计算和网格生成的工作原理详解:

1. 布局数学:重心松弛法

不采用手动锚定节点或使用僵硬的布局组:

  • 水平轴(X):严格绑定到进化阶段索引(婴儿=0,幼年=1,成年=2等),乘以列间距。
  • 垂直轴(Y):为了保持连接清晰,我们运行重心松弛迭代:
  • 前向传递:将子节点拉向其父节点的平均Y位置。
  • 后向传递:将父节点拉向其子节点的平均Y位置。
  • 碰撞检查:遍历每个阶段列,将重叠的节点按最小垂直间距推开。

这能自动拉直连接线,消除视觉上的杂乱无章。

2. 单次绘制调用渲染所有线条(MaskableGraphic

实例化拉伸的UI图像GameObject来绘制线条会产生庞大的层级结构并增加绘制调用次数。

相反,我们的连接器继承自MaskableGraphic,并通过VertexHelper将粗立方贝塞尔曲线直接生成到UI网格中:

// 1. 计算立方贝塞尔曲线点
private Vector2 CalculateCubicBezierPoint(Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, float t)
{
    float u = 1f - t;
    float tt = t * t;
    float uu = u * u;
    return (uu * u * p0) + (3f * uu * t * p1) + (3f * u * tt * p2) + (tt * t * p3);
}

// 2. 直接将程序化粗线几何体生成到UI网格中
private void DrawThickLineStrip(VertexHelper vh, List<Vector2> points, float width, Color color)
{
    float halfWidth = width * 0.5f;
    int startIndex = vh.currentVertCount;

    for (int i = 0; i < points.Count; i++)
    {
        Vector2 current = points[i];
        Vector2 dir = (i == points.Count - 1) 
            ? (current - points[i - 1]).normalized 
            : (points[i + 1] - current).normalized;

        Vector2 normal = new Vector2(-dir.y, dir.x);
        Vector2 vLeft = current + normal * halfWidth;
        Vector2 vRight = current - normal * halfWidth;

        float vNorm = (float)i / (points.Count - 1);
        vh.AddVert(new Vector3(vLeft.x, vLeft.y, 0f), color, new Vector2(0f, vNorm));
        vh.AddVert(new Vector3(vRight.x, vRight.y, 0f), color, new Vector2(1f, vNorm));
    }

    for (int i = 0; i < points.Count - 1; i++)
    {
        int vertIdx = startIndex + i * 2;
        vh.AddTriangle(vertIdx, vertIdx + 1, vertIdx + 2);
        vh.AddTriangle(vertIdx + 1, vertIdx + 3, vertIdx + 2);
    }
}