摘要
凸包树是一种分层结构,通过递归构建点集的凸包形成"洋葱式"层次。最外层凸包作为根节点,内层凸包作为子节点,可高效处理复杂空间问题。在游戏中,它被用于碰撞检测(逐层筛选提升效率)、路径规划(先粗后精导航)和区域管理(分层渲染与触发)。其核心算法采用Graham Scan求凸包,并通过射线法判断点是否在凸包内部。Unity实现展示了递归构建树结构和Gizmos可视化不同层级凸包(用不同颜色区分),为游戏开发提供空间优化的有效解决方案。(149字)
一、什么是凸包?
凸包就像是“用一根橡皮筋把一堆钉子包起来”,橡皮筋收紧后形成的那条线,就是这些点的凸包。它是能包住所有点的最小凸多边形。
二、什么是凸包树?
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#实现,包括:
- 凸包算法(Graham Scan,适合2D点集)
- 递归构建凸包树
- 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. 使用方法
- 在Unity中新建一个空物体,挂上
ConvexHullTreeVisualizer
脚本。 - 在Inspector面板的
points
列表中添加若干2D点(比如10~30个)。 - 在Scene视图中就能看到一层层的凸包(不同颜色表示不同层)。
5. 总结
- 这套代码可以让你在Unity中直观看到凸包树的分层结构。
- 你可以用它做碰撞加速、区域分层、可视化等用途。
- 如果点数较多,建议适当减少点数以便观察。