using System; using System.Collections.Generic; using System.Diagnostics; using Godot; /// /// 校园学生角色控制器 /// public partial class CampusStudent : CharacterBody2D { /// /// 饰品精灵 /// private Sprite2D _accessory; /// /// 动画播放器 /// private AnimationPlayer _animationPlayer; /// /// 身体精灵 /// private Sprite2D _body; /// /// 眼睛精灵 /// private Sprite2D _eye; /// /// 发型精灵 /// private Sprite2D _hairstyle; /// /// 上一帧的时间间隔 /// private double _lastDelta; /// /// 面朝方向 /// private enum FacingDirection { Up, Down, Left, Right } /// /// 上一次的面朝方向 /// private FacingDirection _lastFacing = FacingDirection.Down; /// /// 上一次的位置 /// private Vector2 _lastPosition; /// /// 导航代理 /// private NavigationAgent2D _navigationAgent; /// /// 服装精灵 /// private Sprite2D _outfit; /// /// 是否已配置巡逻 /// private bool _patrolConfigured; /// /// 当前巡逻点索引 /// private int _patrolIndex; /// /// 巡逻点列表 /// private List _patrolPoints = new(); /// /// 手机精灵 /// private Sprite2D _smartphone; /// /// 卡住计时器 /// private float _stuckTimer; /// /// 导航地图RID /// private Rid _navigationMap; /// /// 当前目标点 /// private Vector2 _currentTarget = Vector2.Zero; /// /// 是否有目标 /// private bool _hasTarget; /// /// 是否启用行为控制 /// private bool _behaviorControlEnabled; /// /// 行为目标点 /// private Vector2 _behaviorTarget = Vector2.Zero; /// /// 是否有行为目标 /// private bool _behaviorHasTarget; /// /// 手机闲置动画是否激活 /// private bool _phoneIdleActive; /// /// 手机退出动作锁定 /// private bool _phoneExitLocked; /// /// 是否使用物理移动 /// private bool _usePhysicsMovement = true; /// /// 网格路径列表 /// private readonly List _gridPath = new(); /// /// 当前网格路径索引 /// private int _gridPathIndex; /// /// 网格路径是否激活 /// private bool _gridPathActive; /// /// 网格路径是否挂起 /// private bool _gridPathPending; /// /// AStar网格 /// private AStarGrid2D _astarGrid; /// /// AStar区域 /// private Rect2I _astarRegion; /// /// AStar地图迭代版本 /// private int _astarMapIteration; /// /// 网格重新寻路重试计时器 /// private float _gridPathRetryTimer; /// /// 导航区域引用 /// private NavigationRegion2D _navigationRegion; /// /// 移动速度 /// [Export] public float MoveSpeed { get; set; } = 60.0f; /// /// 目标到达判定距离 /// [Export] public float TargetReachDistance { get; set; } = 6.0f; /// /// 是否使用16x16精灵 /// [Export] public bool Use16X16Sprites { get; set; } = true; /// /// 是否启用避让 /// [Export] public bool EnableAvoidance { get; set; } /// /// 卡住重新寻路时间 /// [Export] public float StuckRepathSeconds { get; set; } = 0.6f; /// /// 卡住距离阈值 /// [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 GridCellSize { get; set; } = 8.0f; /// /// 网格可行走容差 /// [Export] public float GridWalkableTolerance { get; set; } = 2.0f; /// /// 网格搜索节点限制 /// [Export] public int GridSearchNodeLimit { get; set; } = 8000; /// /// 网格重新寻路间隔 /// [Export] public float GridRepathInterval { get; set; } = 0.25f; /// /// 调试绘制网格 /// [Export] public bool DebugDrawGrid { get; set; } /// /// 调试仅绘制实心点 /// [Export] public bool DebugDrawSolidOnly { get; set; } = true; /// /// 调试绘制路径 /// [Export] public bool DebugDrawPath { get; set; } /// /// 调试绘制半径单元数 /// [Export] public int DebugDrawRadiusCells { get; set; } = 20; /// /// 调试日志网格 /// [Export] public bool DebugLogGrid { get; set; } /// /// 环境碰撞掩码 /// [Export] public uint EnvironmentCollisionMask { get; set; } = 1u; /// /// 学生碰撞层 /// [Export] public uint StudentCollisionLayer { get; set; } = 1u << 1; /// /// 准备就绪时调用 /// public override void _Ready() { _navigationAgent = GetNodeOrNull("NavigationAgent2D"); _animationPlayer = GetNodeOrNull("AnimationPlayer"); _navigationRegion = FindNavigationRegion(); CacheSprites(); ConfigureCollision(); if (_animationPlayer != null) { _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(); } PlayIdleAnimation(); _lastPosition = GlobalPosition; } /// /// 物理处理 /// /// 时间间隔 public override void _PhysicsProcess(double delta) { _lastDelta = delta; if (DebugDrawGrid || DebugDrawPath) { QueueRedraw(); } if ((DebugDrawGrid || DebugDrawPath) && _astarGrid == null) { EnsureAStarGrid(); } if (_gridPathRetryTimer > 0.0f) { _gridPathRetryTimer = Mathf.Max(0.0f, _gridPathRetryTimer - (float)delta); } if (_phoneExitLocked) { Velocity = Vector2.Zero; if (!EnableAvoidance && _usePhysicsMovement) { MoveAndSlide(); } return; } 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)) { if (!_phoneIdleActive && !_phoneExitLocked) { PlayIdleAnimation(); } return; } // 到达目标点或无路可走时,切换到下一个巡游点 if (_navigationAgent.IsNavigationFinished()) { if (_behaviorControlEnabled) { Velocity = Vector2.Zero; if (!EnableAvoidance && _usePhysicsMovement) { MoveAndSlide(); } if (!_phoneIdleActive && !_phoneExitLocked) { PlayIdleAnimation(); } UpdateStuckTimer(delta); return; } AdvanceTarget(); } var nextPosition = _navigationAgent.GetNextPathPosition(); var toNext = nextPosition - GlobalPosition; if (toNext.LengthSquared() < 0.01f) { if (!EnableAvoidance && _usePhysicsMovement) { Velocity = Vector2.Zero; MoveAndSlide(); } if (!_phoneIdleActive && !_phoneExitLocked) { PlayIdleAnimation(); } UpdateStuckTimer(delta); return; } var direction = toNext.Normalized(); var desiredVelocity = direction * MoveSpeed; if (EnableAvoidance) { // 交给导航系统做群体避让 _navigationAgent.Velocity = desiredVelocity; } else { // 未启用避让时直接移动 Velocity = ClampVelocityToNavMesh(desiredVelocity); ApplyMovement(delta); UpdateFacingAnimation(Velocity); UpdateStuckTimer(delta); } } /// /// 绘制调试信息 /// public override void _Draw() { if (!DebugDrawGrid && !DebugDrawPath) return; if (_astarGrid == null || GridCellSize <= 0.0f) return; var cellSize = Mathf.Max(1.0f, GridCellSize); var half = new Vector2(cellSize * 0.5f, cellSize * 0.5f); if (DebugDrawGrid) { var radius = Mathf.Max(1, DebugDrawRadiusCells); var originCell = WorldToGrid(GlobalPosition); var minX = Math.Max(_astarRegion.Position.X, originCell.X - radius); var maxX = Math.Min(_astarRegion.Position.X + _astarRegion.Size.X - 1, originCell.X + radius); var minY = Math.Max(_astarRegion.Position.Y, originCell.Y - radius); var maxY = Math.Min(_astarRegion.Position.Y + _astarRegion.Size.Y - 1, originCell.Y + radius); var solidFill = new Color(1.0f, 0.3f, 0.3f, 0.25f); var solidOutline = new Color(1.0f, 0.3f, 0.3f, 0.6f); var walkFill = new Color(0.2f, 0.8f, 0.3f, 0.08f); var walkOutline = new Color(0.2f, 0.8f, 0.3f, 0.2f); for (var x = minX; x <= maxX; x++) { for (var y = minY; y <= maxY; y++) { var cell = new Vector2I(x, y); var isSolid = _astarGrid.IsPointSolid(cell); if (DebugDrawSolidOnly && !isSolid) { continue; } var worldCenter = GridToWorld(cell); // Draw around the cell center to verify path alignment. var localTopLeft = ToLocal(worldCenter) - half; var rect = new Rect2(localTopLeft, new Vector2(cellSize, cellSize)); DrawRect(rect, isSolid ? solidFill : walkFill); DrawRect(rect, isSolid ? solidOutline : walkOutline, false, 1.0f); } } } if (DebugDrawPath && _gridPath.Count > 0 && _gridPathIndex < _gridPath.Count) { var pathColor = new Color(0.2f, 0.7f, 1.0f, 0.9f); var current = ToLocal(GlobalPosition); for (var i = _gridPathIndex; i < _gridPath.Count; i++) { var next = ToLocal(_gridPath[i]); DrawLine(current, next, pathColor, 2.0f); DrawCircle(next, 2.5f, pathColor); current = next; } } } /// /// 配置巡逻 /// /// 巡逻点列表 /// 起始索引 public void ConfigurePatrol(List points, int startIndex) { _patrolPoints = points ?? new List(); if (_patrolPoints.Count == 0) return; _patrolIndex = (startIndex % _patrolPoints.Count + _patrolPoints.Count) % _patrolPoints.Count; _patrolConfigured = true; ApplyRandomTheme(); if (_navigationAgent != null) AdvanceTarget(); } /// /// 启用行为控制 /// public void EnableBehaviorControl() { _behaviorControlEnabled = true; _behaviorHasTarget = false; _patrolConfigured = false; _patrolPoints.Clear(); } /// /// 设置行为目标 /// /// 目标位置 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; } } /// /// 清除行为目标 /// public void ClearBehaviorTarget() { _behaviorHasTarget = false; _hasTarget = false; _currentTarget = Vector2.Zero; _stuckTimer = 0.0f; _gridPathActive = false; _gridPathPending = false; _gridPathIndex = 0; _gridPathRetryTimer = 0.0f; _gridPath.Clear(); } /// /// 是否已到达行为目标 /// /// 到达返回true public bool HasReachedBehaviorTarget() { if (!_behaviorHasTarget) return true; if (_gridPathActive) { return _gridPathIndex >= _gridPath.Count; } if (_gridPathPending) { return false; } if (_navigationAgent == null) return true; return _navigationAgent.IsNavigationFinished(); } /// /// 开始玩手机 /// public void StartPhoneIdle() { if (_animationPlayer == null || !_animationPlayer.HasAnimation("phone_up")) return; if (_phoneIdleActive) return; _phoneExitLocked = false; _phoneIdleActive = true; _animationPlayer.Play("phone_up"); } /// /// 停止玩手机 /// /// 是否锁定移动 public void StopPhoneIdle(bool lockMovement = false) { if (_animationPlayer == null) { _phoneIdleActive = false; _phoneExitLocked = false; return; } if (_phoneExitLocked && !lockMovement) { return; } if (!_phoneIdleActive && !_phoneExitLocked) { return; } _phoneIdleActive = false; if (_animationPlayer.HasAnimation("phone_down")) { _phoneExitLocked = lockMovement; _animationPlayer.Play("phone_down"); } else { _phoneExitLocked = false; PlayIdleAnimation(); } } /// /// 设置导航地图 /// /// 地图RID public void SetNavigationMap(Rid map) { // 由校园控制器传入导航地图,供本地边界夹紧使用 _navigationMap = map; _astarGrid = null; _astarRegion = new Rect2I(); _astarMapIteration = 0; _navigationRegion = FindNavigationRegion(); } /// /// 应用随机主题 /// public void ApplyRandomTheme() { // 随机替换身体与配件贴图,形成不同主题外观 if (_body == null) CacheSprites(); Debug.Assert(_body != null, nameof(_body) + " != null"); _body.Texture = ResourceLoader.Load(Res.GetRandom(Res.Type.Body, Use16X16Sprites)); _hairstyle.Texture = ResourceLoader.Load(Res.GetRandom(Res.Type.Hair, Use16X16Sprites)); _outfit.Texture = ResourceLoader.Load(Res.GetRandom(Res.Type.Outfit, Use16X16Sprites)); _eye.Texture = ResourceLoader.Load(Res.GetRandom(Res.Type.Eye, Use16X16Sprites)); _accessory.Texture = ResourceLoader.Load(Res.GetRandom(Res.Type.Accessory, Use16X16Sprites)); _smartphone.Texture = ResourceLoader.Load(Res.GetRandom(Res.Type.Phone, Use16X16Sprites)); } /// /// 缓存精灵节点 /// private void CacheSprites() { // 缓存子节点引用,避免每帧查找 _body = GetNode("parts/body"); _hairstyle = GetNode("parts/hairstyle"); _outfit = GetNode("parts/outfit"); _eye = GetNode("parts/eye"); _accessory = GetNode("parts/accessory"); _smartphone = GetNode("parts/smartphone"); } /// /// 配置碰撞 /// private void ConfigureCollision() { // 学生只与环境碰撞,不与其它学生碰撞 CollisionLayer = StudentCollisionLayer; CollisionMask = EnvironmentCollisionMask; _usePhysicsMovement = true; } /// /// 推进到下一个目标 /// private void AdvanceTarget() { if (_patrolPoints.Count == 0 || _navigationAgent == null) return; if (_behaviorControlEnabled) return; // 避免当前点过近导致原地抖动,最多尝试一轮 for (var i = 0; i < _patrolPoints.Count; i++) { var target = _patrolPoints[_patrolIndex]; _patrolIndex = (_patrolIndex + 1) % _patrolPoints.Count; if (GlobalPosition.DistanceTo(target) > TargetReachDistance * 1.5f) { _currentTarget = target; _hasTarget = true; _navigationAgent.TargetPosition = target; _stuckTimer = 0.0f; 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); } /// /// 更新卡住计时器 /// /// 时间间隔 private void UpdateStuckTimer(double delta) { if (StuckRepathSeconds <= 0.0f || !_hasTarget) { _lastPosition = GlobalPosition; return; } // 若短时间内几乎没有位移,则重新请求路径来脱困 if (GlobalPosition.DistanceTo(_lastPosition) <= StuckDistanceEpsilon) _stuckTimer += (float)delta; else _stuckTimer = 0.0f; _lastPosition = GlobalPosition; if (_stuckTimer >= StuckRepathSeconds) { _stuckTimer = 0.0f; RepathToCurrentTarget(); } } /// /// 重新规划路径到当前目标 /// private void RepathToCurrentTarget() { if (!_hasTarget) return; if (UseGridPathfinding) { _gridPathActive = false; _gridPathPending = true; _gridPathIndex = 0; _gridPath.Clear(); TryBuildPendingGridPath(); return; } if (_navigationAgent == null) return; // 重新请求到同一目标点的路径,避免“固定时间换目的地” _navigationAgent.TargetPosition = _currentTarget; } /// /// 尝试构建挂起的网格路径 /// private void TryBuildPendingGridPath() { if (!UseGridPathfinding || !_gridPathPending) return; if (_gridPathRetryTimer > 0.0f) return; if (!IsNavigationMapReady()) return; var path = BuildGridPath(GlobalPosition, _currentTarget); if (path == null || path.Count == 0) { _gridPathRetryTimer = Mathf.Max(0.1f, GridRepathInterval); return; } _gridPath.Clear(); _gridPath.AddRange(path); _gridPathIndex = 0; _gridPathActive = true; _gridPathPending = false; if (DebugDrawGrid || DebugDrawPath) { QueueRedraw(); } } /// /// 导航地图是否准备就绪 /// /// 准备就绪返回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; } /// /// 应用移动 /// /// 时间间隔 private void ApplyMovement(double delta) { if (_usePhysicsMovement) { MoveAndSlide(); return; } // 关闭碰撞时直接平移,避免 CharacterBody2D 在无碰撞层时不移动 GlobalPosition += Velocity * (float)delta; } /// /// 更新面朝向动画 /// /// 当前速度 private void UpdateFacingAnimation(Vector2 velocity) { if (_phoneIdleActive || _phoneExitLocked) { return; } if (velocity.LengthSquared() < 0.01f) { PlayIdleAnimation(); return; } if (Mathf.Abs(velocity.X) >= Mathf.Abs(velocity.Y)) _lastFacing = velocity.X >= 0 ? FacingDirection.Right : FacingDirection.Left; else _lastFacing = velocity.Y >= 0 ? FacingDirection.Down : FacingDirection.Up; switch (_lastFacing) { case FacingDirection.Left: PlayAnimation("walk_left"); break; case FacingDirection.Right: PlayAnimation("walk_right"); break; case FacingDirection.Up: PlayAnimation("walk_up"); break; case FacingDirection.Down: PlayAnimation("walk_down"); break; } } /// /// 播放闲置动画 /// private void PlayIdleAnimation() { if (_phoneIdleActive || _phoneExitLocked) { return; } switch (_lastFacing) { case FacingDirection.Left: PlayAnimation("idle_left"); break; case FacingDirection.Right: PlayAnimation("idle_right"); break; case FacingDirection.Up: PlayAnimation("idle_back"); break; case FacingDirection.Down: PlayAnimation("idle_front"); break; } } /// /// 播放指定动画 /// /// 动画名称 private void PlayAnimation(string animationName) { if (_animationPlayer == null) return; if (_animationPlayer.CurrentAnimation != animationName) _animationPlayer.Play(animationName); } /// /// 动画结束回调 /// /// 动画名称 private void OnAnimationFinished(StringName animationName) { if (_animationPlayer == null) return; if (animationName == "phone_up" && _phoneIdleActive) { if (_animationPlayer.HasAnimation("phone_loop")) { _animationPlayer.Play("phone_loop"); } return; } if (animationName == "phone_down" && !_phoneIdleActive) { _phoneExitLocked = false; PlayIdleAnimation(); } } /// /// 处理网格路径移动 /// /// 时间间隔 /// 如果正在移动返回true private bool ProcessGridPathMovement(double delta) { if (_gridPathIndex >= _gridPath.Count) { Velocity = Vector2.Zero; if (!EnableAvoidance && _usePhysicsMovement) { MoveAndSlide(); } if (!_phoneIdleActive && !_phoneExitLocked) { PlayIdleAnimation(); } UpdateStuckTimer(delta); return true; } var target = _gridPath[_gridPathIndex]; var toNext = target - GlobalPosition; if (toNext.LengthSquared() <= TargetReachDistance * TargetReachDistance) { _gridPathIndex += 1; return true; } if (delta <= 0.0) { return true; } 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); UpdateFacingAnimation(Velocity); UpdateStuckTimer(delta); return true; } /// /// 转换为轴向速度 /// /// 位移 /// 轴向速度 private Vector2 ToAxisVelocity(Vector2 delta) { if (Mathf.Abs(delta.X) >= Mathf.Abs(delta.Y)) { return new Vector2(Mathf.Sign(delta.X), 0); } return new Vector2(0, Mathf.Sign(delta.Y)); } /// /// 构建网格路径 /// /// 起点 /// 终点 /// 路径点列表 private List BuildGridPath(Vector2 start, Vector2 target) { if (!IsNavigationMapReady() || GridCellSize <= 0.0f) { return null; } EnsureAStarGrid(); if (_astarGrid == null) { return null; } var startCell = WorldToGrid(start); var targetCell = WorldToGrid(target); if (!IsCellInBounds(startCell) || !IsCellInBounds(targetCell)) { if (DebugLogGrid) { GD.Print($"[AStarGrid] out_of_bounds start={startCell} target={targetCell} region={_astarRegion}"); } return null; } startCell = FindNearestOpenCell(startCell, 6); targetCell = FindNearestOpenCell(targetCell, 6); if (!IsCellInBounds(startCell) || !IsCellInBounds(targetCell)) { if (DebugLogGrid) { GD.Print($"[AStarGrid] no_open_cell start={startCell} target={targetCell}"); } return null; } if (_astarGrid.IsPointSolid(startCell) || _astarGrid.IsPointSolid(targetCell)) { if (DebugLogGrid) { GD.Print($"[AStarGrid] solid start={startCell} target={targetCell}"); } return null; } var path = _astarGrid.GetIdPath(startCell, targetCell); if (path.Count == 0) { if (DebugLogGrid) { GD.Print($"[AStarGrid] empty_path start={startCell} target={targetCell}"); } return null; } var result = new List(path.Count); foreach (Vector2I cell in path) { result.Add(GridToWorld(cell)); } if (result.Count > 0) { result.RemoveAt(0); } RemoveImmediateBacktracks(result); return SimplifyGridPath(result); } /// /// 移除直接回头的路径点 /// /// 路径 private void RemoveImmediateBacktracks(List path) { if (path == null || path.Count < 3) return; var index = 2; while (index < path.Count) { if (path[index].DistanceTo(path[index - 2]) <= 0.01f) { path.RemoveAt(index - 1); index = Math.Max(2, index - 1); continue; } index += 1; } } /// /// 简化网格路径(移除共线点) /// /// 路径 /// 简化后的路径 private List SimplifyGridPath(List path) { if (path == null || path.Count < 3) return path; var simplified = new List { path[0] }; var lastDir = Vector2.Zero; for (var i = 1; i < path.Count; i++) { var delta = path[i] - path[i - 1]; if (delta.LengthSquared() < 0.01f) { continue; } var axisDir = ToAxisVelocity(delta); if (simplified.Count == 1) { simplified.Add(path[i]); lastDir = axisDir; continue; } if (axisDir == lastDir) { simplified[simplified.Count - 1] = path[i]; } else { simplified.Add(path[i]); lastDir = axisDir; } } return simplified; } /// /// 确保AStar网格已建立 /// private void EnsureAStarGrid() { if (!IsNavigationMapReady()) return; var region = BuildGridRegion(); if (region.Size.X <= 0 || region.Size.Y <= 0) { return; } var cellSize = new Vector2(Mathf.Max(1.0f, GridCellSize), Mathf.Max(1.0f, GridCellSize)); var mapIteration = (int)NavigationServer2D.MapGetIterationId(_navigationMap); var needsRebuild = _astarGrid == null || _astarMapIteration != mapIteration || _astarRegion.Position != region.Position || _astarRegion.Size != region.Size || _astarGrid.CellSize != cellSize; if (!needsRebuild) { return; } _astarGrid ??= new AStarGrid2D(); _astarGrid.Region = region; _astarGrid.CellSize = cellSize; _astarGrid.Offset = Vector2.Zero; _astarGrid.DiagonalMode = AStarGrid2D.DiagonalModeEnum.Never; _astarGrid.Update(); 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++) { var cell = new Vector2I(x, y); var center = GridToWorld(cell); var walkable = IsCellWalkable(center); _astarGrid.SetPointSolid(cell, !walkable); } } _astarRegion = region; _astarMapIteration = mapIteration; if (DebugLogGrid) { GD.Print($"[AStarGrid] region={region} cell={cellSize}"); } if (DebugDrawGrid || DebugDrawPath) { QueueRedraw(); } } /// /// 构建网格区域 /// /// 网格区域 private Rect2I BuildGridRegion() { var bounds = BuildWorldBounds(); var cell = Mathf.Max(1.0f, GridCellSize); var minX = Mathf.FloorToInt(bounds.Position.X / cell); var minY = Mathf.FloorToInt(bounds.Position.Y / cell); var maxX = Mathf.CeilToInt((bounds.Position.X + bounds.Size.X) / cell); var maxY = Mathf.CeilToInt((bounds.Position.Y + bounds.Size.Y) / cell); var sizeX = Math.Max(1, maxX - minX); var sizeY = Math.Max(1, maxY - minY); return new Rect2I(new Vector2I(minX, minY), new Vector2I(sizeX, sizeY)); } /// /// 构建世界边界 /// /// 世界边界 private Rect2 BuildWorldBounds() { var viewport = GetViewportRect(); var bounds = new Rect2(viewport.Position, viewport.Size); var navRegion = _navigationRegion ?? FindNavigationRegion(); if (navRegion == null || navRegion.NavigationPolygon == null) { return bounds; } var polygon = navRegion.NavigationPolygon; var outlineCount = polygon.GetOutlineCount(); if (outlineCount == 0) { return bounds; } var min = Vector2.Zero; var max = Vector2.Zero; var hasPoint = false; for (var i = 0; i < outlineCount; i++) { var outline = polygon.GetOutline(i); for (var j = 0; j < outline.Length; j++) { var world = navRegion.ToGlobal(outline[j]); if (!hasPoint) { min = world; max = world; hasPoint = true; continue; } min = new Vector2(Mathf.Min(min.X, world.X), Mathf.Min(min.Y, world.Y)); max = new Vector2(Mathf.Max(max.X, world.X), Mathf.Max(max.Y, world.Y)); } } if (!hasPoint) { return bounds; } var navBounds = new Rect2(min, max - min); return bounds.Merge(navBounds); } /// /// 单元格是否在区域内 /// /// 单元格 /// 如果在区域内返回true private bool IsCellInRegion(Vector2I cell) { return cell.X >= _astarRegion.Position.X && cell.X < _astarRegion.Position.X + _astarRegion.Size.X && cell.Y >= _astarRegion.Position.Y && cell.Y < _astarRegion.Position.Y + _astarRegion.Size.Y; } /// /// 单元格是否在边界内 /// /// 单元格 /// 如果在边界内返回true private bool IsCellInBounds(Vector2I cell) { if (_astarGrid != null) { return _astarGrid.IsInBounds(cell.X, cell.Y); } return IsCellInRegion(cell); } /// /// 世界坐标转网格坐标 /// /// 世界坐标 /// 网格坐标 private Vector2I WorldToGrid(Vector2 world) { var size = Mathf.Max(1.0f, GridCellSize); return new Vector2I( Mathf.FloorToInt(world.X / size), Mathf.FloorToInt(world.Y / size)); } /// /// 网格坐标转世界坐标 /// /// 网格坐标 /// 世界坐标 private Vector2 GridToWorld(Vector2I grid) { var size = Mathf.Max(1.0f, GridCellSize); return new Vector2((grid.X + 0.5f) * size, (grid.Y + 0.5f) * size); } /// /// 查找最近的开放单元格 /// /// 起点 /// 搜索半径 /// 最近的开放单元格 private Vector2I FindNearestOpenCell(Vector2I origin, int radius) { if (_astarGrid == null) return origin; if (IsCellInBounds(origin) && !_astarGrid.IsPointSolid(origin)) return origin; for (var r = 1; r <= radius; r++) { for (var dx = -r; dx <= r; dx++) { var dy = r - Math.Abs(dx); var candidateA = new Vector2I(origin.X + dx, origin.Y + dy); if (IsCellInBounds(candidateA) && !_astarGrid.IsPointSolid(candidateA)) { return candidateA; } if (dy == 0) continue; var candidateB = new Vector2I(origin.X + dx, origin.Y - dy); if (IsCellInBounds(candidateB) && !_astarGrid.IsPointSolid(candidateB)) { return candidateB; } } } return origin; } /// /// 世界坐标是否可行走 /// /// 世界坐标 /// 如果可行走返回true private bool IsWorldWalkable(Vector2 world) { if (!IsNavigationMapReady()) return false; var closest = NavigationServer2D.MapGetClosestPoint(_navigationMap, world); return closest.DistanceTo(world) <= GetGridTolerance(); } /// /// 查找导航区域节点 /// /// 导航区域 private NavigationRegion2D FindNavigationRegion() { return GetTree()?.CurrentScene?.FindChild("NavigationRegion2D", true, false) as NavigationRegion2D; } /// /// 单元格中心是否可行走 /// /// 中心点 /// 如果可行走返回true private bool IsCellWalkable(Vector2 center) { if (!IsWorldWalkable(center)) return false; var half = Mathf.Max(1.0f, GridCellSize * 0.45f); var offsets = new[] { new Vector2(half, 0), new Vector2(-half, 0), new Vector2(0, half), new Vector2(0, -half), new Vector2(half, half), new Vector2(half, -half), new Vector2(-half, half), new Vector2(-half, -half) }; for (var i = 0; i < offsets.Length; i++) { if (!IsWorldWalkable(center + offsets[i])) { return false; } } return true; } /// /// 获取网格容差 /// /// 网格容差 private float GetGridTolerance() { return Mathf.Max(1.0f, GridWalkableTolerance); } }