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 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; [Export] public float MoveSpeed { get; set; } = 60.0f; [Export] public float TargetReachDistance { get; set; } = 6.0f; [Export] public bool Use16X16Sprites { get; set; } = true; [Export] public bool EnableAvoidance { get; set; } = true; [Export] public float StuckRepathSeconds { get; set; } = 0.6f; [Export] public float StuckDistanceEpsilon { get; set; } = 2.0f; public override void _Ready() { _navigationAgent = GetNodeOrNull("NavigationAgent2D"); _animationPlayer = GetNodeOrNull("AnimationPlayer"); CacheSprites(); if (_navigationAgent != null) { // 让寻路点更“贴近目标”,避免走到边缘时抖动 _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 (_navigationAgent == null || _patrolPoints.Count == 0) { PlayIdleAnimation(); return; } // 到达目标点或无路可走时,切换到下一个巡游点 if (_navigationAgent.IsNavigationFinished() || _navigationAgent.DistanceToTarget() <= TargetReachDistance) AdvanceTarget(); var nextPosition = _navigationAgent.GetNextPathPosition(); var toNext = nextPosition - GlobalPosition; if (toNext.LengthSquared() < 0.01f) { if (!EnableAvoidance) { Velocity = Vector2.Zero; MoveAndSlide(); } PlayIdleAnimation(); UpdateStuckTimer(delta); return; } var direction = toNext.Normalized(); var desiredVelocity = direction * MoveSpeed; if (EnableAvoidance) { // 交给导航系统做群体避让 _navigationAgent.Velocity = desiredVelocity; } else { // 未启用避让时直接移动 Velocity = desiredVelocity; MoveAndSlide(); UpdateFacingAnimation(Velocity); UpdateStuckTimer(delta); } } 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 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 AdvanceTarget() { if (_patrolPoints.Count == 0 || _navigationAgent == null) 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) { _navigationAgent.TargetPosition = target; return; } } _navigationAgent.TargetPosition = _patrolPoints[_patrolIndex]; } private void OnVelocityComputed(Vector2 safeVelocity) { // 使用安全速度移动,避免与其它角色硬碰硬卡住 Velocity = safeVelocity; MoveAndSlide(); UpdateFacingAnimation(Velocity); UpdateStuckTimer(_lastDelta); } private void UpdateStuckTimer(double delta) { // 若短时间内几乎没有位移,则换一个目标点脱困 if (GlobalPosition.DistanceTo(_lastPosition) <= StuckDistanceEpsilon) _stuckTimer += (float)delta; else _stuckTimer = 0.0f; _lastPosition = GlobalPosition; if (_stuckTimer >= StuckRepathSeconds) { _stuckTimer = 0.0f; AdvanceTarget(); } } private void UpdateFacingAnimation(Vector2 velocity) { 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() { 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 enum FacingDirection { Up, Down, Left, Right } }