using Godot; using System; using System.Linq; using System.Diagnostics; using Godot.Collections; using Namespace; using Vector2 = Godot.Vector2; using Vector3 = Godot.Vector3; public class TileWorld : Spatial { private enum GenerationState { Heightmap, Color, Objects, Done } // constants private static readonly Color RockColor = new Color(0.5f, 0.5f, 0.4f); private static readonly Color GrassColor = new Color(0, 0.4f, 0); private GenerationState _currentGenerationState = GenerationState.Heightmap; // signals [Signal] delegate void WorldGenerated(); // public members public enum MapType { Noise, Debug, Flat, } [ExportFlagsEnum(typeof(MapType))] public MapType GenerationMapType = MapType.Debug; [Export] public int Size = 64; [Export] public bool DebugMap; public float HeightScale = 1.0f; public Image HeightmapImage; public Image ColormapImage; public Image NavigationmapImage; public int Seed = 0; public Spatial Entities; public HexGrid HexGrid; // private members private int _halfSize; private Random _tileTypeRandom; private Viewport _worldOffscreenViewport; private TextureRect _worldOffscreenTextureRect; private Viewport _heightmapOffscreenViewport; private TextureRect _heightmapOffscreenTextureRect; private Array _rockAssets = new(); private Array _grassAssets = new(); private Array _treeAssets = new(); private PackedScene _chestScene = GD.Load("res://entities/Chest.tscn"); private Spatial _environmentNode; private bool _resizeTriggered; private int _resizeExtraFrameCount = 0; // Called when the node enters the scene tree for the first time. public override void _Ready() { HexGrid = new HexGrid(); _tileTypeRandom = new Random(); _worldOffscreenViewport = (Viewport)GetNode("WorldOffscreenViewport"); Debug.Assert(_worldOffscreenViewport != null); _worldOffscreenViewport.Size = new Vector2(Size, Size); _worldOffscreenTextureRect = (TextureRect)GetNode("WorldOffscreenViewport/TextureRect"); Debug.Assert(_worldOffscreenTextureRect != null); _worldOffscreenTextureRect.SetSize(new Vector2(Size, Size)); _heightmapOffscreenViewport = (Viewport)GetNode("HeightmapOffscreenViewport"); Debug.Assert(_heightmapOffscreenViewport != null); _heightmapOffscreenViewport.Size = new Vector2(Size, Size); _heightmapOffscreenTextureRect = (TextureRect)GetNode("HeightmapOffscreenViewport/TextureRect"); Debug.Assert(_heightmapOffscreenTextureRect != null); _heightmapOffscreenTextureRect.SetSize(new Vector2(Size, Size)); GetNode("Assets").Visible = false; foreach (Spatial asset in GetNode("Assets/Rocks").GetChildren()) { _rockAssets.Add(asset); } foreach (Spatial asset in GetNode("Assets/Grass").GetChildren()) { _grassAssets.Add(asset); } foreach (Spatial asset in GetNode("Assets/Trees").GetChildren()) { _treeAssets.Add(asset); } _chestScene = GD.Load("res://entities/Chest.tscn"); _environmentNode = GetNode("Environment"); Entities = GetNode("Entities"); ResetWorldImages(Size); } public void ResetWorldImages(int size) { GD.Print("Resetting World Images to size " + size); Vector2 sizeVector = new Vector2(size, size); ColormapImage = new Image(); ColormapImage.Create(size, size, false, Image.Format.Rgba8); ColormapImage.FillRect(new Rect2(0, 0, size, size), Colors.Black); _worldOffscreenTextureRect.SetSize(sizeVector); _worldOffscreenViewport.Size = sizeVector; HeightmapImage = new Image(); HeightmapImage.Create(size, size, false, Image.Format.Rf); HeightmapImage.FillRect(new Rect2(0, 0, size, size), Colors.ForestGreen); _heightmapOffscreenTextureRect.SetSize(sizeVector); _heightmapOffscreenViewport.Size = sizeVector; NavigationmapImage = new Image(); NavigationmapImage.Create(size, size, false, Image.Format.Rgb8); NavigationmapImage.FillRect(new Rect2(0, 0, size, size), new Color(1, 1, 1)); } public void Generate(int size) { GD.Print("Triggering generation for size: " + size); if (Size != size) { ResetWorldImages(size); _resizeTriggered = true; _resizeExtraFrameCount = 1; } Size = size; _worldOffscreenViewport.Size = new Vector2(size, size); _heightmapOffscreenViewport.Size = new Vector2(size, size); _halfSize = Mathf.RoundToInt(size) / 2; HexGrid.SetBounds( TextureCoordToCell(new Vector2(0, 0)), TextureCoordToCell(new Vector2(size, size)) ); HexGrid.Obstacles.Clear(); HexGrid.Barriers.Clear(); OnMapGenerationStart(); switch (GenerationMapType) { case MapType.Debug: GenerateDebugMap(); break; case MapType.Flat: GenerateFlatMap(); break; case MapType.Noise: GenerateNoiseMap(); break; } } private void GenerateDebugMap() { ColormapImage = new Image(); ColormapImage.Create(Size, Size, false, Image.Format.Rgba8); HeightmapImage = new Image(); HeightmapImage.Create(Size, Size, false, Image.Format.Rf); HeightmapImage.Lock(); ColormapImage.Lock(); foreach (int coordX in Enumerable.Range(0, Size)) { foreach (int coordY in Enumerable.Range(0, Size)) { ColormapImage.SetPixel(coordX, coordY, new Color((float)Mathf.Min(coordX, coordY) / Size, 0, 0)); HeightmapImage.SetPixel(coordX, coordY, new Color((float)Mathf.Min(coordX, coordY) / Size, 0, 0)); } } // Colormap.SetPixel(Size - 1, Size - 1, new Color(1, 1, 1, 1)); ColormapImage.Fill(Colors.ForestGreen); ColormapImage.Unlock(); OnMapGenerationComplete(); } private void GenerateFlatMap() { ColormapImage = new Image(); ColormapImage.Create(Size, Size, false, Image.Format.Rgba8); HeightmapImage = new Image(); HeightmapImage.Create(Size, Size, false, Image.Format.Rf); HeightmapImage.Lock(); ColormapImage.Lock(); foreach (int coordX in Enumerable.Range(0, Size)) { foreach (int coordY in Enumerable.Range(0, Size)) { HeightmapImage.SetPixel(coordX, coordY, new Color(0.5f, 0.5f, 0.5f)); } } ColormapImage.Fill(Colors.ForestGreen); ColormapImage.Unlock(); OnMapGenerationComplete(); } private void GenerateNoiseMap() { HeightmapImage = new Image(); HeightmapImage.Create(Size, Size, false, Image.Format.Rgba8); OpenSimplexNoise noiseGenerator = new OpenSimplexNoise(); noiseGenerator.Seed = Seed; noiseGenerator.Octaves = 4; noiseGenerator.Period = 20; noiseGenerator.Persistence = 0.2f; noiseGenerator.Lacunarity = 4; ImageTexture heightmapTexture = new ImageTexture(); heightmapTexture.CreateFromImage(noiseGenerator.GetSeamlessImage(Size)); heightmapTexture.Flags = 0; _heightmapOffscreenTextureRect.Texture = heightmapTexture; OnHeightMapChanged(); } private void OnMapGenerationStart() { foreach (Node child in _environmentNode.GetChildren()) { child.QueueFree(); } foreach (Node child in Entities.GetChildren()) { child.QueueFree(); } } private void OnHeightMapChanged() { GD.Print("Triggering rendering of height map"); _currentGenerationState = GenerationState.Heightmap; _heightmapOffscreenViewport.RenderTargetUpdateMode = Viewport.UpdateMode.Once; } private void OnMapGenerationComplete() { _currentGenerationState = GenerationState.Done; _worldOffscreenViewport.RenderTargetUpdateMode = Viewport.UpdateMode.Disabled; _heightmapOffscreenViewport.RenderTargetUpdateMode = Viewport.UpdateMode.Disabled; _resizeTriggered = false; HeightmapImage.Lock(); EmitSignal("WorldGenerated"); } Spatial SelectAsset(Vector2 offsetCoord, Array assets, Random randomGenerator, double probability) { if (randomGenerator.NextDouble() < 1.0 - probability) { return null; } int assetIndex = randomGenerator.Next(assets.Count); Spatial assetInstance = (Spatial)assets[assetIndex].Duplicate(); Transform assetTransform = Transform.Identity; assetTransform.origin = GetHexCenterFromOffset(offsetCoord); assetTransform.origin.y = GetHeightAtOffset(offsetCoord); assetTransform.basis = assetTransform.basis.Rotated(Vector3.Up, (float)(randomGenerator.NextDouble() * Mathf.Pi * 2)); assetInstance.Transform = assetTransform; return assetInstance; } bool IsColorEqualApprox(Color colorA, Color colorB) { Vector3 colorDifference = new Vector3(colorA.r - colorB.r, colorA.g - colorB.g, colorA.b - colorB.b); return colorDifference.LengthSquared() < 0.1 * 0.1; } bool IsColorWater(Color color) { return (color.r == 0 && color.g == 0 && color.b > 0.01); } public void MarkCellUnwalkable(HexCell cell) { HexGrid.AddObstacle(cell); NavigationmapImage.Lock(); NavigationmapImage.SetPixelv(OffsetToTextureCoord(cell.OffsetCoords), Colors.Red); NavigationmapImage.Unlock(); } private void PopulateEnvironment() { Random environmentRandom = new Random(Seed); ColormapImage.Lock(); HeightmapImage.Lock(); foreach (int textureCoordU in Enumerable.Range(0, Size)) { foreach (int textureCoordV in Enumerable.Range(0, Size)) { Color colorValue = ColormapImage.GetPixel(textureCoordU, textureCoordV); Vector2 textureCoord = new Vector2(textureCoordU, textureCoordV); HexCell cell = TextureCoordToCell(textureCoord); Vector2 offsetCoord = cell.OffsetCoords; if (IsColorEqualApprox(colorValue, RockColor)) { Spatial rockAsset = SelectAsset(offsetCoord, _rockAssets, environmentRandom, 0.15); if (rockAsset != null) { _environmentNode.AddChild(rockAsset); MarkCellUnwalkable(cell); } } else if (IsColorEqualApprox(colorValue, GrassColor)) { Spatial grassAsset = SelectAsset(offsetCoord, _grassAssets, environmentRandom, 0.35); if (grassAsset != null) { _environmentNode.AddChild(grassAsset); } Spatial treeAsset = SelectAsset(offsetCoord, _treeAssets, environmentRandom, 0.10); if (treeAsset != null) { Entities.AddChild(treeAsset); MarkCellUnwalkable(cell); } else if (environmentRandom.NextDouble() < 0.01) { Chest chestAsset = (Chest)_chestScene.Instance(); Transform assetTransform = Transform.Identity; assetTransform.origin = GetHexCenterFromOffset(offsetCoord); assetTransform.origin.y = GetHeightAtOffset(offsetCoord); chestAsset.Transform = assetTransform; Entities.AddChild(chestAsset); MarkCellUnwalkable(cell); } } else if (IsColorWater(colorValue)) { MarkCellUnwalkable(cell); } } } HeightmapImage.Unlock(); ColormapImage.Unlock(); } public override void _Process(float delta) { if (_resizeTriggered && _resizeExtraFrameCount > 0) { _resizeExtraFrameCount--; return; } if (_currentGenerationState == GenerationState.Heightmap) { HeightmapImage.CopyFrom(_heightmapOffscreenViewport.GetTexture().GetData()); _currentGenerationState = GenerationState.Color; ImageTexture imageTexture = new ImageTexture(); imageTexture.CreateFromImage(HeightmapImage); imageTexture.Flags = 0; _worldOffscreenTextureRect.Texture = imageTexture; GD.Print("Triggering rendering of color map"); _worldOffscreenViewport.RenderTargetUpdateMode = Viewport.UpdateMode.Once; _resizeExtraFrameCount = 1; } else if (_currentGenerationState == GenerationState.Color) { ColormapImage = new Image(); ColormapImage.Create(Size, Size, false, Image.Format.Rgb8); ColormapImage.CopyFrom(_worldOffscreenViewport.GetTexture().GetData()); _currentGenerationState = GenerationState.Objects; PopulateEnvironment(); } else if (_currentGenerationState == GenerationState.Objects) { OnMapGenerationComplete(); } } public bool IsOffsetCoordValid(Vector2 offsetCoord) { return ((int)Math.Clamp(offsetCoord.x, -(float)Size / 2, (float)Size / 2 - 1) == (int)offsetCoord.x && (int)Math.Clamp(offsetCoord.y, -(float)Size / 2, (float)Size / 2 - 1) == (int)offsetCoord.y); } public HexTile3D.TileType GetTileTypeAtOffset(Vector2 offsetCoord) { if (!IsOffsetCoordValid(offsetCoord)) { return HexTile3D.TileType.Undefined; } return HexTile3D.ValidTileTypes[_tileTypeRandom.Next(HexTile3D.ValidTileTypes.Length)]; } public Vector2 OffsetToTextureCoord(Vector2 offsetCoord) { Vector2 mapSize = Vector2.One * Size; Vector2 textureCoord = (offsetCoord + mapSize / 2).PosMod(mapSize); return textureCoord; } public void SetHeightAtOffset(Vector2 offsetCoord, float height) { Vector2 textureCoord = OffsetToTextureCoord(offsetCoord); HeightmapImage.SetPixel((int)textureCoord.x, (int)textureCoord.y, new Color(height, 0f, 0f)); } public float GetHeightAtOffset(Vector2 offsetCoord) { if (_currentGenerationState != GenerationState.Done) { return 0f; } Vector2 textureCoord = OffsetToTextureCoord(offsetCoord); float heightmapHeight = (HeightmapImage.GetPixel((int)textureCoord.x, (int)(textureCoord.y)).r - 0.5f) * HeightScale; if (heightmapHeight <= 0) { heightmapHeight -= 0.03f; } else { heightmapHeight = 0f; } return heightmapHeight; } public void SetTileColorAtOffset(Vector2 offsetCoord, Color color) { Vector2 textureCoord = OffsetToTextureCoord(offsetCoord); ColormapImage.Lock(); ColormapImage.SetPixel((int)textureCoord.x, (int)textureCoord.y, color); ColormapImage.Unlock(); } public Vector2 WorldToOffsetCoords(Vector3 worldCoord) { return HexGrid.GetHexAt(new Vector2(worldCoord.x, worldCoord.z)).OffsetCoords + Vector2.One * Mathf.Round(Size / 2f); } public Vector3 GetTileWorldCenterFromOffset(Vector2 offsetCoord) { Vector2 tileCenter = HexGrid.GetHexCenterFromOffset(offsetCoord - Vector2.One * Mathf.Round(Size / 2f)); return new Vector3(tileCenter.x, GetHeightAtOffset(offsetCoord), tileCenter.y); } public Vector3 GetHexCenterFromOffset(Vector2 offsetCoord) { Vector2 tileCenter = HexGrid.GetHexCenterFromOffset(offsetCoord); return new Vector3(tileCenter.x, GetHeightAtOffset(offsetCoord), tileCenter.y); } public HexCell TextureCoordToCell(Vector2 textureCoord) { return HexCell.FromOffsetCoords(textureCoord - Vector2.One * _halfSize); } public Vector2 TextureCoordToOffsetCoord(Vector2 textureCoord) { return TextureCoordToCell(textureCoord).OffsetCoords; } }