using System.Diagnostics; using System.Linq; using Godot; using Godot.Collections; public class TileInstanceManager : Spatial { private readonly Array _sceneTileChunks = new(); private MultiMeshInstance _tileMultiMeshInstance; private ImageTexture _viewTileTypeTexture; private World _world; // other members [Export] public bool ShowHexTiles; [Export] public Vector2 ViewCenterPlaneCoord; // ui elements // scene nodes // resources // exports [Export] public NodePath World; // Called when the node enters the scene tree for the first time. public override void _Ready() { _world = GetNode(World); _world.Connect("OnTilesChanged", this, nameof(HandleWorldTileChange)); _tileMultiMeshInstance = (MultiMeshInstance)FindNode("TileMultiMeshInstance"); Debug.Assert(_tileMultiMeshInstance != null); } public override void _Process(float delta) { } private SceneTileChunk CreateSceneTileChunk(Vector2 chunkIndex) { var sceneTileChunk = new SceneTileChunk(chunkIndex, _tileMultiMeshInstance); foreach (var hexTile3D in sceneTileChunk.TileNodes) { hexTile3D.Connect("TileClicked", this, nameof(OnTileClicked)); hexTile3D.Connect("TileHovered", this, nameof(OnTileHovered)); } return sceneTileChunk; } private SceneTileChunk FindSceneTileChunkAtIndex(Vector2 chunkIndex) { foreach (Spatial child in GetChildren()) { var sceneTileChunk = child as SceneTileChunk; if (sceneTileChunk == null) continue; if (sceneTileChunk.ChunkIndex == chunkIndex) return sceneTileChunk; } return null; } private void HandleWorldTileChange(Array removedChunkIndices, Array addedChunkIndices) { Array removedChunks = new(); foreach (var chunkIndex in removedChunkIndices) { var chunk = FindSceneTileChunkAtIndex(chunkIndex); if (chunk != null) removedChunks.Add(chunk); } foreach (var chunkIndex in addedChunkIndices) { SceneTileChunk sceneTileChunk = null; if (removedChunks.Count > 0) { sceneTileChunk = removedChunks[^1]; removedChunks.RemoveAt(removedChunks.Count - 1); GD.Print("Reused SceneTileChunk"); } else { sceneTileChunk = CreateSceneTileChunk(chunkIndex); AddChild(sceneTileChunk); GD.Print("Created SceneTileChunk"); } sceneTileChunk.ChunkIndex = chunkIndex; // if (ShowHexTiles) // foreach (var tile3D in sceneTileChunk.TileNodes) // tile3D.Transform = new Transform(Basis.Identity.Scaled(Vector3.One * 0.95f), // tile3D.Transform.origin); _sceneTileChunks.Add(sceneTileChunk); } GD.Print("Removed Chunks " + removedChunkIndices.Count); GD.Print("Added Chunks " + addedChunkIndices.Count); GD.Print("Removed chunk count: " + removedChunks.Count); } public void OnTileClicked(HexTile3D tile) { EmitSignal("TileClicked", tile); } public void OnTileHovered(HexTile3D tile) { EmitSignal("TileHovered", tile); } // signals [Signal] private delegate void TileClicked(HexTile3D tile3d); [Signal] private delegate void TileHovered(HexTile3D tile3d); private class SceneTileChunk : Spatial { private readonly PackedScene _hexTile3DScene = GD.Load("res://scenes/HexTile3D.tscn"); private readonly MultiMeshInstance _multiMeshInstance; private readonly HexGrid HexGrid = new(); private readonly Array TileInstanceIndices = new(); public readonly Array TileNodes = new(); private Vector2 _chunkIndex = Vector2.Inf; public SceneTileChunk(Vector2 chunkIndex, MultiMeshInstance multiMeshInstance) { var chunkSize = global::World.ChunkSize; foreach (var i in Enumerable.Range(0, chunkSize)) foreach (var j in Enumerable.Range(0, chunkSize)) { var tile3D = (HexTile3D)_hexTile3DScene.Instance(); var tileTransform = Transform.Identity; var centerPlaneCoord = HexGrid.GetHexCenterFromOffset(new Vector2(i, j)); tileTransform.origin = new Vector3(centerPlaneCoord.x, 0, centerPlaneCoord.y); tile3D.Transform = tileTransform; TileNodes.Add(tile3D); AddChild(tile3D); } _multiMeshInstance = multiMeshInstance; var chunkTileCount = global::World.ChunkSize * global::World.ChunkSize; foreach (var i in Enumerable.Range(0, chunkTileCount)) TileInstanceIndices.Add(_multiMeshInstance.Multimesh.InstanceCount + i); _multiMeshInstance.Multimesh.InstanceCount += chunkTileCount; _multiMeshInstance.Multimesh.VisibleInstanceCount = _multiMeshInstance.Multimesh.InstanceCount; GD.Print("VisibleInstanceCount = " + _multiMeshInstance.Multimesh.VisibleInstanceCount); ChunkIndex = chunkIndex; } public Vector2 ChunkIndex { get => _chunkIndex; set { var chunkTransform = Transform.Identity; var chunkOriginPlaneCoord = HexGrid.GetHexCenterFromOffset(value * global::World.ChunkSize); chunkTransform.origin = new Vector3(chunkOriginPlaneCoord.x, 0, chunkOriginPlaneCoord.y); Transform = chunkTransform; _chunkIndex = value; var tileOrientation = new Basis(Vector3.Up, 90f * Mathf.Pi / 180f); GD.Print("Updating transforms for instances of chunk " + value); foreach (var i in Enumerable.Range(0, TileInstanceIndices.Count)) { var column = i % global::World.ChunkSize; var row = i / global::World.ChunkSize; var tilePlaneCoord = HexGrid.GetHexCenterFromOffset(new Vector2(column, row)); _multiMeshInstance.Multimesh.SetInstanceTransform(TileInstanceIndices[i], new Transform(tileOrientation, chunkTransform.origin + new Vector3(tilePlaneCoord.x, 0, tilePlaneCoord.y))); } } } } }