Skip to content

Commit

Permalink
将移除碰撞器改为延迟到下一次碰撞检测时才移除,解决碰撞检测过程中移除碰撞器导致碰撞检测到一半失效的 bug
Browse files Browse the repository at this point in the history
  • Loading branch information
MrTrueChina committed Nov 10, 2021
1 parent 2719984 commit 49e24e0
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ internal partial class Quadtree : MonoBehaviour
/// <summary>
/// 碰撞器到节点的映射表
/// </summary>
private readonly Dictionary<QuadtreeCollider, QuadtreeNode> collidersToNodes = new Dictionary<QuadtreeCollider, QuadtreeNode>();
private Dictionary<QuadtreeCollider, QuadtreeNode> collidersToNodes = new Dictionary<QuadtreeCollider, QuadtreeNode>();
/// <summary>
/// 需要移除的碰撞器节点
/// </summary>
private readonly List<QuadtreeCollider> needRemoveColliders = new List<QuadtreeCollider>();

private void Awake()
{
Expand All @@ -29,6 +33,12 @@ private void Update()
// 更新四叉树
UpdateQuadtree();

// 移除需要移除的碰撞器
RemoveNeedRemoveColliders();

// 移除所有已被销毁的碰撞器的映射
RemoveDestroyedCollidersToNodes();

// 进行检测
Detect();
}
Expand All @@ -38,8 +48,8 @@ private void Update()
/// </summary>
private void UpdateQuadtree()
{
// 从根节点开始更新碰撞器位置
QuadtreeNode.OperationResult positionResult = root.UpdatePosition();
// 从根节点开始更新碰撞器位置并移除已经被销毁的碰撞器
QuadtreeNode.OperationResult positionResult = root.UpdatePositionAndRemoveDestroyedCollider();

// 更新映射表
collidersToNodes.OverlayMerge(positionResult.CollidersToNodes).RemoveOnValueIsNull();
Expand All @@ -52,6 +62,35 @@ private void UpdateQuadtree()
// 但这些节点需要进行最大半径更新,否则新节点的最大半径是负无穷,会导致生长的这一帧碰撞检测错误
}

/// <summary>
/// 移除需要移除的碰撞器
/// </summary>
private void RemoveNeedRemoveColliders()
{
// 遍历所有需要移除的碰撞器并移除
needRemoveColliders.ForEach(node =>
{
// 不是 null 的移除,是 null 的是因为碰撞机被销毁而需要移除的,这些需要用另外的方式处理
if(node != null)
{
// 移除碰撞机并在需要的时候进行合并
ImmediateRemoveColliderWithMerge(node);
}
});

// 清空需要移除的碰撞机列表
needRemoveColliders.Clear();
}

/// <summary>
/// 移除所有已被销毁的碰撞器的映射
/// </summary>
private void RemoveDestroyedCollidersToNodes()
{
// 从映射表里过滤出碰撞器不为 null 的映射,组成新的映射表
collidersToNodes = collidersToNodes.Where(pair => pair.Key != null).ToDictionary(pair => pair.Key, pair => pair.Value);
}

/// <summary>
/// 进行碰撞检测
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,22 +50,38 @@ public static QuadtreeNode.OperationResult AddCollider(QuadtreeCollider collider
}

/// <summary>
/// 从四叉树中移除碰撞器,符合条件时会合并节点
/// 移除碰撞器,到下次进行检测时才会真正移除碰撞器,符合条件时会合并节点
/// </summary>
/// <param name="collider"></param>
public static void RemoveColliderWithMerge(QuadtreeCollider collider)
{
// 如果没有实例,不进行处理,这一步是必须的,否则在游戏关闭时会发生销毁时四叉树实例一次次出现,进而导致异常
if (instance == null)
{
return;
}

// 添加到需要移除的碰撞器列表里
Instance.needRemoveColliders.Add(collider);
}

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

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

/// <summary>
Expand All @@ -74,7 +90,7 @@ internal static QuadtreeNode.OperationResult RemoveColliderWithOutMerge(Quadtree
/// <param name="collider"></param>
/// <param name="withMerge">是否需要在需要合并的时候进行合并</param>
/// <returns></returns>
internal static QuadtreeNode.OperationResult RemoveCollider(QuadtreeCollider collider, bool withMerge)
internal static QuadtreeNode.OperationResult ImmediateRemoveCollider(QuadtreeCollider collider, bool withMerge)
{
// 如果没有实例,不进行处理,这一步是必须的,否则在游戏关闭时会发生销毁时四叉树实例一次次出现,进而导致异常
if(instance == null)
Expand All @@ -92,11 +108,11 @@ internal static QuadtreeNode.OperationResult RemoveCollider(QuadtreeCollider col
QuadtreeNode.OperationResult result;
if (withMerge)
{
result = Instance.collidersToNodes[collider].RemoveColliderFromSelfWithMerge(collider);
result = Instance.collidersToNodes[collider].ImmediateRemoveColliderFromSelfWithMerge(collider);
}
else
{
result = Instance.collidersToNodes[collider].RemoveColliderFromSelfWithOutMerge(collider);
result = Instance.collidersToNodes[collider].ImmediateRemoveColliderFromSelfWithOutMerge(collider);
}

if (result.Success)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ internal partial class QuadtreeNode
/// </summary>
/// <param name="collider"></param>
/// <returns></returns>
internal OperationResult RemoveColliderFromSelfWithOutMerge(QuadtreeCollider collider)
internal OperationResult ImmediateRemoveColliderFromSelfWithOutMerge(QuadtreeCollider collider)
{
bool listResult = colliders.Remove(collider);

Expand All @@ -39,10 +39,10 @@ internal OperationResult RemoveColliderFromSelfWithOutMerge(QuadtreeCollider col
/// </summary>
/// <param name="collider"></param>
/// <returns></returns>
internal OperationResult RemoveColliderFromSelfWithMerge(QuadtreeCollider collider)
internal OperationResult ImmediateRemoveColliderFromSelfWithMerge(QuadtreeCollider collider)
{
// 移除碰撞器
OperationResult result = RemoveColliderFromSelfWithOutMerge(collider);
OperationResult result = ImmediateRemoveColliderFromSelfWithOutMerge(collider);

// 移除失败,不需要合并,直接返回结果
if(!result.Success)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,24 @@ internal partial class QuadtreeNode
/// 更新碰撞器位置
/// </summary>
/// <returns></returns>
internal OperationResult UpdatePosition()
internal OperationResult UpdatePositionAndRemoveDestroyedCollider()
{
if (HaveChildren())
{
// 有子节点,通知子节点更新碰撞器位置
return UpdateChildrenPosition();
return UpdateChildrenPositionAndRemoveDestroyedCollider();
}
else
{
// 没有子节点,更新当前节点内的碰撞器位置
return UpdateSelfPosition();
return UpdateSelfPositionAndRemoveDestroyedCollider();
}
}

/// <summary>
/// 更新子节点的碰撞器位置
/// </summary>
private OperationResult UpdateChildrenPosition()
private OperationResult UpdateChildrenPositionAndRemoveDestroyedCollider()
{
// 更新碰撞器位置必定成功
OperationResult result = new OperationResult(true);
Expand All @@ -36,7 +36,7 @@ private OperationResult UpdateChildrenPosition()
foreach (QuadtreeNode child in children)
{
// 通知子节点更新碰撞器位置
OperationResult childResult = child.UpdatePosition();
OperationResult childResult = child.UpdatePositionAndRemoveDestroyedCollider();

// 将子节更新导致的映射表更新合并到总结果里
result.CollidersToNodes.OverlayMerge(childResult.CollidersToNodes);
Expand All @@ -60,11 +60,14 @@ private OperationResult UpdateChildrenPosition()
/// <summary>
/// 更新当前节点内的碰撞器的位置
/// </summary>
private OperationResult UpdateSelfPosition()
private OperationResult UpdateSelfPositionAndRemoveDestroyedCollider()
{
// 更新碰撞器位置必定成功
OperationResult result = new OperationResult(true);

// 移除所有为空的碰撞器,这些是在外部被销毁的碰撞器
colliders.RemoveAll(collider => collider == null);

// 移除所有当前节点保存的、已经离开当前节点范围的碰撞器,并将这些碰撞器保存起来
OperationResult removeResult = GetAndRemoveCollidersOutOfArea();

Expand Down Expand Up @@ -101,7 +104,7 @@ private OperationResult GetAndRemoveCollidersOutOfArea()
foreach (QuadtreeCollider collider in new List<QuadtreeCollider>(result.CollidersToNodes.Keys))
{
// 移除碰撞器
OperationResult removeResult = Quadtree.RemoveColliderWithOutMerge(collider);
OperationResult removeResult = Quadtree.ImmediateRemoveColliderWithOutMerge(collider);

// 记录映射表的变化
result.CollidersToNodes.OverlayMerge(removeResult.CollidersToNodes);
Expand Down

0 comments on commit 49e24e0

Please sign in to comment.