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;
///
/// 网格重新寻路重试计时器
///
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 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 GridWalkableTolerance { 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()
{
_navigationAgent = GetNodeOrNull("NavigationAgent2D");
_animationPlayer = GetNodeOrNull("AnimationPlayer");
_campusController = GetTree()?.CurrentScene as CampusController;
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 (DebugDrawPath)
{
QueueRedraw();
}
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 (!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();
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;
}
///
/// 应用随机主题
///
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 (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 (_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;
}
///
/// 世界坐标是否可行走
///
/// 世界坐标
/// 如果可行走返回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);
}
}