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 Sprite2D _outfit; /// /// 是否已配置巡逻 /// private bool _patrolConfigured; /// /// 当前巡逻点索引 /// private int _patrolIndex; /// /// 巡逻点列表 /// private List _patrolPoints = new(); /// /// 手机精灵 /// private Sprite2D _smartphone; /// /// 卡住计时器 /// private float _stuckTimer; /// /// 当前目标点 /// 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; /// /// 网格重新寻路重试计时器 /// private float _gridPathRetryTimer; /// /// 校园控制器 /// private CampusController _campusController; /// /// 移动速度 /// [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 float StuckRepathSeconds { get; set; } = 0.6f; /// /// 卡住距离阈值 /// [Export] public float StuckDistanceEpsilon { get; set; } = 2.0f; /// /// 网格重新寻路间隔 /// [Export] public float GridRepathInterval { get; set; } = 0.25f; /// /// 调试绘制路径 /// [Export] public bool DebugDrawPath { get; set; } /// /// 环境碰撞掩码 /// [Export] public uint EnvironmentCollisionMask { get; set; } = 1u; /// /// 学生碰撞层 /// [Export] public uint StudentCollisionLayer { get; set; } = 1u << 1; /// /// 准备就绪时调用 /// public override void _Ready() { _animationPlayer = GetNodeOrNull("AnimationPlayer"); _campusController = GetTree()?.CurrentScene as CampusController; CacheSprites(); ConfigureCollision(); if (_animationPlayer != null) { _animationPlayer.AnimationFinished += OnAnimationFinished; } if (_patrolConfigured) AdvanceTarget(); PlayIdleAnimation(); _lastPosition = GlobalPosition; } /// /// 物理处理 /// /// 时间间隔 public override void _PhysicsProcess(double delta) { _lastDelta = delta; if (DebugDrawPath) { QueueRedraw(); } if (_gridPathRetryTimer > 0.0f) { _gridPathRetryTimer = Mathf.Max(0.0f, _gridPathRetryTimer - (float)delta); } if (_phoneExitLocked) { Velocity = Vector2.Zero; if (_usePhysicsMovement) { MoveAndSlide(); } return; } var hasPatrolTarget = !_behaviorControlEnabled && _patrolPoints.Count > 0; if (_behaviorControlEnabled && _behaviorHasTarget) { TryBuildPendingGridPath(); } else if (hasPatrolTarget) { if (!_hasTarget || (!_gridPathPending && !_gridPathActive)) { AdvanceTarget(); } TryBuildPendingGridPath(); } else { if (!_phoneIdleActive && !_phoneExitLocked) { PlayIdleAnimation(); } return; } if (_gridPathActive) { if (ProcessGridPathMovement(delta)) { if (hasPatrolTarget && !_gridPathActive) { AdvanceTarget(); } return; } } if (_gridPathPending) { Velocity = Vector2.Zero; if (_usePhysicsMovement) { MoveAndSlide(); } if (!_phoneIdleActive && !_phoneExitLocked) { PlayIdleAnimation(); } UpdateStuckTimer(delta); return; } if (!_phoneIdleActive && !_phoneExitLocked) { PlayIdleAnimation(); } } /// /// 绘制调试信息 /// public override void _Draw() { if (!DebugDrawPath) return; if (_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(); AdvanceTarget(); } /// /// 启用行为控制 /// public void EnableBehaviorControl() { _behaviorControlEnabled = true; _behaviorHasTarget = false; _patrolConfigured = false; _patrolPoints.Clear(); } /// /// 设置行为目标 /// /// 目标位置 public void SetBehaviorTarget(Vector2 target) { _behaviorControlEnabled = true; _behaviorTarget = target; _behaviorHasTarget = true; BeginGridTarget(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(); } /// /// 设置网格寻路目标 /// /// 目标位置 private void BeginGridTarget(Vector2 target) { _currentTarget = target; _hasTarget = true; _stuckTimer = 0.0f; _gridPathActive = false; _gridPathPending = true; _gridPathIndex = 0; _gridPathRetryTimer = 0.0f; _gridPath.Clear(); TryBuildPendingGridPath(); } /// /// 是否已到达行为目标 /// /// 到达返回true public bool HasReachedBehaviorTarget() { if (!_behaviorHasTarget) return true; if (_gridPathActive) { return _gridPathIndex >= _gridPath.Count; } if (_gridPathPending) { return false; } return true; } /// /// 开始玩手机 /// 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(); } } /// /// 应用随机主题 /// 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) 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) { BeginGridTarget(target); return; } } BeginGridTarget(_patrolPoints[_patrolIndex]); } /// /// 更新卡住计时器 /// /// 时间间隔 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; _gridPathActive = false; _gridPathPending = true; _gridPathIndex = 0; _gridPath.Clear(); TryBuildPendingGridPath(); } /// /// 尝试构建挂起的网格路径 /// private void TryBuildPendingGridPath() { if (!_gridPathPending) return; if (_gridPathRetryTimer > 0.0f) 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 (DebugDrawPath) { QueueRedraw(); } } /// /// 应用移动 /// /// 时间间隔 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 (_usePhysicsMovement) { MoveAndSlide(); } if (!_phoneIdleActive && !_phoneExitLocked) { PlayIdleAnimation(); } UpdateStuckTimer(delta); _gridPathActive = false; 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; 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 (_campusController == null) { _campusController = GetTree()?.CurrentScene as CampusController; } var path = _campusController?.RequestGridPath(start, target); if (path == null || path.Count == 0) { return null; } RemoveImmediateBacktracks(path); return SimplifyGridPath(path); } /// /// 移除直接回头的路径点 /// /// 路径 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; } }