refactor(enemy): 移动层探测与 EdgeSafeMargin 改走 Body 权威,删除重复的碰撞体解析
This commit is contained in:
@@ -42,8 +42,6 @@ namespace BaseGames.Enemies
|
|||||||
[SerializeField] private float _navJumpMaxHeight = 6f;
|
[SerializeField] private float _navJumpMaxHeight = 6f;
|
||||||
[Tooltip("可处理的最大跳跃水平距离")]
|
[Tooltip("可处理的最大跳跃水平距离")]
|
||||||
[SerializeField] private float _navJumpMaxDist = 10f;
|
[SerializeField] private float _navJumpMaxDist = 10f;
|
||||||
[Tooltip("用于确定射线起点宽度和底边的 Collider2D;留空则 Awake 时自动查找")]
|
|
||||||
[SerializeField] private Collider2D _groundCheckCollider;
|
|
||||||
[Tooltip("从碰撞体底边向下的射线检测距离")]
|
[Tooltip("从碰撞体底边向下的射线检测距离")]
|
||||||
[SerializeField] private float _groundCheckDist = 0.15f;
|
[SerializeField] private float _groundCheckDist = 0.15f;
|
||||||
[Tooltip("射线数量(1 = 仅中心,>1 时沿碰撞体底边均匀分布)")]
|
[Tooltip("射线数量(1 = 仅中心,>1 时沿碰撞体底边均匀分布)")]
|
||||||
@@ -67,6 +65,17 @@ namespace BaseGames.Enemies
|
|||||||
private int _facingDir = 1;
|
private int _facingDir = 1;
|
||||||
private Coroutine _linkCoroutine;
|
private Coroutine _linkCoroutine;
|
||||||
|
|
||||||
|
// 身体几何权威(EnemyBase.Body)。懒获取以支持编辑器模式(Awake 未执行)下的 Gizmo/探测预览。
|
||||||
|
private EnemyBase _enemy;
|
||||||
|
private IEnemyBody Body
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_enemy == null) _enemy = GetComponent<EnemyBase>();
|
||||||
|
return _enemy != null ? _enemy.Body : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── 转身状态 ────────────────────────────────────────────────────────
|
// ── 转身状态 ────────────────────────────────────────────────────────
|
||||||
private bool _isTurning;
|
private bool _isTurning;
|
||||||
private int _pendingFacingDir; // 转身目标方向,转身完成后 ApplyFacingFlip 使用
|
private int _pendingFacingDir; // 转身目标方向,转身完成后 ApplyFacingFlip 使用
|
||||||
@@ -184,12 +193,10 @@ namespace BaseGames.Enemies
|
|||||||
|
|
||||||
private Vector2 GetGroundRayOrigin(int index)
|
private Vector2 GetGroundRayOrigin(int index)
|
||||||
{
|
{
|
||||||
// 优先用序列化字段,编辑器模式下 Awake 未执行时也能直接 GetComponent
|
var body = Body;
|
||||||
var col = _groundCheckCollider != null ? _groundCheckCollider : GetComponent<Collider2D>();
|
if (body == null || body.Collider == null) return (Vector2)transform.position;
|
||||||
if (col == null)
|
|
||||||
return (Vector2)transform.position;
|
|
||||||
|
|
||||||
Bounds b = col.bounds;
|
Bounds b = body.Bounds;
|
||||||
float x = _groundCheckCount <= 1
|
float x = _groundCheckCount <= 1
|
||||||
? b.center.x
|
? b.center.x
|
||||||
: Mathf.Lerp(b.min.x, b.max.x, (float)index / (_groundCheckCount - 1));
|
: Mathf.Lerp(b.min.x, b.max.x, (float)index / (_groundCheckCount - 1));
|
||||||
@@ -206,31 +213,23 @@ namespace BaseGames.Enemies
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 墙体射线起点:碰撞体朝向侧边缘中心高度
|
// 墙体射线起点:身体朝向侧边缘、中心高度
|
||||||
private Vector2 GetWallRayOrigin()
|
private Vector2 GetWallRayOrigin()
|
||||||
{
|
{
|
||||||
var col = _groundCheckCollider != null ? _groundCheckCollider : GetComponent<Collider2D>();
|
var body = Body;
|
||||||
if (col == null) return (Vector2)transform.position;
|
if (body == null || body.Collider == null) return (Vector2)transform.position;
|
||||||
Bounds b = col.bounds;
|
return new Vector2(body.FrontEdgeX(_facingDir), body.Bounds.center.y);
|
||||||
float x = _facingDir >= 0 ? b.max.x : b.min.x;
|
|
||||||
return new Vector2(x, b.center.y);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 悬崖射线起点:碰撞体前下角再向前偏移 _ledgeCheckFwdOffset
|
// 悬崖射线起点:身体前下角再向前偏移 _ledgeCheckFwdOffset
|
||||||
private Vector2 GetLedgeRayOrigin()
|
private Vector2 GetLedgeRayOrigin()
|
||||||
{
|
{
|
||||||
var col = _groundCheckCollider != null ? _groundCheckCollider : GetComponent<Collider2D>();
|
var body = Body;
|
||||||
if (col == null) return (Vector2)transform.position;
|
if (body == null || body.Collider == null) return (Vector2)transform.position;
|
||||||
Bounds b = col.bounds;
|
float sign = _facingDir >= 0 ? 1f : -1f;
|
||||||
float x = _facingDir >= 0
|
return new Vector2(body.FrontEdgeX(_facingDir) + sign * _ledgeCheckFwdOffset, body.Bounds.min.y);
|
||||||
? b.max.x + _ledgeCheckFwdOffset
|
|
||||||
: b.min.x - _ledgeCheckFwdOffset;
|
|
||||||
return new Vector2(x, b.min.y);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Collider2D GroundColliderOrSelf()
|
|
||||||
=> _groundCheckCollider != null ? _groundCheckCollider : GetComponent<Collider2D>();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 朝 <paramref name="dir"/> 前进是否会撞墙或让碰撞体前缘越过地形边缘(墙 或 悬崖)。
|
/// 朝 <paramref name="dir"/> 前进是否会撞墙或让碰撞体前缘越过地形边缘(墙 或 悬崖)。
|
||||||
/// 用碰撞体前缘(bounds.max.x / min.x)判定,天然考虑碰撞体宽度——
|
/// 用碰撞体前缘(bounds.max.x / min.x)判定,天然考虑碰撞体宽度——
|
||||||
@@ -242,37 +241,36 @@ namespace BaseGames.Enemies
|
|||||||
/// <summary>朝 <paramref name="dir"/> 前缘水平方向是否有墙。</summary>
|
/// <summary>朝 <paramref name="dir"/> 前缘水平方向是否有墙。</summary>
|
||||||
public bool WouldHitWallAhead(float dir)
|
public bool WouldHitWallAhead(float dir)
|
||||||
{
|
{
|
||||||
var col = GroundColliderOrSelf();
|
var body = Body;
|
||||||
if (col == null || _wallCheckDist <= 0f) return false;
|
if (body == null || body.Collider == null || _wallCheckDist <= 0f) return false;
|
||||||
Bounds b = col.bounds;
|
|
||||||
float sign = Mathf.Sign(dir);
|
float sign = Mathf.Sign(dir);
|
||||||
float edgeX = sign >= 0f ? b.max.x : b.min.x;
|
return Physics2D.Raycast(new Vector2(body.FrontEdgeX(dir), body.Bounds.center.y),
|
||||||
return Physics2D.Raycast(new Vector2(edgeX, b.center.y), new Vector2(sign, 0f), _wallCheckDist, _wallMask);
|
new Vector2(sign, 0f), _wallCheckDist, _wallMask);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>朝 <paramref name="dir"/> 前缘外是否为悬崖(脚边地面缺失,会让碰撞体越出地形边缘)。</summary>
|
/// <summary>朝 <paramref name="dir"/> 前缘外是否为悬崖(脚边地面缺失,会让碰撞体越出地形边缘)。</summary>
|
||||||
public bool WouldFallAhead(float dir)
|
public bool WouldFallAhead(float dir)
|
||||||
{
|
{
|
||||||
var col = GroundColliderOrSelf();
|
var body = Body;
|
||||||
if (col == null || _ledgeCheckDownDist <= 0f) return false;
|
if (body == null || body.Collider == null || _ledgeCheckDownDist <= 0f) return false;
|
||||||
Bounds b = col.bounds;
|
|
||||||
float sign = Mathf.Sign(dir);
|
float sign = Mathf.Sign(dir);
|
||||||
float edgeX = sign >= 0f ? b.max.x : b.min.x;
|
return !Physics2D.Raycast(
|
||||||
return !Physics2D.Raycast(new Vector2(edgeX + sign * _ledgeCheckFwdOffset, b.min.y),
|
new Vector2(body.FrontEdgeX(dir) + sign * _ledgeCheckFwdOffset, body.Bounds.min.y),
|
||||||
Vector2.down, _ledgeCheckDownDist, _groundMask);
|
Vector2.down, _ledgeCheckDownDist, _groundMask);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 身体中心到"能站住的平台边缘最近点"的水平安全余量 = 碰撞体水平半宽 + 悬崖检测前向偏移。
|
/// 身体中心到"能站住的平台边缘最近点"的水平安全余量 = 身体半宽 + 悬崖探测前向偏移。
|
||||||
/// WouldFallAhead 在"碰撞体前缘 + _ledgeCheckFwdOffset"处探地,故身体中心必须离平台边缘至少此距离,
|
/// 半宽取自唯一权威 <see cref="EnemyBase.Body"/>;探测偏移是本移动层自己的射线调参。
|
||||||
/// 否则前缘探出崖沿被夹停。供 nav 段内随机游走把随机点从段两端各内缩此值,避免随机到身体站不住的崖沿点。
|
/// WouldFallAhead 在"身体前缘 + _ledgeCheckFwdOffset"处探地,故身体中心必须离平台边缘至少此距离,
|
||||||
|
/// 否则前缘探出崖沿被夹停。供 nav 段内随机游走/目标点吸附把点从段两端内缩此值。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public float EdgeSafeMargin
|
public float EdgeSafeMargin
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
var col = GroundColliderOrSelf();
|
var body = Body;
|
||||||
float halfWidth = col != null ? col.bounds.extents.x : 0f;
|
float halfWidth = body != null ? body.HalfWidth : 0f;
|
||||||
return halfWidth + Mathf.Max(0f, _ledgeCheckFwdOffset);
|
return halfWidth + Mathf.Max(0f, _ledgeCheckFwdOffset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -302,8 +300,6 @@ namespace BaseGames.Enemies
|
|||||||
Debug.LogError($"[EnemyMovement] '{name}' 启用了墙检测(_wallCheckDist>0)但 _wallMask 为 Nothing," +
|
Debug.LogError($"[EnemyMovement] '{name}' 启用了墙检测(_wallCheckDist>0)但 _wallMask 为 Nothing," +
|
||||||
"撞墙判定将失效(会穿墙)。请在 Inspector 配置墙体层(推荐 Platform + Wall)。", this);
|
"撞墙判定将失效(会穿墙)。请在 Inspector 配置墙体层(推荐 Platform + Wall)。", this);
|
||||||
_rb = GetComponent<Rigidbody2D>();
|
_rb = GetComponent<Rigidbody2D>();
|
||||||
if (_groundCheckCollider == null)
|
|
||||||
_groundCheckCollider = GetComponent<Collider2D>();
|
|
||||||
|
|
||||||
// 从 Sprite 或 localScale 的初始状态推断朝向,并统一切换为 localScale 翻转。
|
// 从 Sprite 或 localScale 的初始状态推断朝向,并统一切换为 localScale 翻转。
|
||||||
// 这样子对象(含 RaySensor2D)会随 localScale 正确翻转,不再依赖 flipX。
|
// 这样子对象(含 RaySensor2D)会随 localScale 正确翻转,不再依赖 flipX。
|
||||||
@@ -331,8 +327,8 @@ namespace BaseGames.Enemies
|
|||||||
transform.localScale = new Vector3(signX, s.y, s.z);
|
transform.localScale = new Vector3(signX, s.y, s.z);
|
||||||
|
|
||||||
// 将 Visual 子节点的 localPosition 对齐到 Collider2D offset,使视觉中心与碰撞体中心重合
|
// 将 Visual 子节点的 localPosition 对齐到 Collider2D offset,使视觉中心与碰撞体中心重合
|
||||||
if (_visualRoot != null && _groundCheckCollider != null)
|
if (_visualRoot != null && Body != null && Body.Collider != null)
|
||||||
_visualRoot.localPosition = _groundCheckCollider.offset;
|
_visualRoot.localPosition = Body.Collider.offset;
|
||||||
|
|
||||||
if (_enableTurnAnimation)
|
if (_enableTurnAnimation)
|
||||||
{
|
{
|
||||||
@@ -722,7 +718,7 @@ namespace BaseGames.Enemies
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. 对齐 localPosition 到 Collider2D offset
|
// 2. 对齐 localPosition 到 Collider2D offset
|
||||||
var col = _groundCheckCollider != null ? _groundCheckCollider : GetComponent<Collider2D>();
|
var col = Body != null ? Body.Collider : GetComponent<Collider2D>();
|
||||||
if (col != null)
|
if (col != null)
|
||||||
{
|
{
|
||||||
UnityEditor.Undo.RecordObject(visual, "Set Visual LocalPosition");
|
UnityEditor.Undo.RecordObject(visual, "Set Visual LocalPosition");
|
||||||
|
|||||||
Reference in New Issue
Block a user