修复student运动bugs
This commit is contained in:
parent
1776244fac
commit
1690981c70
@ -18,6 +18,10 @@ public partial class CampusController : Node2D
|
|||||||
private Node2D _studentsRoot;
|
private Node2D _studentsRoot;
|
||||||
private readonly List<Vector2> _coveragePoints = new();
|
private readonly List<Vector2> _coveragePoints = new();
|
||||||
private bool _spawnPending = true;
|
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.
|
// Called when the node enters the scene tree for the first time.
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
@ -45,6 +49,9 @@ public partial class CampusController : Node2D
|
|||||||
AddChild(_studentsRoot);
|
AddChild(_studentsRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 使用可视化轮廓重新烘焙导航多边形,确保洞被正确识别
|
||||||
|
RebakeNavigationPolygonFromOutlines();
|
||||||
|
|
||||||
// 等待导航地图同步完成后再生成学生
|
// 等待导航地图同步完成后再生成学生
|
||||||
_spawnPending = true;
|
_spawnPending = true;
|
||||||
}
|
}
|
||||||
@ -93,6 +100,11 @@ public partial class CampusController : Node2D
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_navBakePending || !_navBakeReady)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 已生成过学生则不重复生成
|
// 已生成过学生则不重复生成
|
||||||
if (_studentsRoot != null && _studentsRoot.GetChildCount() > 0)
|
if (_studentsRoot != null && _studentsRoot.GetChildCount() > 0)
|
||||||
{
|
{
|
||||||
@ -119,13 +131,26 @@ public partial class CampusController : Node2D
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 强制刷新一次导航数据,确保首帧同步
|
var iterationId = NavigationServer2D.MapGetIterationId(map);
|
||||||
NavigationServer2D.MapForceUpdate(map);
|
if (iterationId == 0)
|
||||||
if (NavigationServer2D.MapGetIterationId(map) == 0)
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 等待导航图完成新一轮同步,避免采样到旧的/空的地图
|
||||||
|
if (_navBakeMap.IsValid)
|
||||||
|
{
|
||||||
|
if (map == _navBakeMap && iterationId == _navBakeIterationId)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NavigationServer2D.MapGetRegions(map).Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SpawnStudents(map);
|
SpawnStudents(map);
|
||||||
_spawnPending = false;
|
_spawnPending = false;
|
||||||
}
|
}
|
||||||
@ -133,7 +158,7 @@ public partial class CampusController : Node2D
|
|||||||
private void SpawnStudents(Rid map)
|
private void SpawnStudents(Rid map)
|
||||||
{
|
{
|
||||||
_coveragePoints.Clear();
|
_coveragePoints.Clear();
|
||||||
_coveragePoints.AddRange(BuildCoveragePoints(map));
|
_coveragePoints.AddRange(BuildCoveragePoints());
|
||||||
if (_coveragePoints.Count == 0)
|
if (_coveragePoints.Count == 0)
|
||||||
{
|
{
|
||||||
GD.PushWarning("未采样到可行走区域,跳过学生生成。");
|
GD.PushWarning("未采样到可行走区域,跳过学生生成。");
|
||||||
@ -152,6 +177,7 @@ public partial class CampusController : Node2D
|
|||||||
|
|
||||||
_studentsRoot.AddChild(student);
|
_studentsRoot.AddChild(student);
|
||||||
student.Name = $"CampusStudent_{i + 1}";
|
student.Name = $"CampusStudent_{i + 1}";
|
||||||
|
student.SetNavigationMap(map);
|
||||||
|
|
||||||
// 随机放置在可行走区域,并设置不同的巡游起点
|
// 随机放置在可行走区域,并设置不同的巡游起点
|
||||||
var randomIndex = GD.RandRange(0, _coveragePoints.Count - 1);
|
var randomIndex = GD.RandRange(0, _coveragePoints.Count - 1);
|
||||||
@ -160,7 +186,7 @@ public partial class CampusController : Node2D
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Vector2> BuildCoveragePoints(Rid map)
|
private List<Vector2> BuildCoveragePoints()
|
||||||
{
|
{
|
||||||
var points = new List<Vector2>();
|
var points = new List<Vector2>();
|
||||||
var polygon = _navigationRegion.NavigationPolygon;
|
var polygon = _navigationRegion.NavigationPolygon;
|
||||||
@ -169,10 +195,27 @@ public partial class CampusController : Node2D
|
|||||||
return points;
|
return points;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据导航多边形顶点计算采样范围
|
var outlineCount = polygon.GetOutlineCount();
|
||||||
var min = polygon.Vertices[0];
|
var outer = polygon.GetOutline(0);
|
||||||
var max = polygon.Vertices[0];
|
if (outer.Length == 0)
|
||||||
foreach (var v in polygon.Vertices)
|
{
|
||||||
|
return points;
|
||||||
|
}
|
||||||
|
|
||||||
|
var holes = new List<Vector2[]>();
|
||||||
|
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));
|
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));
|
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)
|
for (float y = min.Y; y <= max.Y; y += step)
|
||||||
{
|
{
|
||||||
var local = new Vector2(x, y);
|
var local = new Vector2(x, y);
|
||||||
var global = _navigationRegion.ToGlobal(local);
|
if (!Geometry2D.IsPointInPolygon(local, outer))
|
||||||
var closest = NavigationServer2D.MapGetClosestPoint(map, global);
|
|
||||||
if (closest.DistanceTo(global) > minDistance)
|
|
||||||
{
|
{
|
||||||
continue;
|
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)
|
if (points.Count >= MaxCoveragePoints)
|
||||||
{
|
{
|
||||||
return points;
|
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)
|
if (points.Count == 0)
|
||||||
{
|
{
|
||||||
var centerLocal = (min + max) * 0.5f;
|
var centerLocal = (min + max) * 0.5f;
|
||||||
var centerGlobal = _navigationRegion.ToGlobal(centerLocal);
|
points.Add(_navigationRegion.ToGlobal(centerLocal));
|
||||||
points.Add(NavigationServer2D.MapGetClosestPoint(map, centerGlobal));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return points;
|
return points;
|
||||||
@ -239,4 +308,41 @@ public partial class CampusController : Node2D
|
|||||||
}
|
}
|
||||||
return false;
|
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;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,13 +7,12 @@
|
|||||||
[ext_resource type="PackedScene" uid="uid://drmjsqoy8htc8" path="res://scenes/ui-elements/task_list.tscn" id="3_4gjr3"]
|
[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"]
|
[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)
|
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, 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)])
|
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, 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)])
|
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_geometry_type = 1
|
||||||
parsed_collision_mask = 4294967294
|
parsed_collision_mask = 4294967294
|
||||||
source_geometry_mode = 1
|
agent_radius = 4.0
|
||||||
agent_radius = 8.0
|
|
||||||
|
|
||||||
[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_4gjr3"]
|
[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_4gjr3"]
|
||||||
texture = ExtResource("1_p4tmp")
|
texture = ExtResource("1_p4tmp")
|
||||||
|
|||||||
@ -21,21 +21,32 @@ public partial class CampusStudent : CharacterBody2D
|
|||||||
private List<Vector2> _patrolPoints = new();
|
private List<Vector2> _patrolPoints = new();
|
||||||
private Sprite2D _smartphone;
|
private Sprite2D _smartphone;
|
||||||
private float _stuckTimer;
|
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 MoveSpeed { get; set; } = 60.0f;
|
||||||
[Export] public float TargetReachDistance { get; set; } = 6.0f;
|
[Export] public float TargetReachDistance { get; set; } = 6.0f;
|
||||||
[Export] public bool Use16X16Sprites { get; set; } = true;
|
[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 StuckRepathSeconds { get; set; } = 0.6f;
|
||||||
[Export] public float StuckDistanceEpsilon { get; set; } = 2.0f;
|
[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()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
_navigationAgent = GetNodeOrNull<NavigationAgent2D>("NavigationAgent2D");
|
_navigationAgent = GetNodeOrNull<NavigationAgent2D>("NavigationAgent2D");
|
||||||
_animationPlayer = GetNodeOrNull<AnimationPlayer>("AnimationPlayer");
|
_animationPlayer = GetNodeOrNull<AnimationPlayer>("AnimationPlayer");
|
||||||
CacheSprites();
|
CacheSprites();
|
||||||
|
ConfigureCollision();
|
||||||
|
|
||||||
if (_navigationAgent != null)
|
if (_navigationAgent != null)
|
||||||
{
|
{
|
||||||
|
// 强制关闭避让,学生之间直接穿过
|
||||||
|
EnableAvoidance = false;
|
||||||
|
|
||||||
// 让寻路点更“贴近目标”,避免走到边缘时抖动
|
// 让寻路点更“贴近目标”,避免走到边缘时抖动
|
||||||
_navigationAgent.PathDesiredDistance = TargetReachDistance;
|
_navigationAgent.PathDesiredDistance = TargetReachDistance;
|
||||||
_navigationAgent.TargetDesiredDistance = TargetReachDistance;
|
_navigationAgent.TargetDesiredDistance = TargetReachDistance;
|
||||||
@ -62,14 +73,14 @@ public partial class CampusStudent : CharacterBody2D
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 到达目标点或无路可走时,切换到下一个巡游点
|
// 到达目标点或无路可走时,切换到下一个巡游点
|
||||||
if (_navigationAgent.IsNavigationFinished() || _navigationAgent.DistanceToTarget() <= TargetReachDistance)
|
if (_navigationAgent.IsNavigationFinished())
|
||||||
AdvanceTarget();
|
AdvanceTarget();
|
||||||
|
|
||||||
var nextPosition = _navigationAgent.GetNextPathPosition();
|
var nextPosition = _navigationAgent.GetNextPathPosition();
|
||||||
var toNext = nextPosition - GlobalPosition;
|
var toNext = nextPosition - GlobalPosition;
|
||||||
if (toNext.LengthSquared() < 0.01f)
|
if (toNext.LengthSquared() < 0.01f)
|
||||||
{
|
{
|
||||||
if (!EnableAvoidance)
|
if (!EnableAvoidance && _usePhysicsMovement)
|
||||||
{
|
{
|
||||||
Velocity = Vector2.Zero;
|
Velocity = Vector2.Zero;
|
||||||
MoveAndSlide();
|
MoveAndSlide();
|
||||||
@ -91,8 +102,8 @@ public partial class CampusStudent : CharacterBody2D
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// 未启用避让时直接移动
|
// 未启用避让时直接移动
|
||||||
Velocity = desiredVelocity;
|
Velocity = ClampVelocityToNavMesh(desiredVelocity);
|
||||||
MoveAndSlide();
|
ApplyMovement(delta);
|
||||||
UpdateFacingAnimation(Velocity);
|
UpdateFacingAnimation(Velocity);
|
||||||
UpdateStuckTimer(delta);
|
UpdateStuckTimer(delta);
|
||||||
}
|
}
|
||||||
@ -110,6 +121,12 @@ public partial class CampusStudent : CharacterBody2D
|
|||||||
if (_navigationAgent != null) AdvanceTarget();
|
if (_navigationAgent != null) AdvanceTarget();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetNavigationMap(Rid map)
|
||||||
|
{
|
||||||
|
// 由校园控制器传入导航地图,供本地边界夹紧使用
|
||||||
|
_navigationMap = map;
|
||||||
|
}
|
||||||
|
|
||||||
public void ApplyRandomTheme()
|
public void ApplyRandomTheme()
|
||||||
{
|
{
|
||||||
// 随机替换身体与配件贴图,形成不同主题外观
|
// 随机替换身体与配件贴图,形成不同主题外观
|
||||||
@ -135,6 +152,14 @@ public partial class CampusStudent : CharacterBody2D
|
|||||||
_smartphone = GetNode<Sprite2D>("parts/smartphone");
|
_smartphone = GetNode<Sprite2D>("parts/smartphone");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ConfigureCollision()
|
||||||
|
{
|
||||||
|
// 学生只与环境碰撞,不与其它学生碰撞
|
||||||
|
CollisionLayer = StudentCollisionLayer;
|
||||||
|
CollisionMask = EnvironmentCollisionMask;
|
||||||
|
_usePhysicsMovement = true;
|
||||||
|
}
|
||||||
|
|
||||||
private void AdvanceTarget()
|
private void AdvanceTarget()
|
||||||
{
|
{
|
||||||
if (_patrolPoints.Count == 0 || _navigationAgent == null) return;
|
if (_patrolPoints.Count == 0 || _navigationAgent == null) return;
|
||||||
@ -146,26 +171,38 @@ public partial class CampusStudent : CharacterBody2D
|
|||||||
_patrolIndex = (_patrolIndex + 1) % _patrolPoints.Count;
|
_patrolIndex = (_patrolIndex + 1) % _patrolPoints.Count;
|
||||||
if (GlobalPosition.DistanceTo(target) > TargetReachDistance * 1.5f)
|
if (GlobalPosition.DistanceTo(target) > TargetReachDistance * 1.5f)
|
||||||
{
|
{
|
||||||
|
_currentTarget = target;
|
||||||
|
_hasTarget = true;
|
||||||
_navigationAgent.TargetPosition = target;
|
_navigationAgent.TargetPosition = target;
|
||||||
|
_stuckTimer = 0.0f;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_navigationAgent.TargetPosition = _patrolPoints[_patrolIndex];
|
_currentTarget = _patrolPoints[_patrolIndex];
|
||||||
|
_hasTarget = true;
|
||||||
|
_navigationAgent.TargetPosition = _currentTarget;
|
||||||
|
_stuckTimer = 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnVelocityComputed(Vector2 safeVelocity)
|
private void OnVelocityComputed(Vector2 safeVelocity)
|
||||||
{
|
{
|
||||||
// 使用安全速度移动,避免与其它角色硬碰硬卡住
|
// 使用安全速度移动,避免与其它角色硬碰硬卡住
|
||||||
Velocity = safeVelocity;
|
Velocity = ClampVelocityToNavMesh(safeVelocity);
|
||||||
MoveAndSlide();
|
ApplyMovement(_lastDelta);
|
||||||
UpdateFacingAnimation(Velocity);
|
UpdateFacingAnimation(Velocity);
|
||||||
UpdateStuckTimer(_lastDelta);
|
UpdateStuckTimer(_lastDelta);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateStuckTimer(double delta)
|
private void UpdateStuckTimer(double delta)
|
||||||
{
|
{
|
||||||
// 若短时间内几乎没有位移,则换一个目标点脱困
|
if (StuckRepathSeconds <= 0.0f || !_hasTarget)
|
||||||
|
{
|
||||||
|
_lastPosition = GlobalPosition;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 若短时间内几乎没有位移,则重新请求路径来脱困
|
||||||
if (GlobalPosition.DistanceTo(_lastPosition) <= StuckDistanceEpsilon)
|
if (GlobalPosition.DistanceTo(_lastPosition) <= StuckDistanceEpsilon)
|
||||||
_stuckTimer += (float)delta;
|
_stuckTimer += (float)delta;
|
||||||
else
|
else
|
||||||
@ -176,10 +213,52 @@ public partial class CampusStudent : CharacterBody2D
|
|||||||
if (_stuckTimer >= StuckRepathSeconds)
|
if (_stuckTimer >= StuckRepathSeconds)
|
||||||
{
|
{
|
||||||
_stuckTimer = 0.0f;
|
_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)
|
private void UpdateFacingAnimation(Vector2 velocity)
|
||||||
{
|
{
|
||||||
if (velocity.LengthSquared() < 0.01f)
|
if (velocity.LengthSquared() < 0.01f)
|
||||||
@ -243,4 +322,4 @@ public partial class CampusStudent : CharacterBody2D
|
|||||||
Left,
|
Left,
|
||||||
Right
|
Right
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user