From 7f7d237915a04b6ba2e1645f4c329bc1e9004710 Mon Sep 17 00:00:00 2001 From: Joywayer Date: Wed, 22 Jul 2026 13:45:21 +0800 Subject: [PATCH] =?UTF-8?q?feat(enemy):=20Wander=20=E5=88=B0=E8=BE=BE?= =?UTF-8?q?=E9=9A=8F=E6=9C=BA=E7=82=B9=E5=90=8E=E5=8F=AF=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E5=81=9C=E9=A1=BF=E6=97=B6=E9=95=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 到达一个段内随机游走点后原地停顿 [_wanderPauseMin, _wanderPauseMax] 秒再挑下一个点: - 两个可配置字段(相等=固定时长;都为 0=不停顿立即挑点),每次停顿时长在区间内随机 - 停顿期间切 Idle 步态(否则站着播 Walk=原地踏步),速度归零 - 切换 locomotion 模式时清停顿态,避免残留 实测:移动→到点→停顿(Idle,velX=0)→再出发 循环正常。 Co-Authored-By: Claude Opus 4.8 (1M context) --- .../Enemies/Navigation/EnemyLocomotion.cs | 51 ++++++++++++++++--- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/Assets/_Game/Scripts/Enemies/Navigation/EnemyLocomotion.cs b/Assets/_Game/Scripts/Enemies/Navigation/EnemyLocomotion.cs index a5c192b8..3e8444f8 100644 --- a/Assets/_Game/Scripts/Enemies/Navigation/EnemyLocomotion.cs +++ b/Assets/_Game/Scripts/Enemies/Navigation/EnemyLocomotion.cs @@ -14,6 +14,12 @@ namespace BaseGames.Enemies [Header("巡逻策略(Wander/Pace/Waypoints)")] [SerializeField] private PatrolStrategy _patrolStrategy = PatrolStrategy.Wander; + [Header("Wander 策略参数")] + [Tooltip("到达一个随机游走点后的停顿时长下限(秒)。停顿期间原地待机(播 Idle),再挑下一个点。")] + [SerializeField] [Min(0f)] private float _wanderPauseMin = 0.5f; + [Tooltip("到达一个随机游走点后的停顿时长上限(秒)。与下限相同=固定时长;两者都为 0=不停顿、立即挑下一个点。")] + [SerializeField] [Min(0f)] private float _wanderPauseMax = 1.5f; + [Header("Waypoints 策略参数")] [SerializeField] private Transform[] _waypoints; [SerializeField] private bool _pingPong; @@ -32,6 +38,9 @@ namespace BaseGames.Enemies private int _wpIndex = -1; private int _wpDir = 1; private bool _warnedNoWaypoints; + // Wander 到点停顿状态 + private bool _wanderPausing; + private float _wanderPauseTimer; public LocomotionMode CurrentMode => _mode; public bool IsMoving => _enemy != null && _enemy.Nav != null && _enemy.Nav.IsMoving; @@ -48,6 +57,7 @@ namespace BaseGames.Enemies { if (_mode == mode) return; _mode = mode; + _wanderPausing = false; // 切换模式清 Wander 停顿态 if (mode == LocomotionMode.Idle) _enemy?.StopMovement(); if (mode == LocomotionMode.Patrol && _enemy?.Stats != null) _enemy.Nav?.SetSpeed(_enemy.Stats.WalkSpeed); @@ -83,11 +93,16 @@ namespace BaseGames.Enemies private void Update() { if (_enemy == null) return; - if (!_gaitInit || _gaitMode != _mode) + // Wander 到点停顿期间用 Idle 步态(原地待机,否则站着播 Walk=原地踏步)。 + LocomotionMode gait = (_mode == LocomotionMode.Patrol + && _patrolStrategy == PatrolStrategy.Wander + && _wanderPausing) + ? LocomotionMode.Idle : _mode; + if (!_gaitInit || _gaitMode != gait) { - _enemy.PlayLocomotionClip(_mode); + _enemy.PlayLocomotionClip(gait); // 招式动画锁定期内不算"已应用",解锁后重新同步步态(否则 Skill_End 后卡在收招动画) - if (!_enemy.IsAnimLocked) { _gaitMode = _mode; _gaitInit = true; } + if (!_enemy.IsAnimLocked) { _gaitMode = gait; _gaitInit = true; } } switch (_mode) { @@ -106,14 +121,38 @@ namespace BaseGames.Enemies } } + /// + /// 段内随机游走:随机点限制在当前所站的导航段上(不跨 NavLink,避免崖边夹停/卡 link), + /// 到达一个点后原地停顿 [_wanderPauseMin, _wanderPauseMax] 秒(期间播 Idle),再挑下一个点。 + /// + private void TickWander() + { + if (_enemy.Nav == null) return; + + // 移动中:清停顿态,等到达 + if (_enemy.Nav.IsMoving) { _wanderPausing = false; return; } + + // 已到达(或尚未出发):先停顿计时,再挑下一个点 + if (!_wanderPausing) + { + _wanderPausing = true; + _wanderPauseTimer = Random.Range(_wanderPauseMin, Mathf.Max(_wanderPauseMin, _wanderPauseMax)); + } + + _wanderPauseTimer -= Time.deltaTime; + if (_wanderPauseTimer <= 0f) + { + _wanderPausing = false; + _enemy.Nav.WalkToRandomOnSegment(); + } + } + private void TickPatrol() { switch (_patrolStrategy) { case PatrolStrategy.Wander: - // 段内随机游走:随机点限制在当前所站的导航段上,不跨越 NavLink, - // 避免地面单位游走时被崖边夹停或卡在过不去的跳/落 link 处。 - if (_enemy.Nav != null && !_enemy.Nav.IsMoving) _enemy.Nav.WalkToRandomOnSegment(); + TickWander(); break; case PatrolStrategy.Pace: // 撞墙/悬崖翻向的来回踱步(速度驱动,不依赖 nav)