Skip to content

Commit

Permalink
Merge pull request #42 from MrTrueChina/3.0-添加合并节点功能
Browse files Browse the repository at this point in the history
添加合并节点功能
  • Loading branch information
MrTrueChina authored Jul 15, 2021
2 parents 673bbf1 + 1e5c312 commit 1e6f201
Show file tree
Hide file tree
Showing 18 changed files with 389 additions and 219 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ Transform:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 343835778}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 85, y: 51, z: 0}
m_LocalPosition: {x: 319, y: 626, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 0}
Expand Down Expand Up @@ -533,7 +533,7 @@ Transform:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 542776031}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 1756, y: 272, z: 0}
m_LocalPosition: {x: 1618, y: 386, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 0}
Expand Down Expand Up @@ -1303,7 +1303,7 @@ Transform:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1893816044}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 889, y: 57, z: 0}
m_LocalPosition: {x: 882, y: 278, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 0}
Expand Down
13 changes: 12 additions & 1 deletion Assets/Quadtree Collider Detection/Example/QuadtreeDrawer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,16 @@ private List<QuadtreeCollider> GetColliders(QuadtreeNode quadtreeNode)
return (List<QuadtreeCollider>)typeof(QuadtreeNode).GetField("colliders", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(quadtreeNode);
}

/// <summary>
/// 获取一个节点及其所有子节点的碰撞器总数
/// </summary>
/// <param name="quadtreeNode"></param>
/// <returns></returns>
private int GetCollidersNumber(QuadtreeNode quadtreeNode)
{
return (int)typeof(QuadtreeNode).GetMethod("GetColliderNumbers", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(quadtreeNode, new object[0]);
}

/// <summary>
/// 绘制节点
/// </summary>
Expand Down Expand Up @@ -246,7 +256,8 @@ private string GetNodeInfomationText(QuadtreeNode node)
{
string infomation = "";

infomation += "总碰撞器数:" + GetColliders(node).Count + "\n";
infomation += "当前节点碰撞器数:" + GetColliders(node).Count + "\n";
infomation += "总碰撞器数:" + GetCollidersNumber(node) + "\n";
infomation += "最大检测半径:" + GetMaxRadius(node) + "\n";

return infomation;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ private void OnEnable()
private void OnDisable()
{
// 将这个碰撞器从四叉树中移除
Quadtree.RemoveCollider(this);
Quadtree.RemoveColliderWithMerge(this);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,17 @@ public static float MinSideLength
[Header("单个节点的最短边长,节点任意一条边边长小于这个值则不能再进行分割")]
private float minSideLength = 10; // 这个值用于应对过度分割导致树深度过大性能反而下降的情况,同时可以避免大量碰撞器位置完全相同导致的无限分割

/// <summary>
/// 一个节点里的碰撞器数量上限,超过上限后进行分割
/// </summary>
public static int MinCollidersNumber
{
get { return Config.minCollidersNumber; }
}
[SerializeField]
[Header("单个父级节点的所有子节点的碰撞器数量和下限,低于这个数量则判断为需要合并子节点到这个节点")]
private int minCollidersNumber = 5;

/// <summary>
/// 初始根节点范围
/// </summary>
Expand All @@ -57,5 +68,26 @@ public static Rect StartArea
[SerializeField]
[Header("四叉树创建时的范围")]
private Rect startArea = new Rect(-1, -1, 1922, 1082);

private void OnValidate()
{
// 最大碰撞器数量必须大于0,否则会无限分割
if(maxCollidersNumber < 1)
{
maxCollidersNumber = 1;
}

// 最小边长必须大于0,否则一旦所有碰撞器聚集到一个位置将会发生无限分割
if(minSideLength < 0)
{
minSideLength = 0.1f;
}

// 最小碰撞器数量必须小于最大碰撞器数量,否则会发生合并分割来回跳
if (minCollidersNumber >= maxCollidersNumber)
{
minCollidersNumber = maxCollidersNumber - 1;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 742584cee55bd2c4b9469b9c7adef272, type: 3}
m_Name: Quadtree Config
m_EditorClassIdentifier:
_maxCollidersNumber: 10
_minSideLength: 10
_startArea:
maxCollidersNumber: 10
minSideLength: 10
minCollidersNumber: 7
startArea:
serializedVersion: 2
x: -1
y: -1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ private static bool CircleToCircle(QuadtreeCollider colliderA, QuadtreeCollider
CircleQuadtreeCollider circleColliderB = (CircleQuadtreeCollider)colliderB;

return Vector2.Distance(circleColliderA.Position, circleColliderB.Position) <= circleColliderA.Radius + circleColliderB.Radius;
//TODO:圆形碰撞器的半径和最大检测半径是一样的,如果功能无误可以考虑不进行强转节约计算量
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ internal partial class Quadtree : MonoBehaviour
/// <param name="collider"></param>
internal QuadtreeNode.OperationResult DoAddCollider(QuadtreeCollider collider)
{
// XXX:只假设了存入失败是因为碰撞器不在范围内,可能需要添加限制,或在每次循环时对四叉树总区域进行判断,如果节点就在总区域内部但还是存入失败则报错

// 存入
QuadtreeNode.OperationResult result = root.AddColliderByArea(collider);

Expand All @@ -42,6 +40,7 @@ internal QuadtreeNode.OperationResult DoAddCollider(QuadtreeCollider collider)
// 再次存入
result = root.AddColliderByArea(collider);
}
// XXX:逻辑上存入失败的原因只可能是碰撞器不在范围内,但也不排除意外的可能,如果发生死循环 bug,可以考虑从这里处理

// 更新映射表
collidersToNodes.OverlayMerge(result.CollidersToNodes).RemoveOnValueIsNull();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ private void Detect()

// 发出碰撞事件
detector.SendCollision(collisionColliders);
// XXX:如果在检测时报出空异常等异常,可能是这里没有进行空异常的判断导致的
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,32 @@ public static QuadtreeNode.OperationResult AddCollider(QuadtreeCollider collider
return Instance.DoAddCollider(collider);
}

/// <summary>
/// 从四叉树中移除碰撞器,符合条件时会合并节点
/// </summary>
/// <param name="collider"></param>
/// <returns></returns>
public static QuadtreeNode.OperationResult RemoveColliderWithMerge(QuadtreeCollider collider)
{
return RemoveCollider(collider, true);
}

/// <summary>
/// 从四叉树中移除碰撞器,不进行合并
/// </summary>
/// <param name="collider"></param>
internal static QuadtreeNode.OperationResult RemoveColliderWithOutMerge(QuadtreeCollider collider)
{
return RemoveCollider(collider, false);
}

/// <summary>
/// 从四叉树中移除碰撞器
/// </summary>
/// <param name="collider"></param>
public static QuadtreeNode.OperationResult RemoveCollider(QuadtreeCollider collider)
/// <param name="withMerge">是否需要在需要合并的时候进行合并</param>
/// <returns></returns>
internal static QuadtreeNode.OperationResult RemoveCollider(QuadtreeCollider collider, bool withMerge)
{
// 如果没有实例,不进行处理,这一步是必须的,否则在游戏关闭时会发生销毁时四叉树实例一次次出现,进而导致异常
if(instance == null)
Expand All @@ -68,17 +89,29 @@ public static QuadtreeNode.OperationResult RemoveCollider(QuadtreeCollider colli
}

// 根据映射表直接从末梢节点移除碰撞器
QuadtreeNode.OperationResult result = Instance.collidersToNodes[collider].RemoveColliderFromSelf(collider);
QuadtreeNode.OperationResult result;
if (withMerge)
{
result = Instance.collidersToNodes[collider].RemoveColliderFromSelfWithMerge(collider);
}
else
{
result = Instance.collidersToNodes[collider].RemoveColliderFromSelfWithOutMerge(collider);
}


if (result.Success)
{
// 移除成功后更新映射表,覆盖合并映射表并移除空值
Instance.collidersToNodes.OverlayMerge(result.CollidersToNodes).RemoveOnValueIsNull();
}
else
{
throw new System.ArgumentOutOfRangeException("移除碰撞器 " + collider + " 时发生错误:碰撞器到节点的映射表中存在这个碰撞器,但映射到的节点 " + Instance.collidersToNodes[collider] + " 移除失败,可能是碰撞器并不在节点中");
throw new System.ArgumentOutOfRangeException(
"移除碰撞器 "
+ "(" + collider.Position.x + ", " + collider.Position.y + ")"
+ " 时发生错误:碰撞器到节点的映射表中存在这个碰撞器,但映射到的节点 "
+ "(" + Instance.collidersToNodes[collider].Area.ToString() + ")"
+ " 移除失败,可能是碰撞器并不在节点中");
}

return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,10 +185,18 @@ private OperationResult AddColliderByDirection(QuadtreeCollider collider)
return true;
}

// 如果检测的节点是发起这个方法的节点,无论碰撞器在什么位置,都可以存入到这个节点中
if (nodeParam == this)
{
return true;
}

// 当前节点相对于父节点的方向与碰撞器相对于父节点的方向,在 X 轴上是否一致
bool colliderAndNodeOnSameXSide = !((nodeParam.Area.center.x > nodeParam.parent.Area.center.x) ^ (colliderParam.Position.x > nodeParam.parent.Area.center.x));
bool colliderAndNodeOnSameXSide = !((nodeParam.Area.center.x >= nodeParam.parent.Area.center.x) ^ (colliderParam.Position.x >= nodeParam.parent.Area.center.x));
// 当前节点相对于父节点的方向与碰撞器相对于父节点的方向,在 Y 轴上是否一致
bool colliderAndNodeOnSameYSide = !((nodeParam.Area.center.y > nodeParam.parent.Area.center.y) ^ (colliderParam.Position.y > nodeParam.parent.Area.center.y));
bool colliderAndNodeOnSameYSide = !((nodeParam.Area.center.y >= nodeParam.parent.Area.center.y) ^ (colliderParam.Position.y >= nodeParam.parent.Area.center.y));

// 这里使用 >= 是因为 Rect 的范围是(包含左边和底边,不包含顶边和右边)

// 两个方向都一致,说明碰撞器可以存入这个节点
return colliderAndNodeOnSameXSide && colliderAndNodeOnSameYSide;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ internal Rect Area
/// <summary>
/// 这个节点所拥有的碰撞器
/// </summary>
private readonly List<QuadtreeCollider> colliders = new List<QuadtreeCollider>();
private List<QuadtreeCollider> colliders = new List<QuadtreeCollider>();
/// <summary>
/// 这个节点所拥有的的子节点
/// </summary>
Expand Down
Loading

0 comments on commit 1e6f201

Please sign in to comment.