diff --git a/Scripts/DSL/Nodes/MoveNode.cs b/Scripts/DSL/Nodes/MoveNode.cs index 951b3ee..abed15a 100644 --- a/Scripts/DSL/Nodes/MoveNode.cs +++ b/Scripts/DSL/Nodes/MoveNode.cs @@ -1,26 +1,45 @@ +using System; +using System.Collections.Generic; using Godot; public class MoveNode : ProgramNode { public Vector3 startPosition; public Vector3I targetPosition; + public List pathPoints; public MoveNode() { DisplayText = "Move"; } public override bool Execute(Robot robot, double delta) { - Vector3 worldTarget = GameData.map[targetPosition.Y].tiles[targetPosition.X, targetPosition.Z].Position; - GD.Print(startPosition + "/" + worldTarget); + pathPoints ??= [.. Pathfinding.GetPath(Pathfinding.GetClosestStartPoint(robot.Position), targetPosition)]; + startPosition = robot.Position; - Vector3 direction = worldTarget - startPosition; - robot.Translate(direction.Normalized() * (float)delta * GameData.robotSpeed); - float distance = direction.Length(); + Vector3 target = pathPoints[0] - startPosition; + float distance = target.Length(); + if (distance < 0.1f) { - robot.Position = worldTarget; - return true; + robot.Position = pathPoints[0]; + pathPoints.Remove(pathPoints[0]); + if (pathPoints.Count <= 0) + { + pathPoints = null; + return true; + } + + return false; } + + Vector3 direction = target / distance; + Vector3 lookDirection = new Vector3(direction.X, 0, direction.Z); + if (lookDirection.Length() > 0.1f) + { + robot.LookAt(robot.GlobalPosition + lookDirection, Vector3.Up); + } + robot.GlobalPosition += direction * (float)delta * GameData.robotSpeed; + return false; } diff --git a/Scripts/WorldGeneration/Layer.cs b/Scripts/WorldGeneration/Layer.cs index 4097e4c..5ada55d 100644 --- a/Scripts/WorldGeneration/Layer.cs +++ b/Scripts/WorldGeneration/Layer.cs @@ -198,9 +198,9 @@ public partial class Layer : Node3D ? new HashSet { currentTile.collapsedMesh } : new HashSet(currentTile.tileMeshes.Keys); - for (int i = 0; i < dirs.Length; i++) + for (int i = 0; i < offsets2D.Length; i++) { - Vector2I newPos = currentPos + offsets[i]; + Vector2I newPos = currentPos + offsets2D[i]; if (!InBounds(newPos, layerSize)) continue; Tile neighborTile = tiles[newPos.X, newPos.Y]; diff --git a/Scripts/WorldGeneration/Pathfinding.cs b/Scripts/WorldGeneration/Pathfinding.cs new file mode 100644 index 0000000..c139cbc --- /dev/null +++ b/Scripts/WorldGeneration/Pathfinding.cs @@ -0,0 +1,94 @@ +using System.Collections.Generic; +using System.Linq; +using Godot; + +public class Pathfinding +{ + private static AStar3D aStar = new AStar3D(); + private static Dictionary coordToId = new(); + private static Dictionary idToCoord = new(); + private static long nextId = 1; + + private static long GetOrCreateId(Vector3I coord) + { + if (coordToId.TryGetValue(coord, out long id)) + return id; + + id = nextId++; + coordToId[coord] = id; + idToCoord[id] = coord; + + return id; + } + + public static void BuildAStarGraph() + { + aStar.Clear(); + coordToId.Clear(); + idToCoord.Clear(); + nextId = 1; + + for (int y = 0; y < GameData.ruinSize; y++) + { + for (int x = 0; x < GameData.layerSize; x++) + { + for (int z = 0; z < GameData.layerSize; z++) + { + Vector3I coord = new Vector3I(x, y, z); + Tile tile = GameData.map[y].tiles[x, z]; + + if (tile == null || tile.collapsedMesh == null) + continue; + + long id = GetOrCreateId(coord); + + aStar.AddPoint(id, tile.Position); + } + } + } + + foreach (var kvp in coordToId) + { + Vector3I from = kvp.Key; + long fromId = kvp.Value; + + foreach (Vector3I offset in WFC.offsets3D) + { + var to = new Vector3I( + from.X + offset.X, + from.Y + offset.Y, + from.Z + offset.Z + ); + + if (!coordToId.ContainsKey(to)) + continue; + + if (!WFC.CanWalk3D(from, to)) + continue; + + long toId = coordToId[to]; + + if (!aStar.ArePointsConnected(fromId, toId)) + { + aStar.ConnectPoints(fromId, toId); + } + } + } + } + + public static List GetPath(Vector3I start, Vector3I end) + { + if (!coordToId.ContainsKey(start) || !coordToId.ContainsKey(end)) + return new List(); + + long startId = coordToId[start]; + long endId = coordToId[end]; + + return aStar.GetPointPath(startId, endId).ToList(); + } + + public static Vector3I GetClosestStartPoint(Vector3 robotPosition) + { + return idToCoord[aStar.GetClosestPoint(robotPosition)]; + } +} \ No newline at end of file diff --git a/Scripts/WorldGeneration/Pathfinding.cs.uid b/Scripts/WorldGeneration/Pathfinding.cs.uid new file mode 100644 index 0000000..d0efce6 --- /dev/null +++ b/Scripts/WorldGeneration/Pathfinding.cs.uid @@ -0,0 +1 @@ +uid://dwya0owm8kv03 diff --git a/Scripts/WorldGeneration/WFC.cs b/Scripts/WorldGeneration/WFC.cs index f7e315a..33448ae 100644 --- a/Scripts/WorldGeneration/WFC.cs +++ b/Scripts/WorldGeneration/WFC.cs @@ -18,7 +18,17 @@ public class WFC Down } - public static readonly Vector2I[] offsets = + public static readonly Direction[] dirs = + { + Direction.Backward, + Direction.Forward, + Direction.Left, + Direction.Right, + Direction.Up, + Direction.Down + }; + + public static readonly Vector2I[] offsets2D = { new Vector2I(0, -1), new Vector2I(0, 1), @@ -26,12 +36,14 @@ public class WFC new Vector2I(1, 0) }; - public static readonly Direction[] dirs = + public static readonly Vector3I[] offsets3D = { - Direction.Backward, - Direction.Forward, - Direction.Left, - Direction.Right + new Vector3I(0, 0, -1), + new Vector3I(0, 0, 1), + new Vector3I(-1, 0, 0), + new Vector3I(1, 0, 0), + new Vector3I(0, -1, 0), + new Vector3I(0, 1, 0) }; public static Dictionary> tileConnections = new Dictionary> @@ -148,6 +160,51 @@ public class WFC return CanConnect(fromTile.collapsedMesh, toTile.collapsedMesh, dir, true); } + public static bool CanWalk3D(Vector3I from, Vector3I to) + { + Tile fromTile = GameData.map[from.Y].tiles[from.X, from.Z]; + Tile toTile = GameData.map[to.Y].tiles[to.X, to.Z]; + + if (fromTile == null || toTile == null) + return false; + + if (from.Y != to.Y) + { + if (Math.Abs(from.Y - to.Y) != 1) + return false; + + if (from.Y > to.Y) + { + return toTile.collapsedMesh == "gate"; + } + else + { + return fromTile.collapsedMesh == "gate"; + } + } + + int dx = to.X - from.X; + int dz = to.Z - from.Z; + + if (Math.Abs(dx) + Math.Abs(dz) != 1) + return false; + + Direction dir; + + if (dx == 1) dir = Direction.Right; + else if (dx == -1) dir = Direction.Left; + else if (dz == 1) dir = Direction.Forward; + else if (dz == -1) dir = Direction.Backward; + else return false; + + return CanWalk( + GameData.map[from.Y].tiles, + new Vector2I(from.X, from.Z), + new Vector2I(to.X, to.Z), + dir + ); + } + public static bool IsMapConnected(Tile[,] layer, float accessibilityThreshhold) { bool result = false; @@ -165,9 +222,9 @@ public class WFC toCheck[index] = toCheck[^1]; toCheck.RemoveAt(toCheck.Count - 1); if (!visited.Add(position)) continue; - for (int i = 0; i < 4; i++) + for (int i = 0; i < offsets2D.Length; i++) { - var next = position + offsets[i]; + var next = position + offsets2D[i]; if (!InBounds(next, layer.GetLength(0))) continue; diff --git a/Scripts/WorldGeneration/World.cs b/Scripts/WorldGeneration/World.cs index c11944e..dfc0e0d 100644 --- a/Scripts/WorldGeneration/World.cs +++ b/Scripts/WorldGeneration/World.cs @@ -14,6 +14,7 @@ public partial class World : Node3D private Dictionary multiMeshes = new(); private Dictionary meshLibrary = new(); Layer layerNode; + Pathfinding pathfinding; private MultiMeshHandler multiMeshHandler; @@ -43,6 +44,8 @@ public partial class World : Node3D map = new Layer[ruinSize]; GenerateWorld(); + Pathfinding.BuildAStarGraph(); + HandleRenderData(BuildRenderData(0)); } @@ -106,9 +109,9 @@ public partial class World : Node3D } else { - layerNode.SetupLayer(layerSize, layer, tileMeshes, map[layer-1].gateCoordinate); + layerNode.SetupLayer(layerSize, layer, tileMeshes, map[layer - 1].gateCoordinate); } - + map[layer] = layerNode; } } @@ -150,31 +153,31 @@ public partial class World : Node3D int posX, posY; - while(currentLight < layerSize * 3) + while (currentLight < layerSize * 3) { posX = rand.Next(layerSize); posY = rand.Next(layerSize); //Skip already placed lights and skip junction and gate as they do not contain lights - if(layer.tiles[posX, posY].collapsedMesh == "junction" || layer.tiles[posX, posY].collapsedMesh == "gate") continue; - if(layer.tiles[posX, posY].containsLight) continue; + if (layer.tiles[posX, posY].collapsedMesh == "junction" || layer.tiles[posX, posY].collapsedMesh == "gate") continue; + if (layer.tiles[posX, posY].containsLight) continue; layer.tiles[posX, posY].containsLight = true; currentLight++; } - while(currentDecoration < layerSize) + while (currentDecoration < layerSize) { posX = rand.Next(layerSize); posY = rand.Next(layerSize); - if(layer.tiles[posX, posY].containsDecoration) continue; + if (layer.tiles[posX, posY].containsDecoration) continue; layer.tiles[posX, posY].containsDecoration = true; currentDecoration++; } - while(currentResource < layerSize) + while (currentResource < layerSize) { posX = rand.Next(layerSize); posY = rand.Next(layerSize); - if(layer.tiles[posX, posY].containsResource) continue; + if (layer.tiles[posX, posY].containsResource) continue; layer.tiles[posX, posY].containsResource = true; currentResource++; }