补充处理窗体注销的逻辑。
修复部分 激活流程的问题。
This commit is contained in:
@@ -38,19 +38,6 @@ namespace XericUI.VisualForm
|
||||
[SerializeField, Tooltip("安全区坐标单位")]
|
||||
private SafeZoneUnit m_SafeZoneUnit = SafeZoneUnit.Ratio;
|
||||
|
||||
[Header("碰撞分离")]
|
||||
[SerializeField, Tooltip("启用屏幕空间碰撞分离计算")]
|
||||
private bool m_EnableCollision = false;
|
||||
|
||||
[SerializeField, Tooltip("碰撞计算执行间隔(秒),值越小越频繁,0为每帧计算")]
|
||||
private float m_CollisionInterval = 0.04f;
|
||||
|
||||
[SerializeField, Tooltip("碰撞分离最大迭代次数")]
|
||||
private int m_CollisionIterations = 10;
|
||||
|
||||
[SerializeField, Tooltip("碰撞分离推力系数")]
|
||||
private float m_CollisionPushForce = 0.5f;
|
||||
|
||||
#region 静态单例
|
||||
|
||||
private static AnchorDock s_Main;
|
||||
@@ -119,6 +106,11 @@ namespace XericUI.VisualForm
|
||||
/// </summary>
|
||||
private bool _cameraDirty;
|
||||
|
||||
/// <summary>
|
||||
/// 子类可读的摄像机脏标记
|
||||
/// </summary>
|
||||
protected bool IsCameraDirty => _cameraDirty;
|
||||
|
||||
/// <summary>
|
||||
/// 上帧世界摄像机坐标
|
||||
/// </summary>
|
||||
@@ -139,10 +131,33 @@ namespace XericUI.VisualForm
|
||||
/// </summary>
|
||||
private Quaternion _lastUICameraRotation;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 子类可访问属性
|
||||
|
||||
/// <summary>
|
||||
/// 碰撞计算计时器(每帧累加deltaTime,超过CollisionInterval时触发计算)
|
||||
/// 活跃的窗口状态字典(供子类如AdvancedAnchorDock进行碰撞计算)
|
||||
/// </summary>
|
||||
private float _collisionTimer;
|
||||
protected Dictionary<WorldAnchor, WindowState> ActiveStates => _activeStates;
|
||||
|
||||
/// <summary>
|
||||
/// Canvas本地空间Rect(供子类获取安全区等)
|
||||
/// </summary>
|
||||
protected Rect CanvasLocalRect => ((RectTransform)_canvas.transform).rect;
|
||||
|
||||
/// <summary>
|
||||
/// 像素安全区
|
||||
/// </summary>
|
||||
protected Rect PixelSafeZone
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_canvas == null) return Rect.zero;
|
||||
return m_SafeZoneUnit == SafeZoneUnit.Ratio
|
||||
? RatioSafeZoneToPixel(m_SafeZone, CanvasLocalRect)
|
||||
: m_SafeZone;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -264,7 +279,7 @@ namespace XericUI.VisualForm
|
||||
// 2. 检查摄像机变化
|
||||
CheckCameraDirty();
|
||||
|
||||
// 3. 遍历所有中间类,检查坐标脏(比较上帧和本帧位置差异)
|
||||
// 3. 检查所有中间类的位置脏(必须在碰撞钩子之前,供碰撞判断是否需要计算)
|
||||
foreach (var kvp in _activeStates)
|
||||
{
|
||||
var state = kvp.Value;
|
||||
@@ -272,18 +287,28 @@ namespace XericUI.VisualForm
|
||||
state.CheckPositionDirty(m_PositionThreshold);
|
||||
}
|
||||
|
||||
// 4. 碰撞分离计算(按时间间隔执行,必须在位置更新之前,确保同帧生效)
|
||||
if (m_EnableCollision)
|
||||
{
|
||||
_collisionTimer += Time.unscaledDeltaTime;
|
||||
if (_collisionTimer >= m_CollisionInterval)
|
||||
{
|
||||
_collisionTimer %= m_CollisionInterval;
|
||||
ComputeCollisionSeparation();
|
||||
}
|
||||
}
|
||||
// 4. 碰撞分离计算(子类重写钩子,基类为空)
|
||||
OnBeforePositionUpdates();
|
||||
|
||||
// 5. 遍历所有中间类,根据脏标记更新坐标(跳过无启用条目的状态)
|
||||
// 5. 遍历所有中间类,更新坐标
|
||||
UpdateAllStatePositions();
|
||||
|
||||
// 6. 复位脏标记
|
||||
_cameraDirty = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 位置更新前的钩子,子类可在此注入碰撞分离等逻辑
|
||||
/// </summary>
|
||||
protected virtual void OnBeforePositionUpdates() { }
|
||||
|
||||
/// <summary>
|
||||
/// 遍历所有中间类:更新坐标(跳过无启用条目的状态)。
|
||||
/// 单独抽取为虚方法,便于子类重写。
|
||||
/// CheckPositionDirty 已在 LateUpdate 步骤3 统一执行,此处不再重复。
|
||||
/// </summary>
|
||||
protected virtual void UpdateAllStatePositions()
|
||||
{
|
||||
foreach (var kvp in _activeStates)
|
||||
{
|
||||
var state = kvp.Value;
|
||||
@@ -296,9 +321,6 @@ namespace XericUI.VisualForm
|
||||
UpdateStatePosition(state);
|
||||
}
|
||||
}
|
||||
|
||||
// 6. 复位脏标记
|
||||
_cameraDirty = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -512,16 +534,12 @@ namespace XericUI.VisualForm
|
||||
if (entry.Instance == null) continue;
|
||||
if (!entry.Enabled) continue;
|
||||
|
||||
Vector2 rawPoint = basePoint + entry.ScreenOffset;
|
||||
Vector2 rawPoint = basePoint + entry.EffectiveOffset;
|
||||
Vector2 finalPoint;
|
||||
|
||||
// 碰撞偏移:条目参与碰撞且有碰撞偏移时,叠加 CollisionOffset
|
||||
Vector2 collisionOffset = (entry.EnableCollision && entry.IsColliding) ? entry.CollisionOffset : Vector2.zero;
|
||||
rawPoint += collisionOffset;
|
||||
|
||||
if (entry.ClampToSafeZone && baseOverflow != OverflowState.InRange)
|
||||
{
|
||||
finalPoint = clampedBase + entry.ScreenOffset + collisionOffset;
|
||||
finalPoint = clampedBase + entry.ScreenOffset;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -548,159 +566,6 @@ namespace XericUI.VisualForm
|
||||
|
||||
#endregion
|
||||
|
||||
#region 碰撞分离计算
|
||||
|
||||
/// <summary>
|
||||
/// 碰撞条目数据结构(用于碰撞分离计算时的临时存储)
|
||||
/// </summary>
|
||||
private struct CollisionItem
|
||||
{
|
||||
public WindowEntry Entry;
|
||||
public Rect Rect;
|
||||
public int StateIndex; // 所属WindowState在列表中的索引
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 屏幕空间碰撞分离计算。
|
||||
/// 收集所有启用了碰撞的条目,通过迭代推挤算法将它们分开,避免重叠。
|
||||
/// 结果写入各条目的 CollisionOffset(相对 ScreenOffset 的偏移量)。
|
||||
/// </summary>
|
||||
private void ComputeCollisionSeparation()
|
||||
{
|
||||
// 0. 重置所有条目的碰撞状态(上一帧的碰撞数据需要清零)
|
||||
foreach (var kvp in _activeStates)
|
||||
{
|
||||
var state = kvp.Value;
|
||||
if (state == null) continue;
|
||||
foreach (var entry in state.Entries)
|
||||
{
|
||||
entry.CollisionOffset = Vector2.zero;
|
||||
entry.IsColliding = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 1. 收集所有参与碰撞的条目及其屏幕Rect
|
||||
var items = new List<CollisionItem>();
|
||||
foreach (var kvp in _activeStates)
|
||||
{
|
||||
var state = kvp.Value;
|
||||
if (state == null || !state.IsEnabled || !state.HasAnyEnabled) continue;
|
||||
|
||||
var anchor = state.Anchor;
|
||||
if (anchor == null) continue;
|
||||
|
||||
foreach (var entry in state.Entries)
|
||||
{
|
||||
if (!entry.EnableCollision || !entry.Enabled || entry.Instance == null) continue;
|
||||
|
||||
// 获取条目当前屏幕Rect
|
||||
Rect entryRect = GetEntryScreenRect(entry);
|
||||
if (entryRect.width <= 0 || entryRect.height <= 0) continue;
|
||||
|
||||
items.Add(new CollisionItem { Entry = entry, Rect = entryRect });
|
||||
}
|
||||
}
|
||||
|
||||
if (items.Count < 2) return;
|
||||
|
||||
// 2. 迭代推挤分离
|
||||
int iterations = Mathf.Clamp(m_CollisionIterations, 1, 50);
|
||||
for (int iter = 0; iter < iterations; iter++)
|
||||
{
|
||||
bool anyOverlap = false;
|
||||
|
||||
for (int i = 0; i < items.Count; i++)
|
||||
{
|
||||
for (int j = i + 1; j < items.Count; j++)
|
||||
{
|
||||
var itemA = items[i];
|
||||
var itemB = items[j];
|
||||
|
||||
Rect rectA = OffsetRect(itemA.Rect, itemA.Entry.CollisionOffset);
|
||||
Rect rectB = OffsetRect(itemB.Rect, itemB.Entry.CollisionOffset);
|
||||
|
||||
if (!rectA.Overlaps(rectB)) continue;
|
||||
|
||||
anyOverlap = true;
|
||||
|
||||
// 计算推挤方向
|
||||
Vector2 centerA = rectA.center;
|
||||
Vector2 centerB = rectB.center;
|
||||
Vector2 delta = centerA - centerB;
|
||||
float halfW = (rectA.width + rectB.width) * 0.5f;
|
||||
float halfH = (rectA.height + rectB.height) * 0.5f;
|
||||
|
||||
float overlapX = halfW - Mathf.Abs(delta.x);
|
||||
float overlapY = halfH - Mathf.Abs(delta.y);
|
||||
|
||||
if (overlapX <= 0 || overlapY <= 0) continue;
|
||||
|
||||
Vector2 push;
|
||||
if (overlapX < overlapY)
|
||||
{
|
||||
push = new Vector2(Mathf.Sign(delta.x) * overlapX * m_CollisionPushForce, 0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
push = new Vector2(0f, Mathf.Sign(delta.y) * overlapY * m_CollisionPushForce);
|
||||
}
|
||||
|
||||
itemA.Entry.CollisionOffset += push;
|
||||
itemB.Entry.CollisionOffset -= push;
|
||||
|
||||
items[i] = itemA;
|
||||
items[j] = itemB;
|
||||
}
|
||||
}
|
||||
|
||||
if (!anyOverlap) break;
|
||||
}
|
||||
|
||||
// 3. 标记参与碰撞的条目
|
||||
for (int i = 0; i < items.Count; i++)
|
||||
{
|
||||
if (items[i].Entry.CollisionOffset != Vector2.zero)
|
||||
{
|
||||
items[i].Entry.IsColliding = true;
|
||||
items[i].Entry.State.CoordinateDirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取条目的屏幕矩形(基于其WorldAnchor的世界坐标 + ScreenOffset 换算到Canvas本地空间)。
|
||||
/// 矩形中心 = World→Canvas坐标 + ScreenOffset,尺寸 = Instance的RectTransform.rect.size。
|
||||
/// </summary>
|
||||
private Rect GetEntryScreenRect(WindowEntry entry)
|
||||
{
|
||||
if (entry == null || entry.Instance == null) return Rect.zero;
|
||||
|
||||
var state = entry.State;
|
||||
if (state?.Anchor == null) return Rect.zero;
|
||||
|
||||
Vector3 worldPos = state.Anchor.CachedTransform.position;
|
||||
if (!TryWorldToScreenPoint(worldPos, _worldCamera, _uiCamera, _canvas, RectTransform, out Vector2 canvasPos))
|
||||
return Rect.zero;
|
||||
|
||||
Vector2 center = canvasPos + entry.ScreenOffset;
|
||||
|
||||
// 获取实例的RectTransform尺寸
|
||||
RectTransform rt = entry.Instance.transform as RectTransform;
|
||||
if (rt == null) return new Rect(center, Vector2.one * 10f);
|
||||
return new Rect(center - rt.rect.size * 0.5f, rt.rect.size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将Rect按偏移量平移
|
||||
/// </summary>
|
||||
private static Rect OffsetRect(Rect r, Vector2 offset)
|
||||
{
|
||||
r.position += offset;
|
||||
return r;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 子对象哈希集
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user