fix(scaffold): 碰撞体底部对齐 y=0(原点=脚底) 修正 NavAgent 有效位置 + 追击改回nav

根因: NavAgent 用 transform 原点(0,0)判定是否在导航段上,而脚手架 CreateBodyCollider
未设 offset(碰撞体居中,底部在-size.y/2),站到地面时原点悬在导航线上方→HasValidPosition=False。
修复: AlignColliderBottomToPivot 让所有身体/HurtBox/HitBox 碰撞体底部对齐 y=0(通用规则,
角色/敌人/Boss 同理);同步修 E001 prefab 碰撞体 offset。验证: 敌人落地后 navValid=True(自动)。
追击能力改回 nav 寻路(MoveTo),不再直接移动。

注: 导航路径"跟随"仍有既有集成问题(UpdatePath 后 agent 不跟随可达路径),需另行排查。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-09 16:22:54 +08:00
co-authored by Claude Opus 4.8
parent d0f69b0599
commit 790595d8de
3 changed files with 37 additions and 10 deletions
@@ -259,7 +259,7 @@ BoxCollider2D:
m_IsTrigger: 0 m_IsTrigger: 0
m_UsedByEffector: 0 m_UsedByEffector: 0
m_UsedByComposite: 0 m_UsedByComposite: 0
m_Offset: {x: 0, y: 0} m_Offset: {x: 0, y: 0.4}
m_SpriteTilingProperty: m_SpriteTilingProperty:
border: {x: 0, y: 0, z: 0, w: 0} border: {x: 0, y: 0, z: 0, w: 0}
pivot: {x: 0, y: 0} pivot: {x: 0, y: 0}
@@ -647,7 +647,7 @@ CircleCollider2D:
m_IsTrigger: 1 m_IsTrigger: 1
m_UsedByEffector: 0 m_UsedByEffector: 0
m_UsedByComposite: 0 m_UsedByComposite: 0
m_Offset: {x: 0, y: 0} m_Offset: {x: 0, y: 0.4}
serializedVersion: 2 serializedVersion: 2
m_Radius: 0.4 m_Radius: 0.4
--- !u!114 &8468978500168207169 --- !u!114 &8468978500168207169
@@ -751,7 +751,7 @@ CapsuleCollider2D:
m_IsTrigger: 1 m_IsTrigger: 1
m_UsedByEffector: 0 m_UsedByEffector: 0
m_UsedByComposite: 0 m_UsedByComposite: 0
m_Offset: {x: 0, y: 0} m_Offset: {x: 0, y: 0.375}
m_Size: {x: 0.55, y: 0.75} m_Size: {x: 0.55, y: 0.75}
m_Direction: 0 m_Direction: 0
--- !u!114 &7843048500329562790 --- !u!114 &7843048500329562790
@@ -473,6 +473,7 @@ namespace BaseGames.Editor
CapsuleCollider2D hurtCap = GetOrAddComponent<CapsuleCollider2D>(hurtBoxT.gameObject); CapsuleCollider2D hurtCap = GetOrAddComponent<CapsuleCollider2D>(hurtBoxT.gameObject);
hurtCap.isTrigger = true; hurtCap.isTrigger = true;
hurtCap.size = new Vector2(0.55f, 0.75f); hurtCap.size = new Vector2(0.55f, 0.75f);
AlignColliderBottomToPivot(hurtCap); // 底部对齐 y=0
HurtBox hurtBox = GetOrAddComponent<HurtBox>(hurtBoxT.gameObject); HurtBox hurtBox = GetOrAddComponent<HurtBox>(hurtBoxT.gameObject);
Transform contactT = GetOrCreateChild(go.transform, "ContactDamageZone"); Transform contactT = GetOrCreateChild(go.transform, "ContactDamageZone");
@@ -480,6 +481,7 @@ namespace BaseGames.Editor
CircleCollider2D contactCol = GetOrAddComponent<CircleCollider2D>(contactT.gameObject); CircleCollider2D contactCol = GetOrAddComponent<CircleCollider2D>(contactT.gameObject);
contactCol.isTrigger = true; contactCol.isTrigger = true;
contactCol.radius = 0.4f; contactCol.radius = 0.4f;
AlignColliderBottomToPivot(contactCol); // 底部对齐 y=0
HitBox contactHitBox = GetOrAddComponent<HitBox>(contactT.gameObject); HitBox contactHitBox = GetOrAddComponent<HitBox>(contactT.gameObject);
BodyContactDamage bodyContact = GetOrAddComponent<BodyContactDamage>(contactT.gameObject); BodyContactDamage bodyContact = GetOrAddComponent<BodyContactDamage>(contactT.gameObject);
bodyContact.enabled = false; // 伪装/待机期不伤人;由 e001_chase 能力在追击期开启 bodyContact.enabled = false; // 伪装/待机期不伤人;由 e001_chase 能力在追击期开启
@@ -2189,20 +2191,47 @@ namespace BaseGames.Editor
private static Collider2D CreateBodyCollider(GameObject go, EnemyBodyColliderType type, Vector2 size) private static Collider2D CreateBodyCollider(GameObject go, EnemyBodyColliderType type, Vector2 size)
{ {
Collider2D col;
switch (type) switch (type)
{ {
case EnemyBodyColliderType.Capsule: case EnemyBodyColliderType.Capsule:
var cap = GetOrAddComponent<CapsuleCollider2D>(go); var cap = GetOrAddComponent<CapsuleCollider2D>(go);
cap.size = size; cap.size = size;
return cap; col = cap;
break;
case EnemyBodyColliderType.Circle: case EnemyBodyColliderType.Circle:
var cir = GetOrAddComponent<CircleCollider2D>(go); var cir = GetOrAddComponent<CircleCollider2D>(go);
cir.radius = Mathf.Min(size.x, size.y) * 0.5f; cir.radius = Mathf.Min(size.x, size.y) * 0.5f;
return cir; col = cir;
break;
default: // Box default: // Box
var box = GetOrAddComponent<BoxCollider2D>(go); var box = GetOrAddComponent<BoxCollider2D>(go);
box.size = size; box.size = size;
return box; col = box;
break;
}
AlignColliderBottomToPivot(col); // 底部对齐 y=0:原点=脚底,供 NavAgent 检测原点落在导航线上
return col;
}
/// <summary>
/// 把碰撞体底部对齐到 transform 原点(y=0)——原点即脚底。
/// PathBerserker2d NavAgent 用 transform 原点(0,0)判定是否在导航段上,故所有创建物的碰撞体底部须在 y=0,
/// 否则站到地面时原点悬在导航线上方,导致 HasValidPosition=False、寻路失效。
/// </summary>
private static void AlignColliderBottomToPivot(Collider2D col)
{
switch (col)
{
case CapsuleCollider2D cap:
cap.offset = new Vector2(cap.offset.x, cap.size.y * 0.5f);
break;
case BoxCollider2D box:
box.offset = new Vector2(box.offset.x, box.size.y * 0.5f);
break;
case CircleCollider2D cir:
cir.offset = new Vector2(cir.offset.x, cir.radius);
break;
} }
} }
@@ -30,12 +30,10 @@ namespace BaseGames.Enemies.Abilities
_contactDamage.enabled = true; _contactDamage.enabled = true;
// 追击直到玩家消失或被 AI 中断(退出追击的决策归 AI,见 EnemyBrainContext.InterruptAbilities)。 // 追击直到玩家消失或被 AI 中断(退出追击的决策归 AI,见 EnemyBrainContext.InterruptAbilities)。
// 地面爬行怪用直接水平爬行追击(朝玩家方向),不依赖 A* 寻路 // 小怪走 nav 寻路(EnemyBase.MoveTo → 导航),寻路正确性依赖脚手架把碰撞体底部对齐 y=0
float runSpeed = _enemy.Stats != null ? _enemy.Stats.RunSpeed : 0f;
while (_enemy.PlayerTransform != null) while (_enemy.PlayerTransform != null)
{ {
float dir = _enemy.PlayerTransform.position.x >= _enemy.transform.position.x ? 1f : -1f; _enemy.MoveTo(_enemy.PlayerTransform.position);
_enemy.MoveInDirectionWithSpeed(dir, runSpeed);
yield return null; yield return null;
} }