using System.Diagnostics; using System.Linq; using Godot; using Godot.Collections; public class TileInstanceManager : Spatial { // exports [Export] public NodePath World; [Export] public bool ShowHexTiles; [Export] public Vector2 ViewCenterPlaneCoord; // scene nodes public MultiMeshInstance TileMultiMeshInstance; // other members private readonly Array _sceneTileChunks = new(); private int _usedTileInstanceIndices; private ImageTexture _viewTileTypeTexture; private World _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, _usedTileInstanceIndices, ShowHexTiles); _usedTileInstanceIndices += sceneTileChunk.TileNodes.Count; 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]; sceneTileChunk.ChunkIndex = chunkIndex; removedChunks.RemoveAt(removedChunks.Count - 1); } else { sceneTileChunk = CreateSceneTileChunk(chunkIndex); AddChild(sceneTileChunk); } _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 Array _tileInstanceIndices = new(); private readonly HexGrid _hexGrid = new(); private readonly bool _showHexTiles; public readonly Array TileNodes = new(); private Vector2 _chunkIndex = Vector2.Inf; public SceneTileChunk(Vector2 chunkIndex, MultiMeshInstance multiMeshInstance, int tileInstanceIndexStart, bool showHexTiles) { _showHexTiles = showHexTiles; var tileInstanceIndexStart1 = tileInstanceIndexStart; 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(); tile3D.Cell.OffsetCoords = new Vector2(chunkIndex * global::World.ChunkSize + new Vector2(i, j)); 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; Debug.Assert(tileInstanceIndexStart1 + chunkTileCount <= _multiMeshInstance.Multimesh.InstanceCount); foreach (var i in Enumerable.Range(0, chunkTileCount)) _tileInstanceIndices.Add(tileInstanceIndexStart1 + i); // _multiMeshInstance.Multimesh.InstanceCount += chunkTileCount; _multiMeshInstance.Multimesh.VisibleInstanceCount = _multiMeshInstance.Multimesh.InstanceCount; 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 + " origin: " + chunkTransform.origin); 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)); var hexTransform = new Transform(tileOrientation, chunkTransform.origin + new Vector3(tilePlaneCoord.x, 0, tilePlaneCoord.y)); if (_showHexTiles) hexTransform = new Transform(tileOrientation.Scaled(Vector3.One * 0.95f), hexTransform.origin); _multiMeshInstance.Multimesh.SetInstanceTransform(_tileInstanceIndices[i], hexTransform); } } } } }