using System; using System.Collections.Generic; using System.Diagnostics; using Godot; using Views; /// /// 校园学生角色控制器 /// public partial class CampusStudent : CharacterBody2D { /// /// 网格路径列表 /// private readonly List _gridPath = new(); /// /// 外观主题 /// private readonly SuitTheme _theme = new(); /// /// 获取外观主题ID /// /// 主题ID public ulong GetSuitThemeId() { return _theme.GetThemeId(); } /// /// 动画播放器 /// private AnimationPlayer _animationPlayer; /// /// 是否启用行为控制 /// private bool _behaviorControlEnabled; /// /// 是否有行为目标 /// private bool _behaviorHasTarget; /// /// 行为目标点 /// private Vector2 _behaviorTarget = Vector2.Zero; /// /// 校园控制器 /// private CampusController _campusController; /// /// 当前目标点 /// private Vector2 _currentTarget = Vector2.Zero; /// /// 网格路径是否激活 /// private bool _gridPathActive; /// /// 当前网格路径索引 /// private int _gridPathIndex; /// /// 网格路径是否挂起 /// private bool _gridPathPending; /// /// 网格重新寻路重试计时器 /// private float _gridPathRetryTimer; /// /// 是否有目标 /// private bool _hasTarget; /// /// 上一帧的时间间隔 /// private double _lastDelta; /// /// 上一次的面朝方向 /// private FacingDirection _lastFacing = FacingDirection.Down; /// /// 上一次的位置 /// private Vector2 _lastPosition; /// /// 是否已配置巡逻 /// private bool _patrolConfigured; /// /// 当前巡逻点索引 /// private int _patrolIndex; /// /// 巡逻点列表 /// private List _patrolPoints = new(); /// /// 手机退出动作锁定 /// private bool _phoneExitLocked; /// /// 手机闲置动画是否激活 /// private bool _phoneIdleActive; /// /// 卡住计时器 /// private float _stuckTimer; /// /// 是否使用物理移动 /// private bool _usePhysicsMovement = true; /// /// 移动速度 /// [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; _theme.IsPortrait = false; _theme.Use16 = Use16X16Sprites; 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(); } /// /// 进入建筑时同步头像 /// /// 地点ID public void EnterBuilding(CampusLocationId locationId) { _campusController ??= GetTree()?.CurrentScene as CampusController; _campusController?.HandleStudentEnterBuilding(this, locationId); } /// /// 离开建筑时移除头像 /// /// 地点ID public void ExitBuilding(CampusLocationId locationId) { _campusController ??= GetTree()?.CurrentScene as CampusController; _campusController?.HandleStudentExitBuilding(this, locationId); } /// /// 设置网格寻路目标 /// /// 目标位置 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 (_theme.IsEmpty()) CacheSprites(); Debug.Assert(_theme != null, nameof(_theme) + " != null"); for (Res.Type typeId = 0; typeId < Res.Type.ResTypeMax; typeId++) _theme.ApplyComponent(typeId, Res.GetResourcePathWithId(0, typeId, Use16X16Sprites, false, true)); } /// /// 缓存精灵节点 /// private void CacheSprites() { // 缓存子节点引用,避免每帧查找 _theme.CacheComponent(Res.Type.Accessory, GetNode("parts/accessory")); _theme.CacheComponent(Res.Type.Body, GetNode("parts/body")); _theme.CacheComponent(Res.Type.Eye, GetNode("parts/eye")); _theme.CacheComponent(Res.Type.Hair, GetNode("parts/hairstyle")); _theme.CacheComponent(Res.Type.Outfit, GetNode("parts/outfit")); _theme.CacheComponent(Res.Type.Phone, 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; } /// /// 面朝方向 /// private enum FacingDirection { Up, Down, Left, Right } }