using System.Linq; using Godot; using Godot.Collections; using Vector2 = Godot.Vector2; using Vector3 = Godot.Vector3; public class WorldView : Spatial { // ui elements // scene nodes // resources // exports [Export] public NodePath World; [Export] public Vector2 ViewCenterPlaneCoord; [Export] public bool ShowHexTiles = false; // signals [Signal] delegate void TileClicked(HexTile3D tile3d); [Signal] delegate void TileHovered(HexTile3D tile3d); [Signal] delegate void OnWorldViewTileTypeImageChanged(Image viewTileTypeImage); // other members public Vector2 WorldTextureCoordinateOffset = Vector2.Zero; private World _world; private ImageTexture _viewTileTypeTexture; private Image _viewTileTypeImage = new(); private class SceneTileChunk : Spatial { private Vector2 _chunkIndex = Vector2.Inf; public Vector2 ChunkIndex { get { return _chunkIndex; } set { Transform chunkTransform = Transform.Identity; Vector2 chunkOriginPlaneCoord = HexGrid.GetHexCenterFromOffset(value * global::World.ChunkSize); chunkTransform.origin = new Vector3(chunkOriginPlaneCoord.x, 0, chunkOriginPlaneCoord.y); Transform = chunkTransform; _chunkIndex = value; } } public Array TileNodes = new(); private PackedScene _hexTile3DScene = GD.Load("res://scenes/HexTile3D.tscn"); private HexGrid HexGrid = new(); public SceneTileChunk(Vector2 chunkIndex, int size) { foreach (int i in Enumerable.Range(0, size)) { foreach (int j in Enumerable.Range(0, size)) { HexTile3D tile3D = (HexTile3D)_hexTile3DScene.Instance(); Transform tileTransform = Transform.Identity; Vector2 centerPlaneCoord = HexGrid.GetHexCenterFromOffset(new Vector2(i, j)); tileTransform.origin = new Vector3(centerPlaneCoord.x, 0, centerPlaneCoord.y); tile3D.Transform = tileTransform; TileNodes.Add(tile3D); AddChild(tile3D); } } ChunkIndex = chunkIndex; } } private Array _sceneTileChunks = new Array(); // 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)); } public override void _Process(float delta) { } SceneTileChunk CreateSceneTileChunk(Vector2 chunkIndex) { SceneTileChunk sceneTileChunk = new SceneTileChunk(chunkIndex, global::World.ChunkSize); foreach (HexTile3D hexTile3D in sceneTileChunk.TileNodes) { hexTile3D.Connect("TileClicked", this, nameof(OnTileClicked)); hexTile3D.Connect("TileHovered", this, nameof(OnTileHovered)); } return sceneTileChunk; } SceneTileChunk RemoveChunkFromScene(Vector2 chunkIndex) { foreach (Spatial child in GetChildren()) { SceneTileChunk sceneTileChunk = child as SceneTileChunk; if (sceneTileChunk == null) { RemoveChild(child); continue; } if (sceneTileChunk.ChunkIndex == chunkIndex) { return sceneTileChunk; } } return null; } private void UpdateWorldViewTexture() { int worldChunkSize = global::World.ChunkSize; int numWorldChunkRows = global::World.NumChunkRows; int numWorldChunkColumns = global::World.NumChunkColumns; _viewTileTypeImage.Create(worldChunkSize * numWorldChunkColumns, worldChunkSize * numWorldChunkRows, false, Image.Format.Rgba8); Vector2 chunkIndexSouthWest = Vector2.Inf; Vector2 chunkIndexNorthEast = -Vector2.Inf; foreach (SceneTileChunk chunk in _sceneTileChunks) { WorldChunk worldChunk = _world.GetOrCreateWorldChunk((int) chunk.ChunkIndex.x, (int)chunk.ChunkIndex.y, Colors.White); if (chunk.ChunkIndex.x <= chunkIndexSouthWest.x && chunk.ChunkIndex.y <= chunkIndexSouthWest.y) { chunkIndexSouthWest = chunk.ChunkIndex; } else if (chunk.ChunkIndex.x >= chunkIndexNorthEast.x && chunk.ChunkIndex.y >= chunkIndexNorthEast.y) { chunkIndexNorthEast = chunk.ChunkIndex; } _viewTileTypeImage.BlendRect( worldChunk.TileTypeOffscreenViewport.GetTexture().GetData(), new Rect2(Vector2.Zero, Vector2.One * worldChunkSize), (chunk.ChunkIndex - _world.CenterChunkIndex + Vector2.One) * worldChunkSize); } _viewTileTypeTexture = new ImageTexture(); _viewTileTypeTexture.CreateFromImage(_viewTileTypeImage); WorldTextureCoordinateOffset = chunkIndexSouthWest * worldChunkSize; EmitSignal("OnWorldViewTileTypeImageChanged", _viewTileTypeImage); } private void HandleWorldTileChange(Array removedChunkIndices, Array addedChunkIndices) { Array removedChunks = new(); foreach (Vector2 chunkIndex in removedChunkIndices) { SceneTileChunk chunk = RemoveChunkFromScene(chunkIndex); if (chunk != null) { removedChunks.Add(chunk); } } foreach (Vector2 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 (HexTile3D tile3D in sceneTileChunk.TileNodes) { tile3D.Transform = new Transform(Basis.Identity.Scaled(Vector3.One * 0.95f), tile3D.Transform.origin); } } _sceneTileChunks.Add(sceneTileChunk); } UpdateWorldViewTexture(); 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); } }