Fix navigation issue

This commit is contained in:
wjsjwr 2026-01-17 14:59:44 +08:00
parent 5cf70ccf4b
commit fcf4121cbe
2 changed files with 94 additions and 275 deletions

View File

@ -54,6 +54,7 @@ public partial class CampusController : Node2D
private Rect2I _astarRegion; private Rect2I _astarRegion;
private int _astarMapIteration; private int _astarMapIteration;
private Rid _astarMap; private Rid _astarMap;
private readonly List<Vector2I> _astarWalkableCells = new();
private const int GridSearchRadius = 6; private const int GridSearchRadius = 6;
// Called when the node enters the scene tree for the first time. // Called when the node enters the scene tree for the first time.
@ -365,18 +366,25 @@ public partial class CampusController : Node2D
} }
} }
SpawnStudents(map); if (!EnsureAStarGrid() || _astarWalkableCells.Count == 0)
_spawnPending = false; {
return;
}
if (SpawnStudents())
{
_spawnPending = false;
}
} }
private void SpawnStudents(Rid map) private bool SpawnStudents()
{ {
_coveragePoints.Clear(); _coveragePoints.Clear();
_coveragePoints.AddRange(BuildCoveragePoints()); _coveragePoints.AddRange(BuildCoveragePoints());
if (_coveragePoints.Count == 0) if (_coveragePoints.Count == 0)
{ {
GD.PushWarning("未采样到可行走区域,跳过学生生成。"); GD.PushWarning("未采样到可行走区域,跳过学生生成。");
return; return false;
} }
for (int i = 0; i < StudentCount; i++) for (int i = 0; i < StudentCount; i++)
@ -390,15 +398,15 @@ public partial class CampusController : Node2D
} }
_studentsRoot.AddChild(student); _studentsRoot.AddChild(student);
student.SetNavigationMap(map);
student.MoveSpeed = AgentMoveSpeed; student.MoveSpeed = AgentMoveSpeed;
student.GridWalkableTolerance = GridWalkableTolerance;
// 随机放置在可行走区域,并交给行为系统控制 // 随机放置在可行走区域,并交给行为系统控制
var randomIndex = _random != null if (!TryGetRandomWalkableGridPoint(out var gridPoint))
? _random.Next(0, _coveragePoints.Count) {
: (int)GD.RandRange(0, _coveragePoints.Count - 1); GD.PushWarning("AStarGrid2D 未准备好,无法放置学生。");
student.GlobalPosition = _coveragePoints[randomIndex]; return false;
}
student.GlobalPosition = gridPoint;
student.ApplyRandomTheme(); student.ApplyRandomTheme();
var runtime = BuildRandomAgentRuntime(i); var runtime = BuildRandomAgentRuntime(i);
@ -417,6 +425,8 @@ public partial class CampusController : Node2D
_behaviorAgents.Add(agent); _behaviorAgents.Add(agent);
LogSpawn(runtime); LogSpawn(runtime);
} }
return true;
} }
private static readonly CampusTaskType[] TaskTypePool = private static readonly CampusTaskType[] TaskTypePool =
@ -703,6 +713,21 @@ public partial class CampusController : Node2D
return result; return result;
} }
private bool TryGetRandomWalkableGridPoint(out Vector2 point)
{
point = Vector2.Zero;
if (!EnsureAStarGrid() || _astarWalkableCells.Count == 0)
{
return false;
}
var index = _random != null
? _random.Next(0, _astarWalkableCells.Count)
: (int)GD.RandRange(0, _astarWalkableCells.Count - 1);
point = GridToWorld(_astarWalkableCells[index]);
return true;
}
private bool EnsureAStarGrid() private bool EnsureAStarGrid()
{ {
if (!IsNavigationMapReady(out var map)) if (!IsNavigationMapReady(out var map))
@ -737,6 +762,7 @@ public partial class CampusController : Node2D
_astarGrid.DiagonalMode = AStarGrid2D.DiagonalModeEnum.Never; _astarGrid.DiagonalMode = AStarGrid2D.DiagonalModeEnum.Never;
_astarGrid.Update(); _astarGrid.Update();
_astarWalkableCells.Clear();
for (var x = region.Position.X; x < region.Position.X + region.Size.X; x++) for (var x = region.Position.X; x < region.Position.X + region.Size.X; x++)
{ {
for (var y = region.Position.Y; y < region.Position.Y + region.Size.Y; y++) for (var y = region.Position.Y; y < region.Position.Y + region.Size.Y; y++)
@ -745,6 +771,10 @@ public partial class CampusController : Node2D
var center = GridToWorld(cell); var center = GridToWorld(cell);
var walkable = IsCellWalkable(center, map); var walkable = IsCellWalkable(center, map);
_astarGrid.SetPointSolid(cell, !walkable); _astarGrid.SetPointSolid(cell, !walkable);
if (walkable)
{
_astarWalkableCells.Add(cell);
}
} }
} }
@ -973,6 +1003,7 @@ public partial class CampusController : Node2D
_astarGrid = null; _astarGrid = null;
_astarMap = new Rid(); _astarMap = new Rid();
_astarMapIteration = 0; _astarMapIteration = 0;
_astarWalkableCells.Clear();
_navBakePending = false; _navBakePending = false;
_navBakeReady = true; _navBakeReady = true;
RequestDebugGridRedraw(); RequestDebugGridRedraw();

View File

@ -54,10 +54,6 @@ public partial class CampusStudent : CharacterBody2D
/// </summary> /// </summary>
private Vector2 _lastPosition; private Vector2 _lastPosition;
/// <summary>
/// 导航代理
/// </summary>
private NavigationAgent2D _navigationAgent;
/// <summary> /// <summary>
/// 服装精灵 /// 服装精灵
/// </summary> /// </summary>
@ -83,10 +79,6 @@ public partial class CampusStudent : CharacterBody2D
/// </summary> /// </summary>
private float _stuckTimer; private float _stuckTimer;
/// <summary> /// <summary>
/// 导航地图RID
/// </summary>
private Rid _navigationMap;
/// <summary>
/// 当前目标点 /// 当前目标点
/// </summary> /// </summary>
private Vector2 _currentTarget = Vector2.Zero; private Vector2 _currentTarget = Vector2.Zero;
@ -156,10 +148,6 @@ public partial class CampusStudent : CharacterBody2D
/// </summary> /// </summary>
[Export] public bool Use16X16Sprites { get; set; } = true; [Export] public bool Use16X16Sprites { get; set; } = true;
/// <summary> /// <summary>
/// 是否启用避让
/// </summary>
[Export] public bool EnableAvoidance { get; set; }
/// <summary>
/// 卡住重新寻路时间 /// 卡住重新寻路时间
/// </summary> /// </summary>
[Export] public float StuckRepathSeconds { get; set; } = 0.6f; [Export] public float StuckRepathSeconds { get; set; } = 0.6f;
@ -168,18 +156,6 @@ public partial class CampusStudent : CharacterBody2D
/// </summary> /// </summary>
[Export] public float StuckDistanceEpsilon { get; set; } = 2.0f; [Export] public float StuckDistanceEpsilon { get; set; } = 2.0f;
/// <summary> /// <summary>
/// 导航网格吸附距离
/// </summary>
[Export] public float NavMeshClampDistance { get; set; } = 6.0f;
/// <summary>
/// 是否使用网格寻路
/// </summary>
[Export] public bool UseGridPathfinding { get; set; } = true;
/// <summary>
/// 网格可行走容差
/// </summary>
[Export] public float GridWalkableTolerance { get; set; } = 2.0f;
/// <summary>
/// 网格重新寻路间隔 /// 网格重新寻路间隔
/// </summary> /// </summary>
[Export] public float GridRepathInterval { get; set; } = 0.25f; [Export] public float GridRepathInterval { get; set; } = 0.25f;
@ -201,7 +177,6 @@ public partial class CampusStudent : CharacterBody2D
/// </summary> /// </summary>
public override void _Ready() public override void _Ready()
{ {
_navigationAgent = GetNodeOrNull<NavigationAgent2D>("NavigationAgent2D");
_animationPlayer = GetNodeOrNull<AnimationPlayer>("AnimationPlayer"); _animationPlayer = GetNodeOrNull<AnimationPlayer>("AnimationPlayer");
_campusController = GetTree()?.CurrentScene as CampusController; _campusController = GetTree()?.CurrentScene as CampusController;
CacheSprites(); CacheSprites();
@ -212,22 +187,7 @@ public partial class CampusStudent : CharacterBody2D
_animationPlayer.AnimationFinished += OnAnimationFinished; _animationPlayer.AnimationFinished += OnAnimationFinished;
} }
if (_navigationAgent != null) if (_patrolConfigured) AdvanceTarget();
{
// 强制关闭避让,学生之间直接穿过
EnableAvoidance = false;
// 让寻路点更“贴近目标”,避免走到边缘时抖动
_navigationAgent.PathDesiredDistance = TargetReachDistance;
_navigationAgent.TargetDesiredDistance = TargetReachDistance;
_navigationAgent.AvoidanceEnabled = EnableAvoidance;
if (EnableAvoidance)
// 开启避让时使用安全速度回调进行移动
_navigationAgent.VelocityComputed += OnVelocityComputed;
if (_patrolConfigured) AdvanceTarget();
}
PlayIdleAnimation(); PlayIdleAnimation();
_lastPosition = GlobalPosition; _lastPosition = GlobalPosition;
@ -251,40 +211,27 @@ public partial class CampusStudent : CharacterBody2D
if (_phoneExitLocked) if (_phoneExitLocked)
{ {
Velocity = Vector2.Zero; Velocity = Vector2.Zero;
if (!EnableAvoidance && _usePhysicsMovement) if (_usePhysicsMovement)
{ {
MoveAndSlide(); MoveAndSlide();
} }
return; return;
} }
var hasPatrolTarget = !_behaviorControlEnabled && _patrolPoints.Count > 0;
if (_behaviorControlEnabled && _behaviorHasTarget) if (_behaviorControlEnabled && _behaviorHasTarget)
{ {
TryBuildPendingGridPath(); TryBuildPendingGridPath();
if (_gridPathActive)
{
if (ProcessGridPathMovement(delta))
{
return;
}
}
else if (UseGridPathfinding && _gridPathPending)
{
Velocity = Vector2.Zero;
if (!EnableAvoidance && _usePhysicsMovement)
{
MoveAndSlide();
}
if (!_phoneIdleActive && !_phoneExitLocked)
{
PlayIdleAnimation();
}
UpdateStuckTimer(delta);
return;
}
} }
if (_navigationAgent == null || (!_behaviorControlEnabled && _patrolPoints.Count == 0) || (_behaviorControlEnabled && !_behaviorHasTarget)) else if (hasPatrolTarget)
{
if (!_hasTarget || (!_gridPathPending && !_gridPathActive))
{
AdvanceTarget();
}
TryBuildPendingGridPath();
}
else
{ {
if (!_phoneIdleActive && !_phoneExitLocked) if (!_phoneIdleActive && !_phoneExitLocked)
{ {
@ -293,35 +240,23 @@ public partial class CampusStudent : CharacterBody2D
return; return;
} }
// 到达目标点或无路可走时,切换到下一个巡游点 if (_gridPathActive)
if (_navigationAgent.IsNavigationFinished())
{ {
if (_behaviorControlEnabled) if (ProcessGridPathMovement(delta))
{ {
Velocity = Vector2.Zero; if (hasPatrolTarget && !_gridPathActive)
if (!EnableAvoidance && _usePhysicsMovement)
{ {
MoveAndSlide(); AdvanceTarget();
} }
if (!_phoneIdleActive && !_phoneExitLocked)
{
PlayIdleAnimation();
}
UpdateStuckTimer(delta);
return; return;
} }
AdvanceTarget();
} }
var nextPosition = _navigationAgent.GetNextPathPosition(); if (_gridPathPending)
var toNext = nextPosition - GlobalPosition;
if (toNext.LengthSquared() < 0.01f)
{ {
if (!EnableAvoidance && _usePhysicsMovement) Velocity = Vector2.Zero;
if (_usePhysicsMovement)
{ {
Velocity = Vector2.Zero;
MoveAndSlide(); MoveAndSlide();
} }
@ -333,21 +268,9 @@ public partial class CampusStudent : CharacterBody2D
return; return;
} }
var direction = toNext.Normalized(); if (!_phoneIdleActive && !_phoneExitLocked)
var desiredVelocity = direction * MoveSpeed;
if (EnableAvoidance)
{ {
// 交给导航系统做群体避让 PlayIdleAnimation();
_navigationAgent.Velocity = desiredVelocity;
}
else
{
// 未启用避让时直接移动
Velocity = ClampVelocityToNavMesh(desiredVelocity);
ApplyMovement(delta);
UpdateFacingAnimation(Velocity);
UpdateStuckTimer(delta);
} }
} }
@ -386,7 +309,7 @@ public partial class CampusStudent : CharacterBody2D
_patrolConfigured = true; _patrolConfigured = true;
ApplyRandomTheme(); ApplyRandomTheme();
if (_navigationAgent != null) AdvanceTarget(); AdvanceTarget();
} }
/// <summary> /// <summary>
@ -407,28 +330,9 @@ public partial class CampusStudent : CharacterBody2D
public void SetBehaviorTarget(Vector2 target) public void SetBehaviorTarget(Vector2 target)
{ {
_behaviorControlEnabled = true; _behaviorControlEnabled = true;
target = ClampTargetToNavMesh(target);
_behaviorTarget = target; _behaviorTarget = target;
_behaviorHasTarget = true; _behaviorHasTarget = true;
_currentTarget = target; BeginGridTarget(target);
_hasTarget = true;
_stuckTimer = 0.0f;
_gridPathActive = false;
_gridPathPending = false;
_gridPathIndex = 0;
_gridPathRetryTimer = 0.0f;
_gridPath.Clear();
if (UseGridPathfinding)
{
_gridPathPending = true;
TryBuildPendingGridPath();
}
if (!UseGridPathfinding && _navigationAgent != null)
{
_navigationAgent.TargetPosition = target;
}
} }
/// <summary> /// <summary>
@ -447,6 +351,23 @@ public partial class CampusStudent : CharacterBody2D
_gridPath.Clear(); _gridPath.Clear();
} }
/// <summary>
/// 设置网格寻路目标
/// </summary>
/// <param name="target">目标位置</param>
private void BeginGridTarget(Vector2 target)
{
_currentTarget = target;
_hasTarget = true;
_stuckTimer = 0.0f;
_gridPathActive = false;
_gridPathPending = true;
_gridPathIndex = 0;
_gridPathRetryTimer = 0.0f;
_gridPath.Clear();
TryBuildPendingGridPath();
}
/// <summary> /// <summary>
/// 是否已到达行为目标 /// 是否已到达行为目标
/// </summary> /// </summary>
@ -462,8 +383,7 @@ public partial class CampusStudent : CharacterBody2D
{ {
return false; return false;
} }
if (_navigationAgent == null) return true; return true;
return _navigationAgent.IsNavigationFinished();
} }
/// <summary> /// <summary>
@ -515,16 +435,6 @@ public partial class CampusStudent : CharacterBody2D
} }
} }
/// <summary>
/// 设置导航地图
/// </summary>
/// <param name="map">地图RID</param>
public void SetNavigationMap(Rid map)
{
// 由校园控制器传入导航地图,供本地边界夹紧使用
_navigationMap = map;
}
/// <summary> /// <summary>
/// 应用随机主题 /// 应用随机主题
/// </summary> /// </summary>
@ -572,7 +482,7 @@ public partial class CampusStudent : CharacterBody2D
/// </summary> /// </summary>
private void AdvanceTarget() private void AdvanceTarget()
{ {
if (_patrolPoints.Count == 0 || _navigationAgent == null) return; if (_patrolPoints.Count == 0) return;
if (_behaviorControlEnabled) return; if (_behaviorControlEnabled) return;
// 避免当前点过近导致原地抖动,最多尝试一轮 // 避免当前点过近导致原地抖动,最多尝试一轮
@ -582,40 +492,12 @@ public partial class CampusStudent : CharacterBody2D
_patrolIndex = (_patrolIndex + 1) % _patrolPoints.Count; _patrolIndex = (_patrolIndex + 1) % _patrolPoints.Count;
if (GlobalPosition.DistanceTo(target) > TargetReachDistance * 1.5f) if (GlobalPosition.DistanceTo(target) > TargetReachDistance * 1.5f)
{ {
_currentTarget = target; BeginGridTarget(target);
_hasTarget = true;
_navigationAgent.TargetPosition = target;
_stuckTimer = 0.0f;
return; return;
} }
} }
_currentTarget = _patrolPoints[_patrolIndex]; BeginGridTarget(_patrolPoints[_patrolIndex]);
_hasTarget = true;
_navigationAgent.TargetPosition = _currentTarget;
_stuckTimer = 0.0f;
}
/// <summary>
/// 计算避让速度时的回调
/// </summary>
/// <param name="safeVelocity">安全速度</param>
private void OnVelocityComputed(Vector2 safeVelocity)
{
if (_phoneExitLocked)
{
Velocity = Vector2.Zero;
if (_usePhysicsMovement)
{
MoveAndSlide();
}
return;
}
// 使用安全速度移动,避免与其它角色硬碰硬卡住
Velocity = ClampVelocityToNavMesh(safeVelocity);
ApplyMovement(_lastDelta);
UpdateFacingAnimation(Velocity);
UpdateStuckTimer(_lastDelta);
} }
/// <summary> /// <summary>
@ -652,20 +534,11 @@ public partial class CampusStudent : CharacterBody2D
{ {
if (!_hasTarget) return; if (!_hasTarget) return;
if (UseGridPathfinding) _gridPathActive = false;
{ _gridPathPending = true;
_gridPathActive = false; _gridPathIndex = 0;
_gridPathPending = true; _gridPath.Clear();
_gridPathIndex = 0; TryBuildPendingGridPath();
_gridPath.Clear();
TryBuildPendingGridPath();
return;
}
if (_navigationAgent == null) return;
// 重新请求到同一目标点的路径,避免“固定时间换目的地”
_navigationAgent.TargetPosition = _currentTarget;
} }
/// <summary> /// <summary>
@ -673,9 +546,8 @@ public partial class CampusStudent : CharacterBody2D
/// </summary> /// </summary>
private void TryBuildPendingGridPath() private void TryBuildPendingGridPath()
{ {
if (!UseGridPathfinding || !_gridPathPending) return; if (!_gridPathPending) return;
if (_gridPathRetryTimer > 0.0f) return; if (_gridPathRetryTimer > 0.0f) return;
if (!IsNavigationMapReady()) return;
var path = BuildGridPath(GlobalPosition, _currentTarget); var path = BuildGridPath(GlobalPosition, _currentTarget);
if (path == null || path.Count == 0) if (path == null || path.Count == 0)
@ -695,59 +567,6 @@ public partial class CampusStudent : CharacterBody2D
} }
} }
/// <summary>
/// 导航地图是否准备就绪
/// </summary>
/// <returns>准备就绪返回true</returns>
private bool IsNavigationMapReady()
{
return _navigationMap.IsValid && NavigationServer2D.MapGetIterationId(_navigationMap) > 0;
}
/// <summary>
/// 将速度限制在导航网格内
/// </summary>
/// <param name="velocity">目标速度</param>
/// <returns>限制后的速度</returns>
private Vector2 ClampVelocityToNavMesh(Vector2 velocity)
{
if (!IsNavigationMapReady()) return velocity;
if (_lastDelta <= 0.0) return velocity;
// 预测下一帧位置,若偏离导航网格过远则夹回网格内
var candidate = GlobalPosition + velocity * (float)_lastDelta;
var closest = NavigationServer2D.MapGetClosestPoint(_navigationMap, candidate);
if (closest.DistanceTo(candidate) > NavMeshClampDistance)
{
var corrected = closest - GlobalPosition;
if (corrected.LengthSquared() < 0.01f)
{
return Vector2.Zero;
}
return corrected / (float)_lastDelta;
}
return velocity;
}
/// <summary>
/// 将目标点限制在导航网格内
/// </summary>
/// <param name="target">目标点</param>
/// <returns>限制后的目标点</returns>
private Vector2 ClampTargetToNavMesh(Vector2 target)
{
if (!IsNavigationMapReady()) return target;
var closest = NavigationServer2D.MapGetClosestPoint(_navigationMap, target);
if (closest.DistanceTo(target) <= 0.01f)
{
return target;
}
return closest;
}
/// <summary> /// <summary>
/// 应用移动 /// 应用移动
/// </summary> /// </summary>
@ -875,7 +694,7 @@ public partial class CampusStudent : CharacterBody2D
if (_gridPathIndex >= _gridPath.Count) if (_gridPathIndex >= _gridPath.Count)
{ {
Velocity = Vector2.Zero; Velocity = Vector2.Zero;
if (!EnableAvoidance && _usePhysicsMovement) if (_usePhysicsMovement)
{ {
MoveAndSlide(); MoveAndSlide();
} }
@ -885,6 +704,7 @@ public partial class CampusStudent : CharacterBody2D
PlayIdleAnimation(); PlayIdleAnimation();
} }
UpdateStuckTimer(delta); UpdateStuckTimer(delta);
_gridPathActive = false;
return true; return true;
} }
@ -903,18 +723,6 @@ public partial class CampusStudent : CharacterBody2D
var axisVelocity = ToAxisVelocity(toNext); var axisVelocity = ToAxisVelocity(toNext);
var step = axisVelocity * MoveSpeed * (float)delta; var step = axisVelocity * MoveSpeed * (float)delta;
var candidate = GlobalPosition + step;
if (!IsWorldWalkable(candidate))
{
Velocity = Vector2.Zero;
if (!EnableAvoidance && _usePhysicsMovement)
{
MoveAndSlide();
}
RepathToCurrentTarget();
UpdateStuckTimer(delta);
return true;
}
Velocity = step / (float)delta; Velocity = step / (float)delta;
ApplyMovement(delta); ApplyMovement(delta);
@ -1024,24 +832,4 @@ public partial class CampusStudent : CharacterBody2D
return simplified; return simplified;
} }
/// <summary>
/// 世界坐标是否可行走
/// </summary>
/// <param name="world">世界坐标</param>
/// <returns>如果可行走返回true</returns>
private bool IsWorldWalkable(Vector2 world)
{
if (!IsNavigationMapReady()) return false;
var closest = NavigationServer2D.MapGetClosestPoint(_navigationMap, world);
return closest.DistanceTo(world) <= GetGridTolerance();
}
/// <summary>
/// 获取网格容差
/// </summary>
/// <returns>网格容差</returns>
private float GetGridTolerance()
{
return Mathf.Max(1.0f, GridWalkableTolerance);
}
} }