741 lines
21 KiB
C#
741 lines
21 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using Godot;
|
|
using Views;
|
|
|
|
/// <summary>
|
|
/// 校园学生角色控制器
|
|
/// </summary>
|
|
public partial class CampusStudent : CharacterBody2D {
|
|
/// <summary>
|
|
/// 网格路径列表
|
|
/// </summary>
|
|
private readonly List<Vector2> _gridPath = new();
|
|
|
|
/// <summary>
|
|
/// 外观主题
|
|
/// </summary>
|
|
private readonly SuitTheme _theme = new();
|
|
|
|
/// <summary>
|
|
/// 获取外观主题ID
|
|
/// </summary>
|
|
/// <returns>主题ID</returns>
|
|
public ulong GetSuitThemeId() {
|
|
return _theme.GetThemeId();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 动画播放器
|
|
/// </summary>
|
|
private AnimationPlayer _animationPlayer;
|
|
|
|
/// <summary>
|
|
/// 是否启用行为控制
|
|
/// </summary>
|
|
private bool _behaviorControlEnabled;
|
|
|
|
/// <summary>
|
|
/// 是否有行为目标
|
|
/// </summary>
|
|
private bool _behaviorHasTarget;
|
|
|
|
/// <summary>
|
|
/// 行为目标点
|
|
/// </summary>
|
|
private Vector2 _behaviorTarget = Vector2.Zero;
|
|
|
|
/// <summary>
|
|
/// 校园控制器
|
|
/// </summary>
|
|
private CampusController _campusController;
|
|
|
|
/// <summary>
|
|
/// 当前目标点
|
|
/// </summary>
|
|
private Vector2 _currentTarget = Vector2.Zero;
|
|
|
|
/// <summary>
|
|
/// 网格路径是否激活
|
|
/// </summary>
|
|
private bool _gridPathActive;
|
|
|
|
/// <summary>
|
|
/// 当前网格路径索引
|
|
/// </summary>
|
|
private int _gridPathIndex;
|
|
|
|
/// <summary>
|
|
/// 网格路径是否挂起
|
|
/// </summary>
|
|
private bool _gridPathPending;
|
|
|
|
/// <summary>
|
|
/// 网格重新寻路重试计时器
|
|
/// </summary>
|
|
private float _gridPathRetryTimer;
|
|
|
|
/// <summary>
|
|
/// 是否有目标
|
|
/// </summary>
|
|
private bool _hasTarget;
|
|
|
|
/// <summary>
|
|
/// 上一帧的时间间隔
|
|
/// </summary>
|
|
private double _lastDelta;
|
|
|
|
/// <summary>
|
|
/// 上一次的面朝方向
|
|
/// </summary>
|
|
private FacingDirection _lastFacing = FacingDirection.Down;
|
|
|
|
/// <summary>
|
|
/// 上一次的位置
|
|
/// </summary>
|
|
private Vector2 _lastPosition;
|
|
|
|
/// <summary>
|
|
/// 是否已配置巡逻
|
|
/// </summary>
|
|
private bool _patrolConfigured;
|
|
|
|
/// <summary>
|
|
/// 当前巡逻点索引
|
|
/// </summary>
|
|
private int _patrolIndex;
|
|
|
|
/// <summary>
|
|
/// 巡逻点列表
|
|
/// </summary>
|
|
private List<Vector2> _patrolPoints = new();
|
|
|
|
/// <summary>
|
|
/// 手机退出动作锁定
|
|
/// </summary>
|
|
private bool _phoneExitLocked;
|
|
|
|
/// <summary>
|
|
/// 手机闲置动画是否激活
|
|
/// </summary>
|
|
private bool _phoneIdleActive;
|
|
|
|
/// <summary>
|
|
/// 卡住计时器
|
|
/// </summary>
|
|
private float _stuckTimer;
|
|
|
|
/// <summary>
|
|
/// 是否使用物理移动
|
|
/// </summary>
|
|
private bool _usePhysicsMovement = true;
|
|
|
|
/// <summary>
|
|
/// 移动速度
|
|
/// </summary>
|
|
[Export]
|
|
public float MoveSpeed { get; set; } = 60.0f;
|
|
|
|
/// <summary>
|
|
/// 目标到达判定距离
|
|
/// </summary>
|
|
[Export]
|
|
public float TargetReachDistance { get; set; } = 6.0f;
|
|
|
|
/// <summary>
|
|
/// 是否使用16x16精灵
|
|
/// </summary>
|
|
[Export]
|
|
public bool Use16X16Sprites { get; set; } = true;
|
|
|
|
/// <summary>
|
|
/// 卡住重新寻路时间
|
|
/// </summary>
|
|
[Export]
|
|
public float StuckRepathSeconds { get; set; } = 0.6f;
|
|
|
|
/// <summary>
|
|
/// 卡住距离阈值
|
|
/// </summary>
|
|
[Export]
|
|
public float StuckDistanceEpsilon { get; set; } = 2.0f;
|
|
|
|
/// <summary>
|
|
/// 网格重新寻路间隔
|
|
/// </summary>
|
|
[Export]
|
|
public float GridRepathInterval { get; set; } = 0.25f;
|
|
|
|
/// <summary>
|
|
/// 调试绘制路径
|
|
/// </summary>
|
|
[Export]
|
|
public bool DebugDrawPath { get; set; }
|
|
|
|
/// <summary>
|
|
/// 环境碰撞掩码
|
|
/// </summary>
|
|
[Export]
|
|
public uint EnvironmentCollisionMask { get; set; } = 1u;
|
|
|
|
/// <summary>
|
|
/// 学生碰撞层
|
|
/// </summary>
|
|
[Export]
|
|
public uint StudentCollisionLayer { get; set; } = 1u << 1;
|
|
|
|
/// <summary>
|
|
/// 准备就绪时调用
|
|
/// </summary>
|
|
public override void _Ready() {
|
|
_animationPlayer = GetNodeOrNull<AnimationPlayer>("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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 物理处理
|
|
/// </summary>
|
|
/// <param name="delta">时间间隔</param>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 绘制调试信息
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 配置巡逻
|
|
/// </summary>
|
|
/// <param name="points">巡逻点列表</param>
|
|
/// <param name="startIndex">起始索引</param>
|
|
public void ConfigurePatrol(List<Vector2> points, int startIndex) {
|
|
_patrolPoints = points ?? new List<Vector2>();
|
|
if (_patrolPoints.Count == 0) return;
|
|
|
|
_patrolIndex = (startIndex % _patrolPoints.Count + _patrolPoints.Count) % _patrolPoints.Count;
|
|
_patrolConfigured = true;
|
|
ApplyRandomTheme();
|
|
|
|
AdvanceTarget();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 启用行为控制
|
|
/// </summary>
|
|
public void EnableBehaviorControl() {
|
|
_behaviorControlEnabled = true;
|
|
_behaviorHasTarget = false;
|
|
_patrolConfigured = false;
|
|
_patrolPoints.Clear();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 设置行为目标
|
|
/// </summary>
|
|
/// <param name="target">目标位置</param>
|
|
public void SetBehaviorTarget(Vector2 target) {
|
|
_behaviorControlEnabled = true;
|
|
_behaviorTarget = target;
|
|
_behaviorHasTarget = true;
|
|
BeginGridTarget(target);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 清除行为目标
|
|
/// </summary>
|
|
public void ClearBehaviorTarget() {
|
|
_behaviorHasTarget = false;
|
|
_hasTarget = false;
|
|
_currentTarget = Vector2.Zero;
|
|
_stuckTimer = 0.0f;
|
|
_gridPathActive = false;
|
|
_gridPathPending = false;
|
|
_gridPathIndex = 0;
|
|
_gridPathRetryTimer = 0.0f;
|
|
_gridPath.Clear();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 进入建筑时同步头像
|
|
/// </summary>
|
|
/// <param name="locationId">地点ID</param>
|
|
public void EnterBuilding(CampusLocationId locationId) {
|
|
_campusController ??= GetTree()?.CurrentScene as CampusController;
|
|
_campusController?.HandleStudentEnterBuilding(this, locationId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 离开建筑时移除头像
|
|
/// </summary>
|
|
/// <param name="locationId">地点ID</param>
|
|
public void ExitBuilding(CampusLocationId locationId) {
|
|
_campusController ??= GetTree()?.CurrentScene as CampusController;
|
|
_campusController?.HandleStudentExitBuilding(this, locationId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 设置网格寻路目标
|
|
/// </summary>
|
|
/// <param name="target">目标位置</param>
|
|
private void BeginGridTarget(Vector2 target) {
|
|
_currentTarget = target;
|
|
_hasTarget = true;
|
|
_stuckTimer = 0.0f;
|
|
_gridPathActive = false;
|
|
_gridPathPending = true;
|
|
_gridPathIndex = 0;
|
|
_gridPathRetryTimer = 0.0f;
|
|
_gridPath.Clear();
|
|
TryBuildPendingGridPath();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 是否已到达行为目标
|
|
/// </summary>
|
|
/// <returns>到达返回true</returns>
|
|
public bool HasReachedBehaviorTarget() {
|
|
if (!_behaviorHasTarget) return true;
|
|
if (_gridPathActive) return _gridPathIndex >= _gridPath.Count;
|
|
if (_gridPathPending) return false;
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 开始玩手机
|
|
/// </summary>
|
|
public void StartPhoneIdle() {
|
|
if (_animationPlayer == null || !_animationPlayer.HasAnimation("phone_up")) return;
|
|
if (_phoneIdleActive) return;
|
|
|
|
_phoneExitLocked = false;
|
|
_phoneIdleActive = true;
|
|
_animationPlayer.Play("phone_up");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 停止玩手机
|
|
/// </summary>
|
|
/// <param name="lockMovement">是否锁定移动</param>
|
|
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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 应用随机主题
|
|
/// </summary>
|
|
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));
|
|
}
|
|
|
|
/// <summary>
|
|
/// 缓存精灵节点
|
|
/// </summary>
|
|
private void CacheSprites() {
|
|
// 缓存子节点引用,避免每帧查找
|
|
_theme.CacheComponent(Res.Type.Accessory, GetNode<Sprite2D>("parts/accessory"));
|
|
_theme.CacheComponent(Res.Type.Body, GetNode<Sprite2D>("parts/body"));
|
|
_theme.CacheComponent(Res.Type.Eye, GetNode<Sprite2D>("parts/eye"));
|
|
_theme.CacheComponent(Res.Type.Hair, GetNode<Sprite2D>("parts/hairstyle"));
|
|
_theme.CacheComponent(Res.Type.Outfit, GetNode<Sprite2D>("parts/outfit"));
|
|
_theme.CacheComponent(Res.Type.Phone, GetNode<Sprite2D>("parts/smartphone"));
|
|
}
|
|
|
|
/// <summary>
|
|
/// 配置碰撞
|
|
/// </summary>
|
|
private void ConfigureCollision() {
|
|
// 学生只与环境碰撞,不与其它学生碰撞
|
|
CollisionLayer = StudentCollisionLayer;
|
|
CollisionMask = EnvironmentCollisionMask;
|
|
_usePhysicsMovement = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 推进到下一个目标
|
|
/// </summary>
|
|
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]);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 更新卡住计时器
|
|
/// </summary>
|
|
/// <param name="delta">时间间隔</param>
|
|
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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 重新规划路径到当前目标
|
|
/// </summary>
|
|
private void RepathToCurrentTarget() {
|
|
if (!_hasTarget) return;
|
|
|
|
_gridPathActive = false;
|
|
_gridPathPending = true;
|
|
_gridPathIndex = 0;
|
|
_gridPath.Clear();
|
|
TryBuildPendingGridPath();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 尝试构建挂起的网格路径
|
|
/// </summary>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 应用移动
|
|
/// </summary>
|
|
/// <param name="delta">时间间隔</param>
|
|
private void ApplyMovement(double delta) {
|
|
if (_usePhysicsMovement) {
|
|
MoveAndSlide();
|
|
return;
|
|
}
|
|
|
|
// 关闭碰撞时直接平移,避免 CharacterBody2D 在无碰撞层时不移动
|
|
GlobalPosition += Velocity * (float)delta;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 更新面朝向动画
|
|
/// </summary>
|
|
/// <param name="velocity">当前速度</param>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 播放闲置动画
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 播放指定动画
|
|
/// </summary>
|
|
/// <param name="animationName">动画名称</param>
|
|
private void PlayAnimation(string animationName) {
|
|
if (_animationPlayer == null) return;
|
|
|
|
if (_animationPlayer.CurrentAnimation != animationName) _animationPlayer.Play(animationName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 动画结束回调
|
|
/// </summary>
|
|
/// <param name="animationName">动画名称</param>
|
|
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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 处理网格路径移动
|
|
/// </summary>
|
|
/// <param name="delta">时间间隔</param>
|
|
/// <returns>如果正在移动返回true</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 转换为轴向速度
|
|
/// </summary>
|
|
/// <param name="delta">位移</param>
|
|
/// <returns>轴向速度</returns>
|
|
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));
|
|
}
|
|
|
|
/// <summary>
|
|
/// 构建网格路径
|
|
/// </summary>
|
|
/// <param name="start">起点</param>
|
|
/// <param name="target">终点</param>
|
|
/// <returns>路径点列表</returns>
|
|
private List<Vector2> 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 移除直接回头的路径点
|
|
/// </summary>
|
|
/// <param name="path">路径</param>
|
|
private void RemoveImmediateBacktracks(List<Vector2> 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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 简化网格路径(移除共线点)
|
|
/// </summary>
|
|
/// <param name="path">路径</param>
|
|
/// <returns>简化后的路径</returns>
|
|
private List<Vector2> SimplifyGridPath(List<Vector2> path) {
|
|
if (path == null || path.Count < 3) return path;
|
|
|
|
var simplified = new List<Vector2> { 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 面朝方向
|
|
/// </summary>
|
|
private enum FacingDirection {
|
|
Up,
|
|
Down,
|
|
Left,
|
|
Right
|
|
}
|
|
}
|