From 1690981c70e7c52b6b5182fa9b331a24171ce475 Mon Sep 17 00:00:00 2001 From: wjsjwr Date: Sun, 28 Dec 2025 22:49:43 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dstudent=E8=BF=90=E5=8A=A8bugs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scenes/CampusController.cs | 138 ++++++++++++++++++++++++++++++++----- scenes/campus.tscn | 9 ++- scripts/CampusStudent.cs | 101 ++++++++++++++++++++++++--- 3 files changed, 216 insertions(+), 32 deletions(-) diff --git a/scenes/CampusController.cs b/scenes/CampusController.cs index 76d95a6..160b05b 100644 --- a/scenes/CampusController.cs +++ b/scenes/CampusController.cs @@ -18,6 +18,10 @@ public partial class CampusController : Node2D private Node2D _studentsRoot; private readonly List _coveragePoints = new(); private bool _spawnPending = true; + private bool _navBakePending = false; + private bool _navBakeReady = false; + private Rid _navBakeMap = new(); + private uint _navBakeIterationId = 0; // Called when the node enters the scene tree for the first time. public override void _Ready() @@ -45,6 +49,9 @@ public partial class CampusController : Node2D AddChild(_studentsRoot); } + // 使用可视化轮廓重新烘焙导航多边形,确保洞被正确识别 + RebakeNavigationPolygonFromOutlines(); + // 等待导航地图同步完成后再生成学生 _spawnPending = true; } @@ -93,6 +100,11 @@ public partial class CampusController : Node2D return; } + if (_navBakePending || !_navBakeReady) + { + return; + } + // 已生成过学生则不重复生成 if (_studentsRoot != null && _studentsRoot.GetChildCount() > 0) { @@ -119,13 +131,26 @@ public partial class CampusController : Node2D return; } - // 强制刷新一次导航数据,确保首帧同步 - NavigationServer2D.MapForceUpdate(map); - if (NavigationServer2D.MapGetIterationId(map) == 0) + var iterationId = NavigationServer2D.MapGetIterationId(map); + if (iterationId == 0) { return; } + // 等待导航图完成新一轮同步,避免采样到旧的/空的地图 + if (_navBakeMap.IsValid) + { + if (map == _navBakeMap && iterationId == _navBakeIterationId) + { + return; + } + + if (NavigationServer2D.MapGetRegions(map).Count == 0) + { + return; + } + } + SpawnStudents(map); _spawnPending = false; } @@ -133,7 +158,7 @@ public partial class CampusController : Node2D private void SpawnStudents(Rid map) { _coveragePoints.Clear(); - _coveragePoints.AddRange(BuildCoveragePoints(map)); + _coveragePoints.AddRange(BuildCoveragePoints()); if (_coveragePoints.Count == 0) { GD.PushWarning("未采样到可行走区域,跳过学生生成。"); @@ -152,6 +177,7 @@ public partial class CampusController : Node2D _studentsRoot.AddChild(student); student.Name = $"CampusStudent_{i + 1}"; + student.SetNavigationMap(map); // 随机放置在可行走区域,并设置不同的巡游起点 var randomIndex = GD.RandRange(0, _coveragePoints.Count - 1); @@ -160,7 +186,7 @@ public partial class CampusController : Node2D } } - private List BuildCoveragePoints(Rid map) + private List BuildCoveragePoints() { var points = new List(); var polygon = _navigationRegion.NavigationPolygon; @@ -169,10 +195,27 @@ public partial class CampusController : Node2D return points; } - // 根据导航多边形顶点计算采样范围 - var min = polygon.Vertices[0]; - var max = polygon.Vertices[0]; - foreach (var v in polygon.Vertices) + var outlineCount = polygon.GetOutlineCount(); + var outer = polygon.GetOutline(0); + if (outer.Length == 0) + { + return points; + } + + var holes = new List(); + for (int i = 1; i < outlineCount; i++) + { + var hole = polygon.GetOutline(i); + if (hole.Length > 0) + { + holes.Add(hole); + } + } + + // 根据外轮廓计算采样范围 + var min = outer[0]; + var max = outer[0]; + foreach (var v in outer) { min = new Vector2(Mathf.Min(min.X, v.X), Mathf.Min(min.Y, v.Y)); max = new Vector2(Mathf.Max(max.X, v.X), Mathf.Max(max.Y, v.Y)); @@ -185,16 +228,29 @@ public partial class CampusController : Node2D for (float y = min.Y; y <= max.Y; y += step) { var local = new Vector2(x, y); - var global = _navigationRegion.ToGlobal(local); - var closest = NavigationServer2D.MapGetClosestPoint(map, global); - if (closest.DistanceTo(global) > minDistance) + if (!Geometry2D.IsPointInPolygon(local, outer)) { continue; } - if (!HasNearbyPoint(points, closest, minDistance)) + var insideHole = false; + for (int i = 0; i < holes.Count; i++) { - points.Add(closest); + if (Geometry2D.IsPointInPolygon(local, holes[i])) + { + insideHole = true; + break; + } + } + if (insideHole) + { + continue; + } + + var global = _navigationRegion.ToGlobal(local); + if (!HasNearbyPoint(points, global, minDistance)) + { + points.Add(global); if (points.Count >= MaxCoveragePoints) { return points; @@ -203,12 +259,25 @@ public partial class CampusController : Node2D } } + // 补充采样轮廓顶点,避免狭窄区域缺少巡游点 + foreach (var vertex in outer) + { + var global = _navigationRegion.ToGlobal(vertex); + if (!HasNearbyPoint(points, global, minDistance)) + { + points.Add(global); + if (points.Count >= MaxCoveragePoints) + { + return points; + } + } + } + // 兜底:至少给一个可行走点 if (points.Count == 0) { var centerLocal = (min + max) * 0.5f; - var centerGlobal = _navigationRegion.ToGlobal(centerLocal); - points.Add(NavigationServer2D.MapGetClosestPoint(map, centerGlobal)); + points.Add(_navigationRegion.ToGlobal(centerLocal)); } return points; @@ -239,4 +308,41 @@ public partial class CampusController : Node2D } return false; } + + private void RebakeNavigationPolygonFromOutlines() + { + if (_navigationRegion == null || _navigationRegion.NavigationPolygon == null) + { + return; + } + + var navPolygon = _navigationRegion.NavigationPolygon; + var outlineCount = navPolygon.GetOutlineCount(); + if (outlineCount == 0) + { + return; + } + + _navBakePending = true; + _navBakeReady = false; + _navBakeMap = GetNavigationMap(); + _navBakeIterationId = _navBakeMap.IsValid ? NavigationServer2D.MapGetIterationId(_navBakeMap) : 0; + + // 将第一个轮廓作为可通行区域,其余轮廓作为障碍洞 + var sourceGeometry = new NavigationMeshSourceGeometryData2D(); + sourceGeometry.AddTraversableOutline(navPolygon.GetOutline(0)); + for (int i = 1; i < outlineCount; i++) + { + sourceGeometry.AddObstructionOutline(navPolygon.GetOutline(i)); + } + + NavigationServer2D.BakeFromSourceGeometryData(navPolygon, sourceGeometry, Callable.From(() => + { + // 烘焙完成后再刷新导航区域,避免用旧的多边形生成地图 + _navigationRegion.NavigationPolygon = navPolygon; + _navBakePending = false; + _navBakeReady = true; + })); + } + } diff --git a/scenes/campus.tscn b/scenes/campus.tscn index 8d22cd6..9c262bf 100644 --- a/scenes/campus.tscn +++ b/scenes/campus.tscn @@ -7,13 +7,12 @@ [ext_resource type="PackedScene" uid="uid://drmjsqoy8htc8" path="res://scenes/ui-elements/task_list.tscn" id="3_4gjr3"] [sub_resource type="NavigationPolygon" id="NavigationPolygon_8u8vn"] -vertices = PackedVector2Array(960, 544, 0, 544, 416, 512, 464, 512, 384, 512, 384, 464, 176, 496, 304, 464, 144, 496, 200, 352, 200, 264, 232, 304, 232, 464, 232, 288, 144, 208, 144, 192, 160, 192, 160, 208, 240, 208, 240, 16, 272, 16, 272, 232, 432, 232, 432, 192, 448, 192, 448, 232, 608, 232, 608, 16, 640, 16, 640, 240, 704, 240, 704, 192, 728, 192, 728, 168, 760, 168, 760, 192, 768, 192, 768, 16, 800, 16, 800, 224, 840, 224, 840, 192, 856, 192, 856, 224, 864, 224, 864, 288, 832, 288, 832, 224, 800, 288, 912, 304, 912, 288, 936, 288, 952, 336, 936, 184, 952, 184, 832, 336, 832, 304, 832, 512, 800, 512, 576, 288, 576, 304, 544, 304, 544, 288, 960, 512, 120, 360, 128, 360, 128, 400, 192, 400, 192, 352, 288, 464, 288, 448, 304, 448, 176, 464, 144, 464, 0, 496, 96, 496, 96, 288, 120, 264, 0, 288, 0, 0, 32, 0, 32, 208, 416, 288, 464, 288) -polygons = Array[PackedInt32Array]([PackedInt32Array(0, 1, 2, 3), PackedInt32Array(4, 2, 1), PackedInt32Array(5, 4, 1, 6, 7), PackedInt32Array(8, 6, 1), PackedInt32Array(9, 10, 11, 12), PackedInt32Array(13, 11, 10), PackedInt32Array(14, 15, 16, 17), PackedInt32Array(18, 19, 20, 21), PackedInt32Array(22, 23, 24, 25), PackedInt32Array(26, 27, 28, 29), PackedInt32Array(30, 31, 32), PackedInt32Array(32, 33, 34, 35), PackedInt32Array(36, 37, 38, 39), PackedInt32Array(40, 41, 42, 43), PackedInt32Array(43, 44, 45, 46), PackedInt32Array(40, 43, 46), PackedInt32Array(47, 40, 46), PackedInt32Array(39, 47, 46, 48), PackedInt32Array(49, 50, 51, 52), PackedInt32Array(51, 53, 54, 52), PackedInt32Array(49, 52, 55, 56), PackedInt32Array(55, 57, 58, 48), PackedInt32Array(56, 55, 48), PackedInt32Array(46, 56, 48), PackedInt32Array(39, 48, 59, 30), PackedInt32Array(59, 60, 61, 62), PackedInt32Array(57, 63, 0), PackedInt32Array(64, 65, 66), PackedInt32Array(67, 68, 9, 12), PackedInt32Array(69, 70, 71, 7), PackedInt32Array(69, 7, 6), PackedInt32Array(12, 69, 6), PackedInt32Array(12, 6, 72, 67), PackedInt32Array(66, 67, 72, 73), PackedInt32Array(1, 74, 75), PackedInt32Array(8, 1, 75), PackedInt32Array(73, 8, 75, 76, 66), PackedInt32Array(64, 66, 76), PackedInt32Array(77, 64, 76), PackedInt32Array(78, 79, 80, 81), PackedInt32Array(76, 78, 81, 14, 77), PackedInt32Array(10, 77, 14, 17), PackedInt32Array(10, 17, 18, 21, 13), PackedInt32Array(82, 13, 21, 22), PackedInt32Array(82, 22, 25), PackedInt32Array(30, 32, 35, 39), PackedInt32Array(82, 25, 26, 83), PackedInt32Array(35, 36, 39), PackedInt32Array(29, 30, 59), PackedInt32Array(26, 29, 59, 62), PackedInt32Array(26, 62, 83), PackedInt32Array(82, 83, 3, 2), PackedInt32Array(0, 3, 58), PackedInt32Array(0, 58, 57)]) -outlines = Array[PackedVector2Array]([PackedVector2Array(0, 0, 32, 0, 32, 208, 144, 208, 144, 192, 160, 192, 160, 208, 240, 208, 240, 16, 272, 16, 272, 232, 432, 232, 432, 192, 448, 192, 448, 232, 608, 232, 608, 16, 640, 16, 640, 240, 704, 240, 704, 192, 728, 192, 728, 168, 760, 168, 760, 192, 768, 192, 768, 16, 800, 16, 800, 224, 832, 224, 840, 224, 840, 192, 856, 192, 856, 224, 864, 224, 864, 288, 832, 288, 832, 304, 912, 304, 912, 288, 936, 288, 936, 184, 952, 184, 952, 336, 832, 336, 832, 512, 960, 512, 960, 544, 0, 544, 0, 496, 96, 496, 96, 288, 0, 288), PackedVector2Array(144, 496, 176, 496, 176, 464, 144, 464), PackedVector2Array(128, 400, 192, 400, 192, 352, 200, 352, 200, 264, 120, 264, 120, 360, 128, 360), PackedVector2Array(232, 304, 232, 464, 288, 464, 288, 448, 304, 448, 304, 464, 384, 464, 384, 512, 416, 512, 416, 288, 232, 288), PackedVector2Array(464, 288, 464, 512, 800, 512, 800, 288, 576, 288, 576, 304, 544, 304, 544, 288), PackedVector2Array(144, 416, 176, 416, 176, 448, 144, 448)]) +vertices = PackedVector2Array(956, 540, 4, 540, 420, 516, 460, 516, 380, 516, 204, 356, 204, 260, 228, 284, 228, 468, 4, 500, 100, 500, 380, 468, 300, 468, 148, 212, 148, 196, 156, 196, 156, 212, 244, 212, 244, 20, 268, 20, 268, 236, 436, 236, 436, 196, 444, 196, 444, 236, 612, 236, 612, 20, 636, 20, 636, 244, 708, 244, 708, 196, 732, 196, 732, 172, 756, 172, 756, 196, 772, 196, 772, 20, 796, 20, 796, 228, 844, 228, 844, 196, 852, 196, 852, 228, 860, 228, 860, 284, 828, 284, 804, 284, 916, 308, 916, 292, 940, 292, 948, 332, 940, 188, 948, 188, 828, 332, 828, 308, 828, 516, 804, 516, 572, 284, 572, 300, 548, 300, 548, 284, 956, 516, 116, 364, 124, 364, 124, 396, 196, 396, 196, 356, 180, 436, 180, 412, 140, 412, 292, 468, 292, 452, 300, 452, 140, 436, 4, 284, 4, 4, 28, 4, 28, 212, 100, 284, 116, 260, 460, 284, 420, 284) +polygons = Array[PackedInt32Array]([PackedInt32Array(0, 1, 2, 3), PackedInt32Array(4, 2, 1), PackedInt32Array(5, 6, 7, 8), PackedInt32Array(4, 1, 9, 10), PackedInt32Array(11, 4, 10, 12), PackedInt32Array(13, 14, 15, 16), PackedInt32Array(17, 18, 19, 20), PackedInt32Array(21, 22, 23, 24), PackedInt32Array(25, 26, 27, 28), PackedInt32Array(29, 30, 31), PackedInt32Array(31, 32, 33, 34), PackedInt32Array(35, 36, 37, 38), PackedInt32Array(39, 40, 41, 42), PackedInt32Array(42, 43, 44, 45), PackedInt32Array(39, 42, 45), PackedInt32Array(38, 39, 45, 46), PackedInt32Array(47, 48, 49, 50), PackedInt32Array(49, 51, 52, 50), PackedInt32Array(47, 50, 53, 54), PackedInt32Array(53, 55, 56, 46), PackedInt32Array(54, 53, 46), PackedInt32Array(45, 54, 46), PackedInt32Array(38, 46, 57, 29), PackedInt32Array(57, 58, 59, 60), PackedInt32Array(55, 61, 0), PackedInt32Array(62, 63, 64, 10), PackedInt32Array(65, 66, 5, 8), PackedInt32Array(65, 8, 67, 68), PackedInt32Array(64, 65, 68, 69), PackedInt32Array(70, 71, 72, 12), PackedInt32Array(70, 12, 10), PackedInt32Array(8, 70, 10), PackedInt32Array(67, 8, 10, 73), PackedInt32Array(74, 75, 76, 77), PackedInt32Array(78, 74, 77, 79), PackedInt32Array(29, 31, 34, 38), PackedInt32Array(34, 35, 38), PackedInt32Array(28, 29, 57), PackedInt32Array(24, 25, 28, 57, 60), PackedInt32Array(24, 60, 80, 21), PackedInt32Array(20, 21, 80, 81, 6), PackedInt32Array(64, 69, 73, 10), PackedInt32Array(16, 17, 20, 6), PackedInt32Array(81, 7, 6), PackedInt32Array(62, 10, 78, 79), PackedInt32Array(79, 77, 13, 6), PackedInt32Array(6, 13, 16), PackedInt32Array(81, 80, 3, 2), PackedInt32Array(0, 3, 56), PackedInt32Array(0, 56, 55)]) +outlines = Array[PackedVector2Array]([PackedVector2Array(0, 0, 32, 0, 32, 208, 144, 208, 144, 192, 160, 192, 160, 208, 240, 208, 240, 16, 272, 16, 272, 232, 432, 232, 432, 192, 448, 192, 448, 232, 608, 232, 608, 16, 640, 16, 640, 240, 704, 240, 704, 192, 728, 192, 728, 168, 760, 168, 760, 192, 768, 192, 768, 16, 800, 16, 800, 224, 832, 224, 840, 224, 840, 192, 856, 192, 856, 224, 864, 224, 864, 288, 832, 288, 832, 304, 912, 304, 912, 288, 936, 288, 936, 184, 952, 184, 952, 336, 832, 336, 832, 512, 960, 512, 960, 544, 0, 544, 0, 496, 96, 496, 96, 288, 0, 288), PackedVector2Array(144, 432, 176, 432, 176, 416, 144, 416), PackedVector2Array(128, 392, 192, 392, 192, 352, 200, 352, 200, 264, 120, 264, 120, 360, 128, 360), PackedVector2Array(232, 304, 232, 464, 288, 464, 288, 448, 304, 448, 304, 464, 384, 464, 384, 512, 416, 512, 416, 288, 232, 288), PackedVector2Array(464, 288, 464, 512, 800, 512, 800, 288, 576, 288, 576, 304, 544, 304, 544, 288), PackedVector2Array(144, 464, 176, 464, 176, 480, 144, 480)]) parsed_geometry_type = 1 parsed_collision_mask = 4294967294 -source_geometry_mode = 1 -agent_radius = 8.0 +agent_radius = 4.0 [sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_4gjr3"] texture = ExtResource("1_p4tmp") diff --git a/scripts/CampusStudent.cs b/scripts/CampusStudent.cs index 4886631..e47a61e 100644 --- a/scripts/CampusStudent.cs +++ b/scripts/CampusStudent.cs @@ -21,21 +21,32 @@ public partial class CampusStudent : CharacterBody2D private List _patrolPoints = new(); private Sprite2D _smartphone; private float _stuckTimer; + private Rid _navigationMap; + private Vector2 _currentTarget = Vector2.Zero; + private bool _hasTarget; + private bool _usePhysicsMovement = true; [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 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 uint EnvironmentCollisionMask { get; set; } = 1u; + [Export] public uint StudentCollisionLayer { get; set; } = 1u << 1; public override void _Ready() { _navigationAgent = GetNodeOrNull("NavigationAgent2D"); _animationPlayer = GetNodeOrNull("AnimationPlayer"); CacheSprites(); + ConfigureCollision(); if (_navigationAgent != null) { + // 强制关闭避让,学生之间直接穿过 + EnableAvoidance = false; + // 让寻路点更“贴近目标”,避免走到边缘时抖动 _navigationAgent.PathDesiredDistance = TargetReachDistance; _navigationAgent.TargetDesiredDistance = TargetReachDistance; @@ -62,14 +73,14 @@ public partial class CampusStudent : CharacterBody2D } // 到达目标点或无路可走时,切换到下一个巡游点 - if (_navigationAgent.IsNavigationFinished() || _navigationAgent.DistanceToTarget() <= TargetReachDistance) + if (_navigationAgent.IsNavigationFinished()) AdvanceTarget(); var nextPosition = _navigationAgent.GetNextPathPosition(); var toNext = nextPosition - GlobalPosition; if (toNext.LengthSquared() < 0.01f) { - if (!EnableAvoidance) + if (!EnableAvoidance && _usePhysicsMovement) { Velocity = Vector2.Zero; MoveAndSlide(); @@ -91,8 +102,8 @@ public partial class CampusStudent : CharacterBody2D else { // 未启用避让时直接移动 - Velocity = desiredVelocity; - MoveAndSlide(); + Velocity = ClampVelocityToNavMesh(desiredVelocity); + ApplyMovement(delta); UpdateFacingAnimation(Velocity); UpdateStuckTimer(delta); } @@ -110,6 +121,12 @@ public partial class CampusStudent : CharacterBody2D if (_navigationAgent != null) AdvanceTarget(); } + public void SetNavigationMap(Rid map) + { + // 由校园控制器传入导航地图,供本地边界夹紧使用 + _navigationMap = map; + } + public void ApplyRandomTheme() { // 随机替换身体与配件贴图,形成不同主题外观 @@ -135,6 +152,14 @@ public partial class CampusStudent : CharacterBody2D _smartphone = GetNode("parts/smartphone"); } + private void ConfigureCollision() + { + // 学生只与环境碰撞,不与其它学生碰撞 + CollisionLayer = StudentCollisionLayer; + CollisionMask = EnvironmentCollisionMask; + _usePhysicsMovement = true; + } + private void AdvanceTarget() { if (_patrolPoints.Count == 0 || _navigationAgent == null) return; @@ -146,26 +171,38 @@ public partial class CampusStudent : CharacterBody2D _patrolIndex = (_patrolIndex + 1) % _patrolPoints.Count; if (GlobalPosition.DistanceTo(target) > TargetReachDistance * 1.5f) { + _currentTarget = target; + _hasTarget = true; _navigationAgent.TargetPosition = target; + _stuckTimer = 0.0f; return; } } - _navigationAgent.TargetPosition = _patrolPoints[_patrolIndex]; + _currentTarget = _patrolPoints[_patrolIndex]; + _hasTarget = true; + _navigationAgent.TargetPosition = _currentTarget; + _stuckTimer = 0.0f; } private void OnVelocityComputed(Vector2 safeVelocity) { // 使用安全速度移动,避免与其它角色硬碰硬卡住 - Velocity = safeVelocity; - MoveAndSlide(); + 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 @@ -176,10 +213,52 @@ public partial class CampusStudent : CharacterBody2D if (_stuckTimer >= StuckRepathSeconds) { _stuckTimer = 0.0f; - AdvanceTarget(); + RepathToCurrentTarget(); } } + private void RepathToCurrentTarget() + { + if (_navigationAgent == null || !_hasTarget) return; + + // 重新请求到同一目标点的路径,避免“固定时间换目的地” + _navigationAgent.TargetPosition = _currentTarget; + } + + private Vector2 ClampVelocityToNavMesh(Vector2 velocity) + { + if (!_navigationMap.IsValid) return velocity; + if (NavigationServer2D.MapGetIterationId(_navigationMap) == 0) 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 void ApplyMovement(double delta) + { + if (_usePhysicsMovement) + { + MoveAndSlide(); + return; + } + + // 关闭碰撞时直接平移,避免 CharacterBody2D 在无碰撞层时不移动 + GlobalPosition += Velocity * (float)delta; + } + private void UpdateFacingAnimation(Vector2 velocity) { if (velocity.LengthSquared() < 0.01f) @@ -243,4 +322,4 @@ public partial class CampusStudent : CharacterBody2D Left, Right } -} \ No newline at end of file +}