From e6522f2db91805c10562731b596c17fcf80f6d79 Mon Sep 17 00:00:00 2001 From: = <=> Date: Sun, 19 Apr 2026 13:03:56 +0200 Subject: [PATCH] Added WFC and respective parts (Tile, World, Layer, WFC, ResourceLoader) --- Game.tscn | 38 +++++ Prefabs/Layer.tscn | 6 + Scripts/Camera3d.cs | 40 +++++ Scripts/Camera3d.cs.uid | 1 + Scripts/Helpers/GameData.cs | 12 ++ Scripts/Helpers/GameData.cs.uid | 1 + Scripts/Helpers/Layer.cs | 231 ++++++++++++++++++++++++++ Scripts/Helpers/Layer.cs.uid | 1 + Scripts/Helpers/ResourceLoader.cs | 25 +++ Scripts/Helpers/ResourceLoader.cs.uid | 1 + Scripts/Helpers/WFC.cs | 207 +++++++++++++++++++++++ Scripts/Helpers/WFC.cs.uid | 1 + Scripts/Tile.cs | 73 ++++++++ Scripts/Tile.cs.uid | 1 + Scripts/World.cs | 110 ++++++++++++ Scripts/World.cs.uid | 1 + 16 files changed, 749 insertions(+) create mode 100644 Game.tscn create mode 100644 Prefabs/Layer.tscn create mode 100644 Scripts/Camera3d.cs create mode 100644 Scripts/Camera3d.cs.uid create mode 100644 Scripts/Helpers/GameData.cs create mode 100644 Scripts/Helpers/GameData.cs.uid create mode 100644 Scripts/Helpers/Layer.cs create mode 100644 Scripts/Helpers/Layer.cs.uid create mode 100644 Scripts/Helpers/ResourceLoader.cs create mode 100644 Scripts/Helpers/ResourceLoader.cs.uid create mode 100644 Scripts/Helpers/WFC.cs create mode 100644 Scripts/Helpers/WFC.cs.uid create mode 100644 Scripts/Tile.cs create mode 100644 Scripts/Tile.cs.uid create mode 100644 Scripts/World.cs create mode 100644 Scripts/World.cs.uid diff --git a/Game.tscn b/Game.tscn new file mode 100644 index 0000000..f8cea81 --- /dev/null +++ b/Game.tscn @@ -0,0 +1,38 @@ +[gd_scene format=3 uid="uid://cgsmfi2s51cbd"] + +[ext_resource type="Script" uid="uid://br2udyi6t8yvf" path="res://Scripts/World.cs" id="1_xkndl"] +[ext_resource type="Script" uid="uid://dqrdb3bvws6b6" path="res://Scripts/SteamworksHandler.cs" id="2_xkndl"] +[ext_resource type="Script" uid="uid://c7khr6oist3ku" path="res://Scripts/Camera3d.cs" id="3_u44n3"] + +[sub_resource type="CompressedTexture2D" id="CompressedTexture2D_u44n3"] +load_path = "res://.godot/imported/Background.png-e8880a50f4091751eaed728281d3c21e.s3tc.ctex" + +[sub_resource type="PanoramaSkyMaterial" id="PanoramaSkyMaterial_u44n3"] +panorama = SubResource("CompressedTexture2D_u44n3") + +[sub_resource type="Sky" id="Sky_u44n3"] +sky_material = SubResource("PanoramaSkyMaterial_u44n3") + +[sub_resource type="Environment" id="Environment_sb48q"] +background_mode = 1 +background_color = Color(0.27141052, 0.1874483, 0.13788113, 1) +sky = SubResource("Sky_u44n3") + +[node name="Main" type="Node3D" unique_id=234207355] + +[node name="World" type="Node3D" parent="." unique_id=770208789] +script = ExtResource("1_xkndl") + +[node name="DirectionalLight3D" type="DirectionalLight3D" parent="World" unique_id=1521742758] +transform = Transform3D(1, 0, 0, 0, 0.89061797, 0.45475236, 0, -0.45475236, 0.89061797, 0, 10.721322, 3.5568523) + +[node name="SteamworksHandler" type="Node" parent="." unique_id=1183440473] +script = ExtResource("2_xkndl") + +[node name="Camera3D" type="Camera3D" parent="." unique_id=161504606] +transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 30, 20, 30) +current = true +script = ExtResource("3_u44n3") + +[node name="WorldEnvironment" type="WorldEnvironment" parent="." unique_id=377970686] +environment = SubResource("Environment_sb48q") diff --git a/Prefabs/Layer.tscn b/Prefabs/Layer.tscn new file mode 100644 index 0000000..b790d97 --- /dev/null +++ b/Prefabs/Layer.tscn @@ -0,0 +1,6 @@ +[gd_scene format=3 uid="uid://ck10wk10me3tx"] + +[ext_resource type="Script" uid="uid://dkg0vq75koeig" path="res://Scripts/Helpers/Layer.cs" id="1_trar1"] + +[node name="Node3D" type="Node3D" unique_id=724642284] +script = ExtResource("1_trar1") diff --git a/Scripts/Camera3d.cs b/Scripts/Camera3d.cs new file mode 100644 index 0000000..fc99e7a --- /dev/null +++ b/Scripts/Camera3d.cs @@ -0,0 +1,40 @@ +using Godot; +using static GameData; + +public partial class Camera3d : Camera3D +{ + [Export] public float Speed = 7.5f; + [Export] public float MouseSensitivity = 0.2f; + [Export] public float ScrollStrength = 5.0f; + + private Vector2 _mouseDelta; + + public override void _Process(double delta) + { + float d = (float)delta; + + var rotation = RotationDegrees; + rotation.X = Mathf.Clamp(rotation.X, -90f, 90f); + RotationDegrees = rotation; + + _mouseDelta = Vector2.Zero; + + Vector3 direction = Vector3.Zero; + if (Input.IsActionPressed("move_forward")&& Position.Z > 0) direction += Transform.Basis.Z; + if (Input.IsActionPressed("move_backward")&& Position.Z < layerSize * 4) direction -= Transform.Basis.Z; + if (Input.IsActionPressed("move_left") && Position.X > 0) direction -= Transform.Basis.X; + if (Input.IsActionPressed("move_right") && Position.X < layerSize * 4) direction += Transform.Basis.X; + + if (direction != Vector3.Zero) + { + direction = direction.Normalized() * (Speed + 3 * Mathf.Log(Position.Y) + 1) * d; + Translate(direction); + } + + if (Input.IsActionJustPressed("zoom_in") && Position.Y > 10) + Translate(Transform.Basis.Y * ScrollStrength); + + if (Input.IsActionJustPressed("zoom_out") && Position.Y < 50) + Translate(-Transform.Basis.Y * ScrollStrength); + } +} diff --git a/Scripts/Camera3d.cs.uid b/Scripts/Camera3d.cs.uid new file mode 100644 index 0000000..f28011f --- /dev/null +++ b/Scripts/Camera3d.cs.uid @@ -0,0 +1 @@ +uid://c7khr6oist3ku diff --git a/Scripts/Helpers/GameData.cs b/Scripts/Helpers/GameData.cs new file mode 100644 index 0000000..1d4eff2 --- /dev/null +++ b/Scripts/Helpers/GameData.cs @@ -0,0 +1,12 @@ +public partial class GameData +{ + //Amount of layers generated + public static int ruinSize = 10; + //Width+Height of layers (+1 for the border) + public static int layerSize = 20 + 1; + //Current layer the player wants to see + public static int currentLayer = 0; + //The layer that is currently visible + public static int visibleLayer = 0; + +} diff --git a/Scripts/Helpers/GameData.cs.uid b/Scripts/Helpers/GameData.cs.uid new file mode 100644 index 0000000..43d6f13 --- /dev/null +++ b/Scripts/Helpers/GameData.cs.uid @@ -0,0 +1 @@ +uid://dgebbx4nkktu6 diff --git a/Scripts/Helpers/Layer.cs b/Scripts/Helpers/Layer.cs new file mode 100644 index 0000000..e7aa9a9 --- /dev/null +++ b/Scripts/Helpers/Layer.cs @@ -0,0 +1,231 @@ +using Godot; +using System; +using System.Collections.Generic; +using System.Linq; +using static WFC; +public partial class Layer : Node3D +{ + public Tile[,] tiles; + int layerSize; + Tile tile; + int level; + bool updateFailed = false; + + // Called when the node enters the scene tree for the first time. + public override void _Ready() + { + + } + + // Called every frame. 'delta' is the elapsed time since the previous frame. + public override void _Process(double delta) + { + } + + public void SetupLayer(int layerSize, int level, Dictionary tileMeshes) + { + this.layerSize = layerSize; + this.level = level; + tiles = new Tile[layerSize, layerSize]; + GenerateBaseStructure(tileMeshes); + int safetyCounter = 0; + while (true) + { + if (GenerateLayer()) + { + GD.Print("Layer generated"); + break; + } + else + { + GD.PrintErr("Layer failed... trying again"); + } + ResetLayer(tileMeshes); + safetyCounter++; + if (safetyCounter > 1000) break; + } + } + + private void GenerateBaseStructure(Dictionary tileMeshes) + { + Vector3 position; + float offsetX; + float offsetY = level * 4 * -1; + float offsetZ; + for (int x = 0; x < layerSize; x++) + { + offsetX = x * 4; + for (int y = 0; y < layerSize; y++) + { + offsetZ = y * 4; + position = new Vector3(offsetX, offsetY, offsetZ); + tile = new Tile(); + tile.SetMeshes(tileMeshes); + tile.Position = position; + tile.GridPosition = new Vector2I(x, y); + tiles[x, y] = tile; + } + } + } + + private void ResetLayer(Dictionary tileMeshes) + { + for (int x = 0; x < layerSize; x++) + { + for (int y = 0; y < layerSize; y++) + { + tiles[x, y].Reset(tileMeshes); + } + } + } + + private bool GenerateBorder() + { + for (int x = 0; x < layerSize; x++) + { + for (int y = 0; y < layerSize; y++) + { + if (x == 0 || y == 0 || x == layerSize - 1 || y == layerSize - 1) + { + var tile = tiles[x, y]; + + string result = tile.Collapse("border"); + if (result == "ERR") + return false; + + NewPropagate(new Vector2I(x, y)); + } + } + } + return true; + } + + public bool GenerateLayer() + { + bool result = true; + int safetyCounter = 0; + if (!GenerateBorder()) + { + return false; + } + + Vector2I position = GetSmallestPossibilities(); + while (true) + { + string keyword = ""; + if (position.X == 0 || position.X == layerSize - 1 || position.Y == 0 || position.Y == layerSize - 1) + { + keyword = tiles[position.X, position.Y].Collapse("border"); + } + else + { + keyword = tiles[position.X, position.Y].Collapse(""); + } + if (keyword == "ERR") + { + GD.Print("Error in WFC during collapse!"); + return false; + } + if (keyword != "") + { + NewPropagate(position); + if (updateFailed) break; + position = GetSmallestPossibilities(); + if (position == new Vector2(-100, -100)) + { + break; + } + continue; + } + safetyCounter++; + if (safetyCounter == layerSize * layerSize) + { + GD.Print("Error in WFC, overflow!"); + return false; + } + } + if (updateFailed) return false; + //Spawn is a tile border, redo world generation + if (tiles[1, 1].collapsedMesh == "border") return false; + //Player has over 80% of tiles available without destroying walls => Results in about 95% success rate + //Not necessarily needed but improves the overall generation percentage at a low resource cost + if (!WFC.IsMapConnected(tiles, 0.8f)) return false; + return result; + } + + private void NewPropagate(Vector2I startPos) + { + Queue queue = new Queue(); + queue.Enqueue(startPos); + + while (queue.Count > 0) + { + Vector2I currentPos = queue.Dequeue(); + Tile currentTile = tiles[currentPos.X, currentPos.Y]; + + // Use CURRENT state of tile + var currentPossibilities = currentTile.collapsedMesh != null + ? new HashSet { currentTile.collapsedMesh } + : new HashSet(currentTile.tileMeshes.Keys); + + for (int i = 0; i < dirs.Length; i++) + { + Vector2I newPos = currentPos + offsets[i]; + if (!InBounds(newPos, layerSize, true)) continue; + + Tile neighborTile = tiles[newPos.X, newPos.Y]; + + HashSet allowed = new HashSet(); + + foreach (string neighborOption in neighborTile.tileMeshes.Keys) + { + foreach (string item in currentPossibilities) + { + if (WFC.CanConnect(item, neighborOption, dirs[i], false)) + { + allowed.Add(neighborOption); + break; + } + } + } + + int updateCount = neighborTile.Propagate(allowed); + + if (updateCount == int.MaxValue) + { + GD.Print("WFC Error! No meshes left"); + updateFailed = true; + return; + } + + // ONLY enqueue if something changed + if (updateCount > 0) + { + queue.Enqueue(newPos); + } + } + } + } + + + private Vector2I GetSmallestPossibilities() + { + Vector2I result = new Vector2I(-100, -100); + int lowest = 100; + int current; + for (int x = 0; x < layerSize; x++) + { + for (int y = 0; y < layerSize; y++) + { + if (tiles[x, y].collapsedMesh != null) continue; + current = tiles[x, y].tileMeshes.Count; + if (current < lowest) + { + result = new Vector2I(x, y); + lowest = current; + } + } + } + return result; + } +} diff --git a/Scripts/Helpers/Layer.cs.uid b/Scripts/Helpers/Layer.cs.uid new file mode 100644 index 0000000..e183098 --- /dev/null +++ b/Scripts/Helpers/Layer.cs.uid @@ -0,0 +1 @@ +uid://dkg0vq75koeig diff --git a/Scripts/Helpers/ResourceLoader.cs b/Scripts/Helpers/ResourceLoader.cs new file mode 100644 index 0000000..802bc76 --- /dev/null +++ b/Scripts/Helpers/ResourceLoader.cs @@ -0,0 +1,25 @@ +using Godot; +using System; +using System.Collections.Generic; +using System.Linq; +public partial class ResourceLoader +{ + + public static PackedScene LoadLayerPrefab() + { + return GD.Load($"res://Prefabs/Layer.tscn"); + } + + public static Dictionary LoadTiles() + { + Dictionary tileMeshes = new Dictionary(); + PackedScene tileCollection = GD.Load($"res://Assets/Objects/Tiles.glb"); + Node root = tileCollection.Instantiate(); + foreach (MeshInstance3D child in root.GetChildren()) + { + tileMeshes.Add(child.Name.ToString().ToLower(), child); + } + + return tileMeshes; + } +} diff --git a/Scripts/Helpers/ResourceLoader.cs.uid b/Scripts/Helpers/ResourceLoader.cs.uid new file mode 100644 index 0000000..fba65b6 --- /dev/null +++ b/Scripts/Helpers/ResourceLoader.cs.uid @@ -0,0 +1 @@ +uid://cdhftg7wcgyis diff --git a/Scripts/Helpers/WFC.cs b/Scripts/Helpers/WFC.cs new file mode 100644 index 0000000..a15f6a3 --- /dev/null +++ b/Scripts/Helpers/WFC.cs @@ -0,0 +1,207 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Godot; + +public class WFC +{ + public static Dictionary>> adjacency = new Dictionary>>(); + public static Random rand = new Random(); + public enum Direction + { + Up, + Down, + Left, + Right, + None + } + + public static readonly Vector2I[] offsets = + { + new Vector2I(0, -1), + new Vector2I(0, 1), + new Vector2I(-1, 0), + new Vector2I(1, 0) + }; + + public static readonly Direction[] dirs = + { + Direction.Up, + Direction.Down, + Direction.Left, + Direction.Right + }; + + public static Dictionary> tileConnections = new Dictionary> + { + ["t_right"] = new() { Direction.Up, Direction.Down, Direction.Right }, + ["t_left"] = new() { Direction.Up, Direction.Down, Direction.Left }, + ["t_up"] = new() { Direction.Left, Direction.Right, Direction.Up }, + ["t_down"] = new() { Direction.Left, Direction.Right, Direction.Down }, + + ["end_up"] = new() { Direction.Up }, + ["end_down"] = new() { Direction.Down }, + ["end_left"] = new() { Direction.Left }, + ["end_right"] = new() { Direction.Right }, + + ["straight_left_right"] = new() { Direction.Left, Direction.Right }, + ["straight_up_down"] = new() { Direction.Up, Direction.Down }, + + ["corner_up_left"] = new() { Direction.Up, Direction.Left }, + ["corner_up_right"] = new() { Direction.Up, Direction.Right }, + ["corner_down_left"] = new() { Direction.Down, Direction.Left }, + ["corner_down_right"] = new() { Direction.Down, Direction.Right }, + + ["junction"] = new() { Direction.Up, Direction.Down, Direction.Left, Direction.Right }, + + ["border"] = new() { } + }; + + public static Dictionary weights = new() + { + ["junction"] = 5f, + ["t_up"] = 3f, + ["t_down"] = 3f, + ["t_left"] = 3f, + ["t_right"] = 3f, + + ["straight_left_right"] = 2f, + ["straight_up_down"] = 2f, + + ["corner_up_left"] = 0.5f, + ["corner_up_right"] = 0.5f, + ["corner_down_left"] = 0.5f, + ["corner_down_right"] = 0.5f, + + ["end_up"] = 0.2f, + ["end_down"] = 0.1f, + ["end_left"] = 0.2f, + ["end_right"] = 0.3f, + + ["border"] = 0.0f + }; + + public static Direction Opposite(Direction dir) + { + return dir switch + { + Direction.Up => Direction.Down, + Direction.Down => Direction.Up, + Direction.Left => Direction.Right, + Direction.Right => Direction.Left, + _ => dir + }; + } + + public static bool CanConnect(string a, string b, Direction direction, bool checkWalking) + { + var aDirs = tileConnections[a]; + var bDirs = tileConnections[b]; + + bool aOpen = aDirs.Contains(direction); + bool bOpen = bDirs.Contains(Opposite(direction)); + + if (checkWalking) return aOpen && bOpen; + return aOpen == bOpen; + } + + + + public static void FillAdjacencies() + { + foreach (var tile in tileConnections.Keys) + { + adjacency[tile] = new Dictionary>(); + + foreach (Direction dir in Enum.GetValues(typeof(Direction))) + { + var valid = new List(); + + foreach (var other in tileConnections.Keys) + { + if (CanConnect(tile, other, dir, false)) + valid.Add(other); + } + + adjacency[tile][dir] = valid; + } + } + } + + public static bool IsWalkable(Tile tile) + { + var dirs = tileConnections[tile.collapsedMesh]; + return dirs.Count > 0 && !dirs.Contains(Direction.None); + } + + public static bool CanWalk(Tile[,] layer, Vector2I from, Vector2I to, Direction dir) + { + var fromTile = layer[from.X, from.Y]; + var toTile = layer[to.X, to.Y]; + + if (!IsWalkable(toTile)) + return false; + + return CanConnect(fromTile.collapsedMesh, toTile.collapsedMesh, dir, true); + } + + public static bool IsMapConnected(Tile[,] layer, float accessibilityThreshhold) + { + bool result = false; + HashSet visited = new HashSet(); + List toCheck = new List(); + Vector2I position; + toCheck.Add(new Vector2I(1, 1)); + + int safetyCounter = 0; + while (true) + { + if (toCheck.Count <= 0) break; + int index = rand.Next(toCheck.Count); + position = toCheck[index]; + toCheck[index] = toCheck[^1]; + toCheck.RemoveAt(toCheck.Count - 1); + if (!visited.Add(position)) continue; + for (int i = 0; i < 4; i++) + { + var next = position + offsets[i]; + + if (!InBounds(next, layer.GetLength(0), false)) + continue; + + if (CanWalk(layer, position, next, dirs[i])) + { + toCheck.Add(next); + } + } + safetyCounter++; + if (safetyCounter > layer.Length * 2) break; + if (visited.Count >= Math.Pow(layer.GetLength(0) - 1, 2) * accessibilityThreshhold) + { + result = true; + break; + } + + } + if (safetyCounter > layer.Length * 2) GD.PrintErr("Loop too long"); + return result; + } + + public static bool InBounds(Vector2I pos, int layerSize, bool includeBorder = true) + { + if (includeBorder) + { + return pos.X >= 0 && + pos.Y >= 0 && + pos.X < layerSize && + pos.Y < layerSize; + } + else + { + return pos.X > 0 && + pos.Y > 0 && + pos.X < layerSize - 1 && + pos.Y < layerSize - 1; + } + } +} diff --git a/Scripts/Helpers/WFC.cs.uid b/Scripts/Helpers/WFC.cs.uid new file mode 100644 index 0000000..e14b83c --- /dev/null +++ b/Scripts/Helpers/WFC.cs.uid @@ -0,0 +1 @@ +uid://d3jw4gk5f8hhg diff --git a/Scripts/Tile.cs b/Scripts/Tile.cs new file mode 100644 index 0000000..9c2a245 --- /dev/null +++ b/Scripts/Tile.cs @@ -0,0 +1,73 @@ +using Godot; +using System; +using System.Collections.Generic; +using System.Linq; + +public partial class Tile +{ + public Dictionary tileMeshes; + public string collapsedMesh; + Random rand = new Random(); + public Vector3 Position; + public Vector2I GridPosition; + + public void SetMeshes(Dictionary tileMeshes) + { + this.tileMeshes = new Dictionary(tileMeshes); + } + + public string Collapse(string tile) + { + if (collapsedMesh != null) return ""; + if (tileMeshes.Keys.Count <= 0) return "ERR"; + collapsedMesh = (tile.Length > 0) ? tile : ChooseWeighted(); + tileMeshes.Clear(); + return collapsedMesh; + } + + private string ChooseWeighted() + { + float totalWeight = 0f; + + foreach (string tile in tileMeshes.Keys) + { + totalWeight += WFC.weights[tile]; + } + + float r = (float)(rand.NextDouble() * totalWeight); + + float cumulative = 0f; + + foreach (string tile in tileMeshes.Keys) + { + cumulative += WFC.weights[tile]; + + if (r <= cumulative) + return tile; + } + + return "junction"; + } + + public int Propagate(HashSet possibleKeys) + { + int amountRemoved = 0; + if (collapsedMesh != null) return 0; + foreach (string key in tileMeshes.Keys.ToList()) + { + if (!possibleKeys.Contains(key)) + { + tileMeshes.Remove(key); + amountRemoved++; + } + } + if (tileMeshes.Count == 0) return int.MaxValue; + return amountRemoved; + } + + public void Reset(Dictionary tileMeshes) + { + collapsedMesh = null; + SetMeshes(tileMeshes); + } +} diff --git a/Scripts/Tile.cs.uid b/Scripts/Tile.cs.uid new file mode 100644 index 0000000..f194d58 --- /dev/null +++ b/Scripts/Tile.cs.uid @@ -0,0 +1 @@ +uid://crimbrc78gxkc diff --git a/Scripts/World.cs b/Scripts/World.cs new file mode 100644 index 0000000..859f4b7 --- /dev/null +++ b/Scripts/World.cs @@ -0,0 +1,110 @@ +using Godot; +using System; +using System.Collections.Generic; +using System.Linq; +using static GameData; + +public partial class World : Node3D +{ + public Dictionary tileMeshes; + PackedScene layerPrefab = ResourceLoader.LoadLayerPrefab(); + private Dictionary multiMeshes = new(); + private Dictionary meshLibrary = new(); + Layer[] map; + Layer layerNode; + // Called when the node enters the scene tree for the first time. + public override void _Ready() + { + WFC.FillAdjacencies(); + tileMeshes = ResourceLoader.LoadTiles(); + foreach (var kvp in tileMeshes) + { + var temp = kvp.Value; + meshLibrary[kvp.Key] = temp.Mesh; + temp.QueueFree(); + } + foreach (var kvp in meshLibrary) + { + var mm = new MultiMesh(); + mm.Mesh = kvp.Value; + mm.TransformFormat = MultiMesh.TransformFormatEnum.Transform3D; + + var instance = new MultiMeshInstance3D(); + instance.Multimesh = mm; + + AddChild(instance); + multiMeshes[kvp.Key] = instance; + } + map = new Layer[ruinSize]; + GenerateWorld(); + GD.Print("World generated"); + BuildMeshesForLayer(0); + } + + // Called every frame. 'delta' is the elapsed time since the previous frame. + public override void _Process(double delta) + { + if (Input.IsActionJustPressed("layer_up") && currentLayer > 0) currentLayer--; + if (Input.IsActionJustPressed("layer_down") && currentLayer < ruinSize - 1) currentLayer++; + if (currentLayer != visibleLayer) + { + BuildMeshesForLayer(currentLayer); + visibleLayer = currentLayer; + } + } + + private void GenerateWorld() + { + DateTime now = DateTime.Now; + for (int layer = 0; layer < ruinSize; layer++) + { + layerNode = layerPrefab.Instantiate(); + AddChild(layerNode); + layerNode.SetupLayer(layerSize, layer, tileMeshes); + map[layer] = layerNode; + } + GD.Print("Time for map generation: " + (DateTime.Now - now).Seconds); + } + + private void BuildMeshesForLayer(int layerIndex) + { + foreach (MultiMeshInstance3D mm in multiMeshes.Values) + { + mm.Multimesh.InstanceCount = 0; + } + + Layer layer = map[layerIndex]; + + Dictionary> batches = new(); + + for (int x = 0; x < layerSize; x++) + { + for (int y = 0; y < layerSize; y++) + { + Tile tile = layer.tiles[x, y]; + string key = tile.collapsedMesh; + + if (!batches.ContainsKey(key)) + batches[key] = new List(); + + batches[key].Add(new Transform3D( + Basis.Identity, + tile.Position + )); + } + } + + foreach (var kvp in batches) + { + MultiMesh mm = multiMeshes[kvp.Key].Multimesh; + List list = kvp.Value; + + mm.InstanceCount = list.Count; + + for (int i = 0; i < list.Count; i++) + { + mm.SetInstanceTransform(i, list[i]); + } + } + } +} diff --git a/Scripts/World.cs.uid b/Scripts/World.cs.uid new file mode 100644 index 0000000..5fae4cb --- /dev/null +++ b/Scripts/World.cs.uid @@ -0,0 +1 @@ +uid://br2udyi6t8yvf