diff --git a/scenes/CampusController.cs b/scenes/CampusController.cs index 9e7b5f1..1792a28 100644 --- a/scenes/CampusController.cs +++ b/scenes/CampusController.cs @@ -54,6 +54,7 @@ public partial class CampusController : Node2D private Rect2I _astarRegion; private int _astarMapIteration; private Rid _astarMap; + private readonly List _astarWalkableCells = new(); private const int GridSearchRadius = 6; // Called when the node enters the scene tree for the first time. @@ -365,18 +366,25 @@ public partial class CampusController : Node2D } } - SpawnStudents(map); - _spawnPending = false; + if (!EnsureAStarGrid() || _astarWalkableCells.Count == 0) + { + return; + } + + if (SpawnStudents()) + { + _spawnPending = false; + } } - private void SpawnStudents(Rid map) + private bool SpawnStudents() { _coveragePoints.Clear(); _coveragePoints.AddRange(BuildCoveragePoints()); if (_coveragePoints.Count == 0) { GD.PushWarning("未采样到可行走区域,跳过学生生成。"); - return; + return false; } for (int i = 0; i < StudentCount; i++) @@ -390,15 +398,15 @@ public partial class CampusController : Node2D } _studentsRoot.AddChild(student); - student.SetNavigationMap(map); student.MoveSpeed = AgentMoveSpeed; - student.GridWalkableTolerance = GridWalkableTolerance; - // 随机放置在可行走区域,并交给行为系统控制 - var randomIndex = _random != null - ? _random.Next(0, _coveragePoints.Count) - : (int)GD.RandRange(0, _coveragePoints.Count - 1); - student.GlobalPosition = _coveragePoints[randomIndex]; + // 随机放置在可行走区域,并交给行为系统控制 + if (!TryGetRandomWalkableGridPoint(out var gridPoint)) + { + GD.PushWarning("AStarGrid2D 未准备好,无法放置学生。"); + return false; + } + student.GlobalPosition = gridPoint; student.ApplyRandomTheme(); var runtime = BuildRandomAgentRuntime(i); @@ -417,6 +425,8 @@ public partial class CampusController : Node2D _behaviorAgents.Add(agent); LogSpawn(runtime); } + + return true; } private static readonly CampusTaskType[] TaskTypePool = @@ -703,6 +713,21 @@ public partial class CampusController : Node2D 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() { if (!IsNavigationMapReady(out var map)) @@ -737,6 +762,7 @@ public partial class CampusController : Node2D _astarGrid.DiagonalMode = AStarGrid2D.DiagonalModeEnum.Never; _astarGrid.Update(); + _astarWalkableCells.Clear(); 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++) @@ -745,6 +771,10 @@ public partial class CampusController : Node2D var center = GridToWorld(cell); var walkable = IsCellWalkable(center, map); _astarGrid.SetPointSolid(cell, !walkable); + if (walkable) + { + _astarWalkableCells.Add(cell); + } } } @@ -973,6 +1003,7 @@ public partial class CampusController : Node2D _astarGrid = null; _astarMap = new Rid(); _astarMapIteration = 0; + _astarWalkableCells.Clear(); _navBakePending = false; _navBakeReady = true; RequestDebugGridRedraw(); diff --git a/scripts/CampusStudent.cs b/scripts/CampusStudent.cs index 0509220..ebf2150 100644 --- a/scripts/CampusStudent.cs +++ b/scripts/CampusStudent.cs @@ -54,10 +54,6 @@ public partial class CampusStudent : CharacterBody2D /// private Vector2 _lastPosition; - /// - /// 导航代理 - /// - private NavigationAgent2D _navigationAgent; /// /// 服装精灵 /// @@ -83,10 +79,6 @@ public partial class CampusStudent : CharacterBody2D /// private float _stuckTimer; /// - /// 导航地图RID - /// - private Rid _navigationMap; - /// /// 当前目标点 /// private Vector2 _currentTarget = Vector2.Zero; @@ -156,10 +148,6 @@ public partial class CampusStudent : CharacterBody2D /// [Export] public bool Use16X16Sprites { get; set; } = true; /// - /// 是否启用避让 - /// - [Export] public bool EnableAvoidance { get; set; } - /// /// 卡住重新寻路时间 /// [Export] public float StuckRepathSeconds { get; set; } = 0.6f; @@ -168,18 +156,6 @@ public partial class CampusStudent : CharacterBody2D /// [Export] public float StuckDistanceEpsilon { get; set; } = 2.0f; /// - /// 导航网格吸附距离 - /// - [Export] public float NavMeshClampDistance { get; set; } = 6.0f; - /// - /// 是否使用网格寻路 - /// - [Export] public bool UseGridPathfinding { get; set; } = true; - /// - /// 网格可行走容差 - /// - [Export] public float GridWalkableTolerance { get; set; } = 2.0f; - /// /// 网格重新寻路间隔 /// [Export] public float GridRepathInterval { get; set; } = 0.25f; @@ -201,7 +177,6 @@ public partial class CampusStudent : CharacterBody2D /// public override void _Ready() { - _navigationAgent = GetNodeOrNull("NavigationAgent2D"); _animationPlayer = GetNodeOrNull("AnimationPlayer"); _campusController = GetTree()?.CurrentScene as CampusController; CacheSprites(); @@ -212,22 +187,7 @@ public partial class CampusStudent : CharacterBody2D _animationPlayer.AnimationFinished += OnAnimationFinished; } - if (_navigationAgent != null) - { - // 强制关闭避让,学生之间直接穿过 - EnableAvoidance = false; - - // 让寻路点更“贴近目标”,避免走到边缘时抖动 - _navigationAgent.PathDesiredDistance = TargetReachDistance; - _navigationAgent.TargetDesiredDistance = TargetReachDistance; - _navigationAgent.AvoidanceEnabled = EnableAvoidance; - - if (EnableAvoidance) - // 开启避让时使用安全速度回调进行移动 - _navigationAgent.VelocityComputed += OnVelocityComputed; - - if (_patrolConfigured) AdvanceTarget(); - } + if (_patrolConfigured) AdvanceTarget(); PlayIdleAnimation(); _lastPosition = GlobalPosition; @@ -251,40 +211,27 @@ public partial class CampusStudent : CharacterBody2D if (_phoneExitLocked) { Velocity = Vector2.Zero; - if (!EnableAvoidance && _usePhysicsMovement) + if (_usePhysicsMovement) { MoveAndSlide(); } return; } + var hasPatrolTarget = !_behaviorControlEnabled && _patrolPoints.Count > 0; if (_behaviorControlEnabled && _behaviorHasTarget) { 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) { @@ -293,35 +240,23 @@ public partial class CampusStudent : CharacterBody2D return; } - // 到达目标点或无路可走时,切换到下一个巡游点 - if (_navigationAgent.IsNavigationFinished()) + if (_gridPathActive) { - if (_behaviorControlEnabled) + if (ProcessGridPathMovement(delta)) { - Velocity = Vector2.Zero; - if (!EnableAvoidance && _usePhysicsMovement) + if (hasPatrolTarget && !_gridPathActive) { - MoveAndSlide(); + AdvanceTarget(); } - - if (!_phoneIdleActive && !_phoneExitLocked) - { - PlayIdleAnimation(); - } - UpdateStuckTimer(delta); return; } - - AdvanceTarget(); } - var nextPosition = _navigationAgent.GetNextPathPosition(); - var toNext = nextPosition - GlobalPosition; - if (toNext.LengthSquared() < 0.01f) + if (_gridPathPending) { - if (!EnableAvoidance && _usePhysicsMovement) + Velocity = Vector2.Zero; + if (_usePhysicsMovement) { - Velocity = Vector2.Zero; MoveAndSlide(); } @@ -333,21 +268,9 @@ public partial class CampusStudent : CharacterBody2D return; } - var direction = toNext.Normalized(); - var desiredVelocity = direction * MoveSpeed; - - if (EnableAvoidance) + if (!_phoneIdleActive && !_phoneExitLocked) { - // 交给导航系统做群体避让 - _navigationAgent.Velocity = desiredVelocity; - } - else - { - // 未启用避让时直接移动 - Velocity = ClampVelocityToNavMesh(desiredVelocity); - ApplyMovement(delta); - UpdateFacingAnimation(Velocity); - UpdateStuckTimer(delta); + PlayIdleAnimation(); } } @@ -386,7 +309,7 @@ public partial class CampusStudent : CharacterBody2D _patrolConfigured = true; ApplyRandomTheme(); - if (_navigationAgent != null) AdvanceTarget(); + AdvanceTarget(); } /// @@ -407,28 +330,9 @@ public partial class CampusStudent : CharacterBody2D public void SetBehaviorTarget(Vector2 target) { _behaviorControlEnabled = true; - target = ClampTargetToNavMesh(target); _behaviorTarget = target; _behaviorHasTarget = true; - _currentTarget = 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; - } + BeginGridTarget(target); } /// @@ -447,6 +351,23 @@ public partial class CampusStudent : CharacterBody2D _gridPath.Clear(); } + /// + /// 设置网格寻路目标 + /// + /// 目标位置 + private void BeginGridTarget(Vector2 target) + { + _currentTarget = target; + _hasTarget = true; + _stuckTimer = 0.0f; + _gridPathActive = false; + _gridPathPending = true; + _gridPathIndex = 0; + _gridPathRetryTimer = 0.0f; + _gridPath.Clear(); + TryBuildPendingGridPath(); + } + /// /// 是否已到达行为目标 /// @@ -462,8 +383,7 @@ public partial class CampusStudent : CharacterBody2D { return false; } - if (_navigationAgent == null) return true; - return _navigationAgent.IsNavigationFinished(); + return true; } /// @@ -515,16 +435,6 @@ public partial class CampusStudent : CharacterBody2D } } - /// - /// 设置导航地图 - /// - /// 地图RID - public void SetNavigationMap(Rid map) - { - // 由校园控制器传入导航地图,供本地边界夹紧使用 - _navigationMap = map; - } - /// /// 应用随机主题 /// @@ -572,7 +482,7 @@ public partial class CampusStudent : CharacterBody2D /// private void AdvanceTarget() { - if (_patrolPoints.Count == 0 || _navigationAgent == null) return; + if (_patrolPoints.Count == 0) return; if (_behaviorControlEnabled) return; // 避免当前点过近导致原地抖动,最多尝试一轮 @@ -582,40 +492,12 @@ public partial class CampusStudent : CharacterBody2D _patrolIndex = (_patrolIndex + 1) % _patrolPoints.Count; if (GlobalPosition.DistanceTo(target) > TargetReachDistance * 1.5f) { - _currentTarget = target; - _hasTarget = true; - _navigationAgent.TargetPosition = target; - _stuckTimer = 0.0f; + BeginGridTarget(target); return; } } - _currentTarget = _patrolPoints[_patrolIndex]; - _hasTarget = true; - _navigationAgent.TargetPosition = _currentTarget; - _stuckTimer = 0.0f; - } - - /// - /// 计算避让速度时的回调 - /// - /// 安全速度 - private void OnVelocityComputed(Vector2 safeVelocity) - { - if (_phoneExitLocked) - { - Velocity = Vector2.Zero; - if (_usePhysicsMovement) - { - MoveAndSlide(); - } - return; - } - // 使用安全速度移动,避免与其它角色硬碰硬卡住 - Velocity = ClampVelocityToNavMesh(safeVelocity); - ApplyMovement(_lastDelta); - UpdateFacingAnimation(Velocity); - UpdateStuckTimer(_lastDelta); + BeginGridTarget(_patrolPoints[_patrolIndex]); } /// @@ -652,20 +534,11 @@ public partial class CampusStudent : CharacterBody2D { if (!_hasTarget) return; - if (UseGridPathfinding) - { - _gridPathActive = false; - _gridPathPending = true; - _gridPathIndex = 0; - _gridPath.Clear(); - TryBuildPendingGridPath(); - return; - } - - if (_navigationAgent == null) return; - - // 重新请求到同一目标点的路径,避免“固定时间换目的地” - _navigationAgent.TargetPosition = _currentTarget; + _gridPathActive = false; + _gridPathPending = true; + _gridPathIndex = 0; + _gridPath.Clear(); + TryBuildPendingGridPath(); } /// @@ -673,9 +546,8 @@ public partial class CampusStudent : CharacterBody2D /// private void TryBuildPendingGridPath() { - if (!UseGridPathfinding || !_gridPathPending) return; + if (!_gridPathPending) return; if (_gridPathRetryTimer > 0.0f) return; - if (!IsNavigationMapReady()) return; var path = BuildGridPath(GlobalPosition, _currentTarget); if (path == null || path.Count == 0) @@ -695,59 +567,6 @@ public partial class CampusStudent : CharacterBody2D } } - /// - /// 导航地图是否准备就绪 - /// - /// 准备就绪返回true - private bool IsNavigationMapReady() - { - return _navigationMap.IsValid && NavigationServer2D.MapGetIterationId(_navigationMap) > 0; - } - - /// - /// 将速度限制在导航网格内 - /// - /// 目标速度 - /// 限制后的速度 - 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; - } - - /// - /// 将目标点限制在导航网格内 - /// - /// 目标点 - /// 限制后的目标点 - 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; - } - /// /// 应用移动 /// @@ -875,7 +694,7 @@ public partial class CampusStudent : CharacterBody2D if (_gridPathIndex >= _gridPath.Count) { Velocity = Vector2.Zero; - if (!EnableAvoidance && _usePhysicsMovement) + if (_usePhysicsMovement) { MoveAndSlide(); } @@ -885,6 +704,7 @@ public partial class CampusStudent : CharacterBody2D PlayIdleAnimation(); } UpdateStuckTimer(delta); + _gridPathActive = false; return true; } @@ -903,18 +723,6 @@ public partial class CampusStudent : CharacterBody2D var axisVelocity = ToAxisVelocity(toNext); 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; ApplyMovement(delta); @@ -1024,24 +832,4 @@ public partial class CampusStudent : CharacterBody2D return simplified; } - /// - /// 世界坐标是否可行走 - /// - /// 世界坐标 - /// 如果可行走返回true - private bool IsWorldWalkable(Vector2 world) - { - if (!IsNavigationMapReady()) return false; - var closest = NavigationServer2D.MapGetClosestPoint(_navigationMap, world); - return closest.DistanceTo(world) <= GetGridTolerance(); - } - - /// - /// 获取网格容差 - /// - /// 网格容差 - private float GetGridTolerance() - { - return Mathf.Max(1.0f, GridWalkableTolerance); - } }