游戏开发利器:凸包树全解析

摘要

凸包树是一种分层结构,通过递归构建点集的凸包形成"洋葱式"层次。最外层凸包作为根节点,内层凸包作为子节点,可高效处理复杂空间问题。在游戏中,它被用于碰撞检测(逐层筛选提升效率)、路径规划(先粗后精导航)和区域管理(分层渲染与触发)。其核心算法采用Graham Scan求凸包,并通过射线法判断点是否在凸包内部。Unity实现展示了递归构建树结构和Gizmos可视化不同层级凸包(用不同颜色区分),为游戏开发提供空间优化的有效解决方案。(149字)


一、什么是凸包?

凸包就像是“用一根橡皮筋把一堆钉子包起来”,橡皮筋收紧后形成的那条线,就是这些点的凸包。它是能包住所有点的最小凸多边形。


二、什么是凸包树?

1. 生活化比喻

想象你有一堆石头(点),你用一根橡皮筋把它们包起来,得到第一层凸包。
然后你把橡皮筋内圈的石头再用一根橡皮筋包起来,得到第二层凸包。
不断重复,直到所有石头都被包完。
这样一层一层的“橡皮筋包裹”,就像一棵树的结构——外层是父节点,内层是子节点,这就是凸包树

2. 结构说明

  • 每一层凸包是树的一层节点。
  • 外层凸包包着内层凸包,像洋葱一样一层一层。
  • 每个节点(凸包)可以有多个子节点(更内层的凸包)。

三、凸包树的原理

  1. 递归构建

    • 先找出所有点的最外层凸包,作为树的根节点。
    • 把凸包上的点去掉,剩下的点再找凸包,作为根节点的子节点。
    • 递归下去,直到所有点都被包完。
  2. 树形结构

    • 每个凸包节点记录自己的点和子凸包。
    • 这样形成一棵“凸包树”。

四、在游戏中的实际应用

1. 碰撞检测加速

  • 场景:你有一个复杂的多边形障碍物,想让子弹或角色高效判断是否碰撞。
  • 做法:用凸包树把复杂多边形分解成一层层的凸多边形。碰撞检测时,先和外层凸包检测,如果没碰到,直接排除;如果碰到,再检测内层凸包,逐层递进,效率高很多。

2. 路径规划与导航

  • 场景:AI在复杂地图中寻路。
  • 做法:用凸包树分层管理可行走区域,AI可以先在大范围(外层凸包)内粗略规划,再在细节区域(内层凸包)精细调整。

3. 分层可视化与区域管理

  • 场景:大地图分区、分层渲染、区域触发等。
  • 做法:用凸包树快速判断一个点属于哪一层区域,实现分层管理。

五、形象总结

  • 凸包树就像一颗“洋葱”,每一层皮包着更里面的皮。
  • 在游戏中,它让我们能“先大后小”地处理复杂区域,既快又省力。

六、简单可视化(伪代码)

// 伪代码:递归构建凸包树
ConvexHullTree BuildConvexHullTree(List<Vector2> points)
{
    if (points.Count < 3) return null;
    var hull = ConvexHull(points); // 求当前点集的凸包
    var insidePoints = points.Except(hull).ToList();
    var node = new ConvexHullTree { hull = hull };
    node.children = BuildConvexHullTree(insidePoints);
    return node;
}

七、结尾

一句话总结
凸包树就像一颗层层包裹的洋葱,让我们能高效地管理和处理复杂的空间区域,是游戏开发中碰撞检测、导航、区域管理的好帮手!


下面我会给你一个简单的C#实现,包括:

  1. 凸包算法(Graham Scan,适合2D点集)
  2. 递归构建凸包树
  3. Unity中Gizmos可视化凸包树

1. 凸包算法(Graham Scan)

using System.Collections.Generic;
using UnityEngine;
using System.Linq;

public static class ConvexHullHelper
{
    // 计算点p1p2p3的叉积
    private static float Cross(Vector2 p1, Vector2 p2, Vector2 p3)
    {
        return (p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x);
    }

    // Graham Scan求凸包,返回凸包上的点(按逆时针排序)
    public static List<Vector2> GrahamScan(List<Vector2> points)
    {
        if (points.Count < 3) return new List<Vector2>(points);

        // 1. 找到y最小的点,若有多个取x最小
        points = points.OrderBy(p => p.y).ThenBy(p => p.x).ToList();
        Vector2 p0 = points[0];

        // 2. 按极角排序
        var sorted = points.Skip(1).OrderBy(p => Mathf.Atan2(p.y - p0.y, p.x - p0.x)).ToList();
        List<Vector2> hull = new List<Vector2> { p0, sorted[0] };

        for (int i = 1; i < sorted.Count; i++)
        {
            while (hull.Count >= 2 && Cross(hull[hull.Count - 2], hull[hull.Count - 1], sorted[i]) <= 0)
                hull.RemoveAt(hull.Count - 1);
            hull.Add(sorted[i]);
        }
        return hull;
    }
}

2. 凸包树结构与递归构建

public class ConvexHullTreeNode
{
    public List<Vector2> hullPoints; // 当前层凸包
    public List<ConvexHullTreeNode> children = new List<ConvexHullTreeNode>();
}

public static class ConvexHullTreeBuilder
{
    // 递归构建凸包树
    public static ConvexHullTreeNode Build(List<Vector2> points)
    {
        if (points.Count < 3) return null;

        var hull = ConvexHullHelper.GrahamScan(points);
        if (hull.Count < 3) return null;

        // 找出在凸包内部的点
        var insidePoints = points.Except(hull, new Vector2Comparer()).Where(p => IsPointInPolygon(p, hull)).ToList();

        var node = new ConvexHullTreeNode { hullPoints = hull };
        var child = Build(insidePoints);
        if (child != null)
            node.children.Add(child);

        return node;
    }

    // 判断点是否在多边形内部(射线法)
    public static bool IsPointInPolygon(Vector2 point, List<Vector2> polygon)
    {
        int n = polygon.Count;
        bool inside = false;
        for (int i = 0, j = n - 1; i < n; j = i++)
        {
            if (((polygon[i].y > point.y) != (polygon[j].y > point.y)) &&
                (point.x < (polygon[j].x - polygon[i].x) * (point.y - polygon[i].y) / (polygon[j].y - polygon[i].y) + polygon[i].x))
                inside = !inside;
        }
        return inside;
    }

    // 用于List<Vector2>.Except的比较器
    class Vector2Comparer : IEqualityComparer<Vector2>
    {
        public bool Equals(Vector2 a, Vector2 b) => (a - b).sqrMagnitude < 1e-6f;
        public int GetHashCode(Vector2 obj) => obj.GetHashCode();
    }
}

3. Unity中Gizmos可视化凸包树

新建一个脚本ConvexHullTreeVisualizer.cs,挂到场景中的空物体上:

using UnityEngine;
using System.Collections.Generic;

public class ConvexHullTreeVisualizer : MonoBehaviour
{
    public List<Vector2> points = new List<Vector2>(); // 在Inspector中手动添加点
    private ConvexHullTreeNode root;

    void OnDrawGizmos()
    {
        if (points == null || points.Count < 3) return;

        // 构建凸包树
        root = ConvexHullTreeBuilder.Build(points);

        // 递归画树
        if (root != null)
            DrawHullTree(root, 0);
    }

    void DrawHullTree(ConvexHullTreeNode node, int depth)
    {
        if (node == null || node.hullPoints == null) return;

        // 不同层用不同颜色
        Color[] colors = { Color.red, Color.green, Color.blue, Color.yellow, Color.cyan, Color.magenta };
        Gizmos.color = colors[depth % colors.Length];

        // 画凸包
        for (int i = 0; i < node.hullPoints.Count; i++)
        {
            Vector2 a = node.hullPoints[i];
            Vector2 b = node.hullPoints[(i + 1) % node.hullPoints.Count];
            Gizmos.DrawLine(new Vector3(a.x, a.y, 0), new Vector3(b.x, b.y, 0));
        }

        // 递归画子节点
        foreach (var child in node.children)
            DrawHullTree(child, depth + 1);
    }
}

4. 使用方法

  1. 在Unity中新建一个空物体,挂上ConvexHullTreeVisualizer脚本。
  2. 在Inspector面板的points列表中添加若干2D点(比如10~30个)。
  3. 在Scene视图中就能看到一层层的凸包(不同颜色表示不同层)。

5. 总结

  • 这套代码可以让你在Unity中直观看到凸包树的分层结构
  • 你可以用它做碰撞加速、区域分层、可视化等用途。
  • 如果点数较多,建议适当减少点数以便观察。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

你一身傲骨怎能输

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

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

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

打赏作者

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

抵扣说明:

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

余额充值