refactor(nav): 敌人移动改为物理原生架构——根治 TBM 与 Dynamic 刚体冲突
根因: NavAgent.Position setter 直接写 transform.position, TBM 段移动每帧写它, 与敌人 Dynamic 刚体(重力/碰撞)冲突(旧代码靠"NavDriving清零velocity"缓解,但对Dynamic刚体直接 写transform.position本就不推荐,易卡墙/穿透/被物理覆盖)。 根本解: 寻路降为"顾问"(只给方向),移动执行归物理: - EnemyNavAgent.Awake 禁用 TBM.SegmentMovement(不再写 transform.position); - FixedUpdate 改物理原生桥: 朝 PathSubGoal 写 MoveDir+MoveSpeed 由 EnemyMovement 施加 velocity+重力执行, 到达子目标时 CompleteSegmentTraversal 推进段(原由TBM负责); - EnemyMovement 撤掉 NavDriving 的清零velocity/只朝向门, nav方向统一走velocity移动。 保留击退/击飞/落地等全部物理反馈。 附带修 PerceptionStateMachine: 追击维持改"追逐区 OR 视野区"任一(视野los可能被墙挡漏检, 仅靠视野会导致玩家在近身追逐区内却Chase↔Patrol抖动、nav被反复Stop不移动)。 PlayMode 验证: 敌人 nav 巡逻/追击均用velocity移动(FollowPath/onSeg=True)、追击稳定无抖动。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -52,7 +52,8 @@ namespace BaseGames.Enemies
|
|||||||
.To(c.Chase).When(x => x.Sensor.InChaseZone(), "InChaseZone")
|
.To(c.Chase).When(x => x.Sensor.InChaseZone(), "InChaseZone")
|
||||||
.To(c.Rest).When(x => !x.Sensor.InVisionZone(), "leftVision");
|
.To(c.Rest).When(x => !x.Sensor.InVisionZone(), "leftVision");
|
||||||
|
|
||||||
// 追击:委托能力实现移动/伤害;脱离视野 → R(永不回警觉);退出时中断追击能力
|
// 追击:委托能力实现移动/伤害;脱离**全部感知**(追逐区 且 视野区都不在) → R(永不回警觉);退出时中断追击能力。
|
||||||
|
// 维持用"追逐区 OR 视野区"任一:视野(los)可能被墙挡而漏检,仅靠视野会导致玩家在近身追逐区内却反复进出追击(抖动)。
|
||||||
b.State(c.Chase)
|
b.State(c.Chase)
|
||||||
.OnEnter(x =>
|
.OnEnter(x =>
|
||||||
{
|
{
|
||||||
@@ -60,7 +61,7 @@ namespace BaseGames.Enemies
|
|||||||
if (!string.IsNullOrEmpty(c.ChaseAbilityId)) x.Combat.UseAbility(c.ChaseAbilityId);
|
if (!string.IsNullOrEmpty(c.ChaseAbilityId)) x.Combat.UseAbility(c.ChaseAbilityId);
|
||||||
})
|
})
|
||||||
.OnExit(x => c.OnChaseExit?.Invoke(x))
|
.OnExit(x => c.OnChaseExit?.Invoke(x))
|
||||||
.To(c.Rest).When(x => !x.Sensor.InVisionZone(), "leftVision");
|
.To(c.Rest).When(x => !x.Sensor.InChaseZone() && !x.Sensor.InVisionZone(), "leftAllZones");
|
||||||
|
|
||||||
b.State(c.Death).OnEnter(x => c.OnDeathEnter?.Invoke(x));
|
b.State(c.Death).OnEnter(x => c.OnDeathEnter?.Invoke(x));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -306,10 +306,7 @@ namespace BaseGames.Enemies
|
|||||||
if (!_isTurning)
|
if (!_isTurning)
|
||||||
_facingDir = transform.localScale.x >= 0f ? _spriteDefaultFacingDir : -_spriteDefaultFacingDir;
|
_facingDir = transform.localScale.x >= 0f ? _spriteDefaultFacingDir : -_spriteDefaultFacingDir;
|
||||||
|
|
||||||
// NavDriving: TBM 直接写 transform.position,零速防止物理重力积累和双重驱动冲突。
|
// 物理原生移动:nav 只提供方向,移动由 velocity+重力执行(不再让 TBM 写 transform.position)。
|
||||||
if (NavDriving)
|
|
||||||
_rb.velocity = Vector2.zero;
|
|
||||||
|
|
||||||
IsGrounded = IsGroundedCheck();
|
IsGrounded = IsGroundedCheck();
|
||||||
WallAndLedgeCheck();
|
WallAndLedgeCheck();
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
@@ -379,7 +376,6 @@ namespace BaseGames.Enemies
|
|||||||
{
|
{
|
||||||
if (_isTurning) return;
|
if (_isTurning) return;
|
||||||
UpdateFacing(dir);
|
UpdateFacing(dir);
|
||||||
if (NavDriving) return; // TBM 驱动位置,仅更新朝向
|
|
||||||
var vel = _rb.velocity;
|
var vel = _rb.velocity;
|
||||||
vel.x = dir * _config.WalkSpeed;
|
vel.x = dir * _config.WalkSpeed;
|
||||||
_rb.velocity = vel;
|
_rb.velocity = vel;
|
||||||
@@ -390,7 +386,6 @@ namespace BaseGames.Enemies
|
|||||||
{
|
{
|
||||||
if (_isTurning) return;
|
if (_isTurning) return;
|
||||||
UpdateFacing(dir);
|
UpdateFacing(dir);
|
||||||
if (NavDriving) return; // TBM 驱动位置,仅更新朝向
|
|
||||||
var vel = _rb.velocity;
|
var vel = _rb.velocity;
|
||||||
vel.x = dir * speed;
|
vel.x = dir * speed;
|
||||||
_rb.velocity = vel;
|
_rb.velocity = vel;
|
||||||
|
|||||||
@@ -76,6 +76,11 @@ namespace BaseGames.Enemies.Navigation
|
|||||||
_movement = GetComponent<TransformBasedMovement>();
|
_movement = GetComponent<TransformBasedMovement>();
|
||||||
_enemyMovement = GetComponent<EnemyMovement>();
|
_enemyMovement = GetComponent<EnemyMovement>();
|
||||||
|
|
||||||
|
// 物理原生移动:禁用 TBM 段移动——不让它直接写 transform.position(会与 Dynamic 刚体物理冲突)。
|
||||||
|
// 段上的水平移动改由 EnemyMovement 用 velocity 执行、刚体重力保持贴地(见 FixedUpdate)。
|
||||||
|
if (_movement != null)
|
||||||
|
_movement.enabledFeatures &= ~TransformBasedMovement.FeatureFlags.SegmentMovement;
|
||||||
|
|
||||||
// 自动发现 INavLinkHandler 组件并注册(包含子对象)
|
// 自动发现 INavLinkHandler 组件并注册(包含子对象)
|
||||||
foreach (var handler in GetComponentsInChildren<INavLinkHandler>(true))
|
foreach (var handler in GetComponentsInChildren<INavLinkHandler>(true))
|
||||||
RegisterLinkHandler(handler);
|
RegisterLinkHandler(handler);
|
||||||
@@ -225,26 +230,33 @@ namespace BaseGames.Enemies.Navigation
|
|||||||
private void HandlePathFailed(NavAgent _) => OnNavPathFailed?.Invoke();
|
private void HandlePathFailed(NavAgent _) => OnNavPathFailed?.Invoke();
|
||||||
private void HandleGoalReached(NavAgent _) => OnGoalReached?.Invoke();
|
private void HandleGoalReached(NavAgent _) => OnGoalReached?.Invoke();
|
||||||
|
|
||||||
// ── NavDriving 信号桥 ───────────────────────────────────────────
|
// ── 物理原生移动桥 ─────────────────────────────────────────────
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 每物理帧向 <see cref="EnemyMovement"/> 写入导航方向信号,并设置 NavDriving 标志。
|
/// 跟随路径段时:向 <see cref="EnemyMovement"/> 写入朝当前子目标的水平方向+速度(由 velocity+重力执行),
|
||||||
/// NavDriving=true 时 EnemyMovement 只更新朝向,TBM 保留对 transform.position 的控制权。
|
/// 并在到达子目标时推进导航到下一段(TBM 段移动已在 Awake 禁用,改由本组件推进)。
|
||||||
|
/// 不直接写 transform.position → 与 Dynamic 刚体物理不冲突,保留击退/击飞/落地等物理反馈。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void FixedUpdate()
|
private void FixedUpdate()
|
||||||
{
|
{
|
||||||
if (_enemyMovement == null || _navAgent == null) return;
|
if (_enemyMovement == null || _navAgent == null) return;
|
||||||
|
|
||||||
bool onSegment = _navAgent.IsMovingOnSegment;
|
bool onSegment = _navAgent.IsMovingOnSegment;
|
||||||
_enemyMovement.NavDriving = onSegment;
|
_enemyMovement.NavDriving = onSegment; // 状态标记(供调试/上层判断"是否导航中")
|
||||||
|
|
||||||
if (onSegment)
|
if (onSegment)
|
||||||
{
|
{
|
||||||
|
// 物理原生:朝子目标水平移动,由 EnemyMovement 施加 velocity、刚体重力保持贴地
|
||||||
float dx = _navAgent.PathSubGoal.x - transform.position.x;
|
float dx = _navAgent.PathSubGoal.x - transform.position.x;
|
||||||
_enemyMovement.PendingInput.MoveDir = Mathf.Abs(dx) > 0.01f ? Mathf.Sign(dx) : 0f;
|
_enemyMovement.PendingInput.MoveDir = Mathf.Abs(dx) > 0.05f ? Mathf.Sign(dx) : 0f;
|
||||||
|
_enemyMovement.PendingInput.MoveSpeed = _movement != null ? _movement.movementSpeed : 0f;
|
||||||
|
|
||||||
|
// 段推进:到达当前子目标 → 推进到下一段(原由 TBM 负责,禁用后由本组件负责)
|
||||||
|
if (_navAgent.HasReachedCurrentSubGoal(0.25f))
|
||||||
|
_navAgent.CompleteSegmentTraversal();
|
||||||
}
|
}
|
||||||
else if (_wasNavOnSegment && !_navAgent.IsFollowingAPath)
|
else if (_wasNavOnSegment && !_navAgent.IsFollowingAPath)
|
||||||
{
|
{
|
||||||
// 导航刚结束(到达目标 / 路径失败)→ 清除残留 MoveDir
|
// 导航刚结束(到达目标 / 路径失败)→ 停下水平移动
|
||||||
_enemyMovement.PendingInput.WantStop = true;
|
_enemyMovement.PendingInput.WantStop = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user