feat: Update enemy AI and movement systems
- Enhanced Physics2D layer collision report with new interactions between Player and Enemy layers. - Refactored BD_InvestigateLastKnown to streamline animation handling and improve readability. - Simplified BD_MaintainCombatDistance by consolidating movement stop logic. - Updated BD_MoveToPlayer to set AI phase on start. - Improved BD_Patrol logic with better handling of stuck states and path failures. - Enhanced BD_PatrolWaypoints to manage stuck conditions and retry logic more effectively. - Refined BD_ReturnToHome to remove unnecessary animation calls. - Updated BD_WalkRandom to ensure AI phase is set correctly on start. - Improved EnemyAbilityBase to delegate target facing to the movement system. - Enhanced EnemyBase with new movement methods for better control. - Refactored EnemyMovement to introduce a new input system for handling movement and facing. - Added EnemyMoveInput struct to encapsulate movement intentions. - Updated Physics2DSettings to reflect new layer collision matrix. - Introduced RTK CLI instructions for optimized command usage.
This commit is contained in:
@@ -50,12 +50,31 @@ namespace BaseGames.Enemies
|
||||
private int _pendingFacingDir; // 转身目标方向,转身完成后 ApplyFacingFlip 使用
|
||||
private Coroutine _turnCoroutine;
|
||||
|
||||
// ── 输入信号(BD 任务在 Update 写入,FixedUpdate 消费后自动清零)──
|
||||
public EnemyMoveInput PendingInput;
|
||||
|
||||
public bool IsGrounded { get; private set; }
|
||||
/// <summary>当前朝向:1 = 右,-1 = 左。</summary>
|
||||
public int FacingDirection => _facingDir;
|
||||
/// <summary>当前是否正在播放转身动画(移动输入在此期间被屏蔽)。</summary>
|
||||
public bool IsTurning => _isTurning;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[Header("── 运行时调试(仅 Editor)──")]
|
||||
[SerializeField] private int _dbg_FacingDirection;
|
||||
[SerializeField] private float _dbg_VelocityX;
|
||||
[SerializeField] private float _dbg_VelocityY;
|
||||
[SerializeField] private bool _dbg_IsGrounded;
|
||||
[SerializeField] private bool _dbg_IsTurning;
|
||||
[Header("── 输入信号(仅 Editor)──")]
|
||||
[SerializeField] private float _dbg_Input_MoveDir;
|
||||
[SerializeField] private float _dbg_Input_MoveSpeed;
|
||||
[SerializeField] private bool _dbg_Input_WantStop;
|
||||
[SerializeField] private bool _dbg_Input_WantFace;
|
||||
[SerializeField] private Vector2 _dbg_Input_FaceTargetPos;
|
||||
[SerializeField] private int _dbg_Input_FaceDir;
|
||||
#endif
|
||||
|
||||
// ── INavLinkHandler ────────────────────────────────────────────
|
||||
private static readonly NavLinkType[] _handledTypes =
|
||||
new[] { NavLinkType.Jump, NavLinkType.Fall };
|
||||
@@ -136,6 +155,25 @@ namespace BaseGames.Enemies
|
||||
Debug.Assert(_config != null, "[EnemyMovement] _config 未赋值,请在 Prefab Inspector 中指定 EnemyStatsSO。", this);
|
||||
_rb = GetComponent<Rigidbody2D>();
|
||||
|
||||
// 从 Sprite 或 localScale 的初始状态推断朝向,并统一切换为 localScale 翻转。
|
||||
// 这样子对象(含 RaySensor2D)会随 localScale 正确翻转,不再依赖 flipX。
|
||||
if (_spriteRenderer != null)
|
||||
{
|
||||
// 两个信号均可能携带初始朝向信息:flipX 或 localScale.x < 0,
|
||||
// XOR 组合:恰好一个翻转 → 面左;两个都翻(互相抵消)→ 面右。
|
||||
bool flippedBySprite = _spriteRenderer.flipX;
|
||||
bool flippedByScale = transform.localScale.x < 0f;
|
||||
_facingDir = (flippedBySprite ^ flippedByScale) ? -1 : 1;
|
||||
|
||||
_spriteRenderer.flipX = false; // 后续由 localScale 驱动,避免双重镜像
|
||||
Vector3 s = transform.localScale;
|
||||
transform.localScale = new Vector3(Mathf.Abs(s.x) * _facingDir, s.y, s.z);
|
||||
}
|
||||
else
|
||||
{
|
||||
_facingDir = transform.localScale.x >= 0f ? 1 : -1;
|
||||
}
|
||||
|
||||
if (_enableTurnAnimation)
|
||||
{
|
||||
if (_animancer == null) _animancer = GetComponentInParent<AnimancerComponent>(true);
|
||||
@@ -147,9 +185,71 @@ namespace BaseGames.Enemies
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
// 持久信号在对象禁用时必须清零,防止重新启用时继承残留移动状态。
|
||||
PendingInput = default;
|
||||
StopHorizontal();
|
||||
}
|
||||
|
||||
private void FixedUpdate()
|
||||
{
|
||||
IsGrounded = IsGroundedCheck();
|
||||
#if UNITY_EDITOR
|
||||
_dbg_Input_MoveDir = PendingInput.MoveDir;
|
||||
_dbg_Input_MoveSpeed = PendingInput.MoveSpeed;
|
||||
_dbg_Input_WantStop = PendingInput.WantStop;
|
||||
_dbg_Input_WantFace = PendingInput.WantFace;
|
||||
_dbg_Input_FaceTargetPos = PendingInput.FaceTargetPos;
|
||||
_dbg_Input_FaceDir = PendingInput.FaceDir;
|
||||
#endif
|
||||
ConsumeInput();
|
||||
#if UNITY_EDITOR
|
||||
_dbg_FacingDirection = _facingDir;
|
||||
_dbg_VelocityX = _rb != null ? _rb.velocity.x : 0f;
|
||||
_dbg_VelocityY = _rb != null ? _rb.velocity.y : 0f;
|
||||
_dbg_IsGrounded = IsGrounded;
|
||||
_dbg_IsTurning = _isTurning;
|
||||
#endif
|
||||
}
|
||||
|
||||
private void ConsumeInput()
|
||||
{
|
||||
// ── 一次性脉冲:消费后清零 ─────────────────────────────────────
|
||||
// WantStop / WantFace 只需写一次,消费后自动清除,
|
||||
// 避免 BD 任务每帧续写而产生的不必要开销。
|
||||
bool wantStop = PendingInput.WantStop;
|
||||
bool wantFace = PendingInput.WantFace;
|
||||
int faceDir = PendingInput.FaceDir;
|
||||
var facePosSnapshot = PendingInput.FaceTargetPos;
|
||||
PendingInput.WantStop = false;
|
||||
PendingInput.WantFace = false;
|
||||
|
||||
// ── 持久字段:MoveDir / MoveSpeed 不清零 ─────────────────────
|
||||
// 解决 FixedUpdate 频率 > Update 频率时的空帧问题:
|
||||
// 两次 Update 之间如果 FixedUpdate 多执行一次,之前写入的 MoveDir
|
||||
// 仍然有效,不会产生意外的 StopHorizontal。
|
||||
if (wantStop)
|
||||
{
|
||||
PendingInput.MoveDir = 0f;
|
||||
PendingInput.MoveSpeed = 0f;
|
||||
StopHorizontal();
|
||||
}
|
||||
else if (PendingInput.MoveDir != 0f)
|
||||
{
|
||||
if (PendingInput.MoveSpeed > 0f)
|
||||
MoveWithSpeed(PendingInput.MoveDir, PendingInput.MoveSpeed);
|
||||
else
|
||||
MoveHorizontal(PendingInput.MoveDir);
|
||||
}
|
||||
|
||||
if (wantFace && !_isTurning)
|
||||
{
|
||||
if (faceDir != 0)
|
||||
UpdateFacing(faceDir > 0 ? 1f : -1f);
|
||||
else
|
||||
FaceTarget(facePosSnapshot);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>按 SO 配置速度水平移动。dir: +1 右 / -1 左 / 0 停止。转身动画期间调用无效。</summary>
|
||||
@@ -267,18 +367,13 @@ namespace BaseGames.Enemies
|
||||
_isTurning = true;
|
||||
StopHorizontal();
|
||||
|
||||
_animancer.Play(_animConfig.Turn);
|
||||
float elapsed = 0f;
|
||||
float duration = _animConfig.Turn.length;
|
||||
|
||||
while (elapsed < duration)
|
||||
{
|
||||
elapsed += Time.deltaTime;
|
||||
yield return null;
|
||||
}
|
||||
// yield return state:Animancer 的 AnimancerState 是 CustomYieldInstruction,
|
||||
// 等待动画自然播完,与 Layer/State 速度缩放无关,比手动计时更可靠。
|
||||
var state = _animancer.Play(_animConfig.Turn);
|
||||
yield return state;
|
||||
|
||||
ApplyFacingFlip(newDir);
|
||||
_isTurning = false;
|
||||
_isTurning = false;
|
||||
_turnCoroutine = null;
|
||||
}
|
||||
|
||||
@@ -298,17 +393,15 @@ namespace BaseGames.Enemies
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>真正执行朝向翻转(修改 SpriteRenderer.flipX 或 localScale)。</summary>
|
||||
/// <summary>真正执行朝向翻转。始终用 localScale 翻转,子对象(传感器 RaySensor2D)随之正确翻转。</summary>
|
||||
private void ApplyFacingFlip(int newDir)
|
||||
{
|
||||
_facingDir = newDir;
|
||||
// 若挂有 SpriteRenderer,重置 flipX = false(localScale 已负责镜像,避免双重翻转)。
|
||||
if (_spriteRenderer != null)
|
||||
_spriteRenderer.flipX = newDir < 0;
|
||||
else
|
||||
{
|
||||
Vector3 s = transform.localScale;
|
||||
transform.localScale = new Vector3(Mathf.Abs(s.x) * newDir, s.y, s.z);
|
||||
}
|
||||
_spriteRenderer.flipX = false;
|
||||
Vector3 s = transform.localScale;
|
||||
transform.localScale = new Vector3(Mathf.Abs(s.x) * newDir, s.y, s.z);
|
||||
}
|
||||
|
||||
private void OnDrawGizmos()
|
||||
@@ -326,6 +419,18 @@ namespace BaseGames.Enemies
|
||||
Vector3 center = transform.position;
|
||||
DrawArrow2D(center, center + new Vector3(_facingDir * 0.5f, 0f, 0f),
|
||||
new Color(1f, 0.6f, 0.1f, 0.9f));
|
||||
|
||||
// ── 3. 地面检测射线(接地亮绿 / 未接地暗绿)─────────────────
|
||||
if (_groundCheckDist > 0f)
|
||||
{
|
||||
bool grounded = Application.isPlaying && IsGrounded;
|
||||
Gizmos.color = grounded
|
||||
? new Color(0.2f, 1f, 0.35f, 0.90f)
|
||||
: new Color(0.4f, 0.75f, 0.4f, 0.40f);
|
||||
Vector3 origin = transform.position;
|
||||
Gizmos.DrawLine(origin, origin + Vector3.down * _groundCheckDist);
|
||||
Gizmos.DrawWireSphere(origin + Vector3.down * _groundCheckDist, 0.04f);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user