Refactoring now functional

WorldChunkRefactoring
Martin Felis 2023-11-15 20:57:25 +01:00
parent fcc2fdb8d3
commit a37b028b39
6 changed files with 329 additions and 380 deletions

View File

@ -36,7 +36,6 @@ public class Game : Spatial
// Resources
private PackedScene _tileHighlightScene;
private TileInstanceManager _tileInstanceManager;
private ShaderMaterial _tileMaterial;
private Label _tileOffsetLabel;
private World _world;
@ -46,7 +45,7 @@ public class Game : Spatial
public override void _Ready()
{
// debugStatsContainer
var debugStatsContainer = (Container)FindNode("DebugStatsContainer");
Container debugStatsContainer = (Container)FindNode("DebugStatsContainer");
_framesPerSecondLabel = debugStatsContainer.GetNode<Label>("fps_label");
_centerLabel = debugStatsContainer.GetNode<Label>("center_label");
@ -60,7 +59,7 @@ public class Game : Spatial
_numCoordsRemovedLabel = debugStatsContainer.GetNode<Label>("num_coords_removed_label");
// UI elements
var worldGeneratorContainer = (Container)FindNode("WorldGeneratorContainer");
Container worldGeneratorContainer = (Container)FindNode("WorldGeneratorContainer");
_worldTextureRect = worldGeneratorContainer.GetNode<TextureRect>("WorldTextureRect");
_heightTextureRect = worldGeneratorContainer.GetNode<TextureRect>("HeightTextureRect");
_generateWorldButton = worldGeneratorContainer.GetNode<Button>("WorldGenerateButton");
@ -77,10 +76,9 @@ public class Game : Spatial
_cameraOffset = _camera.GlobalTranslation - _player.GlobalTranslation;
_world = (World)FindNode("World");
_tileInstanceManager = (TileInstanceManager)FindNode("TileInstanceManager");
// populate UI values
var generatorWorldSizeSlider = worldGeneratorContainer.GetNode<Slider>("HBoxContainer/WorldSizeSlider");
Slider generatorWorldSizeSlider = worldGeneratorContainer.GetNode<Slider>("HBoxContainer/WorldSizeSlider");
// resources
_tileHighlightScene = GD.Load<PackedScene>("utils/TileHighlight.tscn");
@ -88,7 +86,7 @@ public class Game : Spatial
Debug.Assert(_tileMaterial != null);
_blackWhitePatternTexture = new ImageTexture();
var image = new Image();
Image image = new Image();
image.Load("assets/4x4checker.png");
_blackWhitePatternTexture.CreateFromImage(image, (uint)(Texture.FlagsEnum.Mipmaps | Texture.FlagsEnum.Repeat));
@ -104,8 +102,8 @@ public class Game : Spatial
_player.TaskQueueComponent.Connect("StartInteraction", _interactionSystem,
nameof(_interactionSystem.OnStartInteraction));
_player.Connect("GoldCountChanged", this, nameof(OnGoldCountChanged));
_tileInstanceManager.Connect("TileClicked", this, nameof(OnTileClicked));
_tileInstanceManager.Connect("TileHovered", this, nameof(OnTileHovered));
_world.Connect("TileClicked", this, nameof(OnTileClicked));
_world.Connect("TileHovered", this, nameof(OnTileHovered));
_world.Connect("OnWorldViewTileTypeImageChanged", this, nameof(OnWorldViewTileTypeImageChanged));
_world.Connect("OnHeightmapImageChanged", this, nameof(OnHeightmapImageChanged));
@ -118,7 +116,7 @@ public class Game : Spatial
// perform dependency injection
//_streamContainer.SetWorld(_tileWorld);Clicked
var worldInfoComponent = _player.GetNode<WorldInfoComponent>("WorldInfo");
WorldInfoComponent worldInfoComponent = _player.GetNode<WorldInfoComponent>("WorldInfo");
UpdateCurrentTile();
}
@ -134,9 +132,9 @@ public class Game : Spatial
public void UpdateCurrentTile()
{
// cast a ray from the camera to center
var cameraNormal = _camera.ProjectRayNormal(_camera.GetViewport().Size * 0.5f);
var cameraPosition = _camera.ProjectRayOrigin(_camera.GetViewport().Size * 0.5f);
var cameraDir = cameraNormal - cameraPosition;
Vector3 cameraNormal = _camera.ProjectRayNormal(_camera.GetViewport().Size * 0.5f);
Vector3 cameraPosition = _camera.ProjectRayOrigin(_camera.GetViewport().Size * 0.5f);
Vector3 cameraDir = cameraNormal - cameraPosition;
Vector3 centerCoord;
@ -166,13 +164,13 @@ public class Game : Spatial
UpdateCurrentTile();
var tileHighlightTransform = Transform.Identity;
var currentTileCenter = _hexGrid.GetHexCenter(_currentTile);
Transform tileHighlightTransform = Transform.Identity;
Vector2 currentTileCenter = _hexGrid.GetHexCenter(_currentTile);
tileHighlightTransform.origin.x = currentTileCenter.x;
tileHighlightTransform.origin.z = currentTileCenter.y;
_tileHighlight.Transform = tileHighlightTransform;
var cameraTransform = _camera.Transform;
Transform cameraTransform = _camera.Transform;
cameraTransform.origin = _player.GlobalTranslation + _cameraOffset;
_camera.Transform = cameraTransform;
}
@ -181,7 +179,7 @@ public class Game : Spatial
public void OnGenerateButton()
{
GD.Print("Generating");
var worldSizeSlider = (Slider)FindNode("WorldSizeSlider");
Slider worldSizeSlider = (Slider)FindNode("WorldSizeSlider");
if (worldSizeSlider == null) GD.PrintErr("Could not find WorldSizeSlider!");
}
@ -189,8 +187,8 @@ public class Game : Spatial
public void OnAreaInputEvent(Node camera, InputEvent inputEvent, Vector3 position, Vector3 normal,
int shapeIndex)
{
var cellAtCursor = _hexGrid.GetHexAt(new Vector2(position.x, position.z));
var highlightTransform = Transform.Identity;
HexCell cellAtCursor = _hexGrid.GetHexAt(new Vector2(position.x, position.z));
Transform highlightTransform = Transform.Identity;
_mouseWorldLabel.Text = position.ToString("F3");
_mouseTileOffsetLabel.Text = cellAtCursor.OffsetCoords.ToString("N");
@ -213,7 +211,7 @@ public class Game : Spatial
public void OnTileHovered(HexTile3D tile)
{
var highlightTransform = tile.GlobalTransform;
Transform highlightTransform = tile.GlobalTransform;
_mouseTileHighlight.Transform = highlightTransform;
_mouseWorldLabel.Text = highlightTransform.origin.ToString("F3");
_mouseTileOffsetLabel.Text = tile.OffsetCoords.ToString("N");
@ -226,7 +224,7 @@ public class Game : Spatial
{
GD.Print("Clicked on entity at " + entity.GlobalTranslation);
var mountPoint = (Spatial)entity.FindNode("MountPoint");
Spatial mountPoint = (Spatial)entity.FindNode("MountPoint");
if (mountPoint != null)
{
_player.TaskQueueComponent.Reset();
@ -239,7 +237,7 @@ public class Game : Spatial
public void ResetGameState()
{
var playerStartTransform = Transform.Identity;
Transform playerStartTransform = Transform.Identity;
playerStartTransform.origin.y = 0;
_player.Transform = playerStartTransform;
_player.TaskQueueComponent.Reset();
@ -250,9 +248,9 @@ public class Game : Spatial
foreach (Spatial entity in GetNode("Entities").GetChildren())
{
var entityTransform = entity.Transform;
var entityPlanePos = new Vector2(entityTransform.origin.x, entityTransform.origin.z);
var entityOffsetCoordinates = _hexGrid.GetHexAt(entityPlanePos).OffsetCoords;
Transform entityTransform = entity.Transform;
Vector2 entityPlanePos = new Vector2(entityTransform.origin.x, entityTransform.origin.z);
Vector2 entityOffsetCoordinates = _hexGrid.GetHexAt(entityPlanePos).OffsetCoords;
entityTransform.origin.y = 0;
entity.Transform = entityTransform;
}
@ -260,7 +258,7 @@ public class Game : Spatial
private void OnHeightmapImageChanged(Image heightmapImage)
{
var newHeightmapTexture = new ImageTexture();
ImageTexture newHeightmapTexture = new ImageTexture();
newHeightmapTexture.CreateFromImage(heightmapImage,
(uint)(Texture.FlagsEnum.Mipmaps | Texture.FlagsEnum.Repeat));
@ -269,7 +267,7 @@ public class Game : Spatial
private void OnWorldViewTileTypeImageChanged(Image viewTileTypeImage)
{
var newWorldTexture = new ImageTexture();
ImageTexture newWorldTexture = new ImageTexture();
newWorldTexture.CreateFromImage(viewTileTypeImage,
(uint)(Texture.FlagsEnum.Mipmaps | Texture.FlagsEnum.Repeat));
@ -283,7 +281,7 @@ public class Game : Spatial
public void OnGoldCountChanged(int goldCount)
{
var animationPlayer = _gameUi.GetNode<AnimationPlayer>("AnimationPlayer");
AnimationPlayer animationPlayer = _gameUi.GetNode<AnimationPlayer>("AnimationPlayer");
_goldCountLabel.Text = goldCount.ToString();
animationPlayer.CurrentAnimation = "FlashLabel";
animationPlayer.Seek(0);

View File

@ -1,4 +1,4 @@
[gd_scene load_steps=22 format=2]
[gd_scene load_steps=21 format=2]
[ext_resource path="res://entities/Player.tscn" type="PackedScene" id=2]
[ext_resource path="res://scenes/Camera.tscn" type="PackedScene" id=3]
@ -7,7 +7,6 @@
[ext_resource path="res://ui/DebugStatsContainer.gd" type="Script" id=6]
[ext_resource path="res://scenes/World.cs" type="Script" id=7]
[ext_resource path="res://scenes/Game.cs" type="Script" id=9]
[ext_resource path="res://scenes/TileInstanceManager.cs" type="Script" id=10]
[ext_resource path="res://entities/Chest.tscn" type="PackedScene" id=11]
[ext_resource path="res://ui/WorldGeneratorUI.gd" type="Script" id=12]
[ext_resource path="res://assets/Environment/HexTileMesh.tres" type="CylinderMesh" id=13]
@ -358,6 +357,7 @@ flip_v = true
visible = false
[node name="Camera" parent="." instance=ExtResource( 3 )]
transform = Transform( 1, 0, 0, 0, 0.60042, 0.799685, 0, -0.799685, 0.60042, -4.76837e-07, 5.16505, 3.1696 )
[node name="InteractionSystem" type="Node" parent="."]
script = ExtResource( 15 )
@ -370,33 +370,27 @@ WorldNode = NodePath("../World")
[node name="WorldInfo" parent="Player" index="2"]
WorldPath = NodePath("../../World")
[node name="ToolAttachement" parent="Player/Geometry/Armature/Skeleton" index="5"]
transform = Transform( 1, 8.68458e-08, -1.04308e-07, 1.74623e-07, -1, -1.30385e-07, 1.41561e-07, 1.50874e-07, -1, -0.72, 0.45, 3.28113e-08 )
[node name="ToolAttachement" parent="Player/Geometry/PirateAsset/Armature/Skeleton" index="5"]
transform = Transform( 1, 7.13626e-08, -4.47035e-08, 1.64262e-07, -1, -1.00583e-07, 1.19209e-07, 1.18278e-07, -1, -0.72, 0.45, 1.78362e-08 )
[node name="AnimationTree" parent="Player/Geometry" index="2"]
parameters/playback = SubResource( 26 )
[node name="Entities" type="Spatial" parent="."]
visible = false
[node name="Axe" parent="Entities" instance=ExtResource( 14 )]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 1.79762, 0, 0 )
input_ray_pickable = false
[node name="Chest" parent="Entities" instance=ExtResource( 11 )]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -3.27709, 0, 1.02593 )
transform = Transform( -0.825665, 0, 0.56416, 0, 1, 0, -0.56416, 0, -0.825665, -3.27709, 0, 1.02593 )
[node name="World" type="Spatial" parent="."]
script = ExtResource( 7 )
[node name="Chunks" type="Spatial" parent="World"]
[node name="TileInstanceManager" type="Spatial" parent="World"]
script = ExtResource( 10 )
ShowHexTiles = true
World = NodePath("..")
[node name="TileMultiMeshInstance" type="MultiMeshInstance" parent="World/TileInstanceManager"]
[node name="TileMultiMeshInstance" type="MultiMeshInstance" parent="World"]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -2.5, 0 )
multimesh = SubResource( 27 )
@ -430,4 +424,4 @@ directional_shadow_mode = 0
[connection signal="toggled" from="Generator Container/WorldGeneratorContainer/ShowTexturesCheckButton" to="Generator Container/WorldGeneratorContainer" method="_on_ShowTexturesCheckButton_toggled"]
[editable path="Player"]
[editable path="Player/Geometry"]
[editable path="Player/Geometry/PirateAsset"]

View File

@ -1,202 +0,0 @@
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<SceneTileChunk> _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);
_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<Vector2> removedChunkIndices, Array<Vector2> addedChunkIndices)
{
Array<SceneTileChunk> 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<PackedScene>("res://scenes/HexTile3D.tscn");
private readonly MultiMeshInstance _multiMeshInstance;
private readonly Array<int> _tileInstanceIndices = new();
private readonly HexGrid _hexGrid = new();
private readonly bool _showHexTiles;
public readonly Array<HexTile3D> 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);
}
}
}
}
}

View File

@ -17,7 +17,7 @@ public class World : Spatial
}
// constants
public const int ChunkSize = 18;
public const int ChunkSize = 12;
public const int NumChunkRows = 3;
public const int NumChunkColumns = NumChunkRows;
private static readonly Color RockColor = new(0.5f, 0.5f, 0.4f);
@ -25,11 +25,13 @@ public class World : Spatial
private static readonly Color DarkGrassColor = new(0.05882353f, 0.5411765f, 0.05882353f);
private static readonly Color LightWaterColor = new(0.05882353f, 0.05882353f, 0.8627451f);
private readonly List<Vector2> _addedChunkIndices = new();
private readonly Godot.Collections.Dictionary<Vector2, WorldChunk> _cachedWorldChunks;
private readonly List<Vector2> _addedChunkIndices = new();
private readonly List<WorldChunk> _unusedWorldChunks = new();
private readonly Image _heightmapImage = new();
private readonly List<Vector2> _removedChunkIndices = new();
private readonly Image _tileTypeMapImage = new();
private int FrameCounter;
// referenced scenes
private readonly PackedScene _worldChunkScene = GD.Load<PackedScene>("res://scenes/WorldChunk.tscn");
@ -48,7 +50,8 @@ public class World : Spatial
private OpenSimplexNoise _noiseGenerator = new();
private Array<Spatial> _rockAssets;
private TileInstanceManager _tileInstanceManager;
private MultiMeshInstance _tileMultiMeshInstance;
private int _usedTileMeshInstances;
private Array<Spatial> _treeAssets;
private ImageTexture _viewTileTypeTexture;
public Vector2 CenterChunkIndex = Vector2.Zero;
@ -83,6 +86,13 @@ public class World : Spatial
[Signal]
public delegate void EntityClicked(Entity entity);
// signals
[Signal]
private delegate void TileClicked(HexTile3D tile3d);
[Signal]
private delegate void TileHovered(HexTile3D tile3d);
public World()
{
Debug.Assert(ChunkSize % 2 == 0);
@ -96,10 +106,11 @@ public class World : Spatial
Chunks = (Spatial)FindNode("Chunks");
Debug.Assert(Chunks != null);
_tileInstanceManager = (TileInstanceManager)FindNode("TileInstanceManager");
Debug.Assert(_tileInstanceManager != null);
_tileInstanceManager.TileMultiMeshInstance.Multimesh.InstanceCount =
_tileMultiMeshInstance = (MultiMeshInstance)FindNode("TileMultiMeshInstance");
Debug.Assert(_tileMultiMeshInstance != null);
_tileMultiMeshInstance.Multimesh.InstanceCount =
ChunkSize * ChunkSize * NumChunkColumns * NumChunkRows;
_usedTileMeshInstances = 0;
InitNoiseGenerator();
@ -128,65 +139,72 @@ public class World : Spatial
_noiseGenerator.Lacunarity = 2;
}
public WorldChunk GetOrCreateWorldChunk(int xIndex, int yIndex, Color debugColor)
public WorldChunk GetOrCreateWorldChunk(Vector2 chunkIndex, Color debugColor)
{
if (IsChunkCached(xIndex, yIndex))
WorldChunk chunk;
if (IsChunkCached(chunkIndex))
return _cachedWorldChunks[chunkIndex];
if (_unusedWorldChunks.Count > 0)
{
var cachedChunk = _cachedWorldChunks[new Vector2(xIndex, yIndex)];
return cachedChunk;
chunk = _unusedWorldChunks.First();
_unusedWorldChunks.RemoveAt(0);
GD.Print("Reusing chunk from former index " + chunk.ChunkIndex + " at new index " + chunkIndex);
}
else
{
chunk = CreateWorldChunk(chunkIndex, debugColor);
}
return CreateWorldChunk(xIndex, yIndex, debugColor);
_cachedWorldChunks[chunkIndex] = chunk;
return chunk;
}
private bool IsChunkCached(int xIndex, int yIndex)
private bool IsChunkCached(Vector2 chunkIndex)
{
return _cachedWorldChunks.ContainsKey(new Vector2(xIndex, yIndex));
return _cachedWorldChunks.ContainsKey(chunkIndex);
}
private WorldChunk CreateWorldChunk(int xIndex, int yIndex, Color debugColor)
private WorldChunk CreateWorldChunk(Vector2 chunkIndex, Color debugColor)
{
var result = (WorldChunk)_worldChunkScene.Instance();
WorldChunk result = (WorldChunk)_worldChunkScene.Instance();
Chunks.AddChild(result);
result.Connect("TileClicked", this, nameof(OnTileClicked));
result.Connect("TileHovered", this, nameof(OnTileHovered));
result.SetSize(ChunkSize);
result.InitializeTileInstances(chunkIndex, _tileMultiMeshInstance, _usedTileMeshInstances);
_usedTileMeshInstances += result.Tiles.GetChildCount();
var offsetCoordSouthWest = new Vector2(xIndex, yIndex) * ChunkSize;
var offsetCoordNorthEast = offsetCoordSouthWest + new Vector2(1, 1) * (ChunkSize - 1);
var planeCoordSouthWest = HexGrid.GetHexCenterFromOffset(offsetCoordSouthWest) +
new Vector2(-HexGrid.HexSize.x, HexGrid.HexSize.y) * 0.5f;
var planeCoordNorthEast = HexGrid.GetHexCenterFromOffset(offsetCoordNorthEast) +
new Vector2(HexGrid.HexSize.x, -HexGrid.HexSize.y) * 0.5f;
result.ChunkIndex = new Vector2(xIndex, yIndex);
result.PlaneRect = new Rect2(
new Vector2(planeCoordSouthWest.x, planeCoordNorthEast.y),
new Vector2(planeCoordNorthEast.x - planeCoordSouthWest.x, planeCoordSouthWest.y - planeCoordNorthEast.y));
result.SetChunkIndex(chunkIndex, HexGrid);
result.UpdateTileTransforms();
result.DebugColor = debugColor;
result.DebugColor.a = 0.6f;
Chunks.AddChild(result);
var chunkIndex = new Vector2(xIndex, yIndex);
_cachedWorldChunks.Add(chunkIndex, result);
return result;
}
private bool IsColorEqualApprox(Color colorA, Color colorB)
{
var colorDifference = new Vector3(colorA.r - colorB.r, colorA.g - colorB.g, colorA.b - colorB.b);
Vector3 colorDifference = new(colorA.r - colorB.r, colorA.g - colorB.g, colorA.b - colorB.b);
return colorDifference.LengthSquared() < 0.1 * 0.1;
}
private Spatial SelectAsset(Vector2 offsetCoord, Array<Spatial> assets, Random randomGenerator, double probability)
private Spatial SelectAsset(Vector2 textureCoord, Array<Spatial> assets, Random randomGenerator, double probability)
{
if (randomGenerator.NextDouble() < 1.0 - probability) return null;
var assetIndex = randomGenerator.Next(assets.Count);
var assetInstance = (Spatial)assets[assetIndex].Duplicate();
var assetTransform = Transform.Identity;
assetTransform.origin = HexGrid.GetHexCenterVec3FromOffset(offsetCoord);
int assetIndex = randomGenerator.Next(assets.Count);
Spatial assetInstance = (Spatial)assets[assetIndex].Duplicate();
Transform assetTransform = Transform.Identity;
assetTransform.origin = HexGrid.GetHexCenterVec3FromOffset(textureCoord);
// TODO: assetTransform.origin.y = GetHeightAtOffset(offsetCoord);
assetTransform.origin.y = 0;
assetTransform.basis =
@ -198,30 +216,30 @@ public class World : Spatial
private void PopulateChunk(WorldChunk chunk)
{
var environmentRandom = new Random(Seed);
Random environmentRandom = new(Seed);
var tileTypeImage = chunk.TileTypeOffscreenViewport.GetTexture().GetData();
Image tileTypeImage = chunk.TileTypeOffscreenViewport.GetTexture().GetData();
tileTypeImage.Lock();
foreach (var textureCoordU in Enumerable.Range(0, chunk.Size))
foreach (var textureCoordV in Enumerable.Range(0, chunk.Size))
foreach (int textureCoordU in Enumerable.Range(0, chunk.Size))
foreach (int textureCoordV in Enumerable.Range(0, chunk.Size))
{
var colorValue = tileTypeImage.GetPixel(textureCoordU, textureCoordV);
var textureCoord = new Vector2(textureCoordU, textureCoordV);
var offsetCoord = chunk.ChunkIndex * ChunkSize + textureCoord;
Color colorValue = tileTypeImage.GetPixel(textureCoordU, textureCoordV);
Vector2 textureCoord = new(textureCoordU, textureCoordV);
Vector2 offsetCoord = chunk.ChunkIndex * ChunkSize + textureCoord;
if (IsColorEqualApprox(colorValue, RockColor))
{
var rockAsset = SelectAsset(offsetCoord, _rockAssets, environmentRandom, 0.15);
Spatial rockAsset = SelectAsset(textureCoord, _rockAssets, environmentRandom, 0.15);
if (rockAsset != null) chunk.Entities.AddChild(rockAsset);
// TODO: MarkCellUnwalkable(cell);
}
else if (IsColorEqualApprox(colorValue, GrassColor) || IsColorEqualApprox(colorValue, DarkGrassColor))
{
var grassAsset = SelectAsset(offsetCoord, _grassAssets, environmentRandom, 0.15);
Spatial grassAsset = SelectAsset(textureCoord, _grassAssets, environmentRandom, 0.15);
if (grassAsset != null) chunk.Entities.AddChild(grassAsset);
Tree treeAsset = SelectAsset(offsetCoord, _treeAssets, environmentRandom, 0.05) as Tree;
Tree treeAsset = SelectAsset(textureCoord, _treeAssets, environmentRandom, 0.05) as Tree;
if (treeAsset != null)
{
chunk.Entities.AddChild(treeAsset);
@ -268,83 +286,89 @@ public class World : Spatial
return;
}
GD.Print("Update Chunks: " + FrameCounter);
// mark all chunks as retired
Godot.Collections.Dictionary<Vector2, WorldChunk> oldCachedChunks = new(_cachedWorldChunks);
// set new center chunk
var chunkIndex = GetChunkTupleFromPlaneCoord(planeCoord);
CenterChunkIndex = new Vector2(chunkIndex.Item1, chunkIndex.Item2);
CenterChunkIndex = GetChunkTupleFromPlaneCoord(planeCoord);
var currentChunk = GetOrCreateWorldChunk(chunkIndex.Item1, chunkIndex.Item2,
WorldChunk currentChunk = GetOrCreateWorldChunk(CenterChunkIndex,
new Color(GD.Randf(), GD.Randf(), GD.Randf()));
_centerChunkRect = currentChunk.PlaneRect;
_centerChunkRect = new Rect2(
new Vector2(currentChunk.Transform.origin.x, currentChunk.Transform.origin.z)
+ currentChunk.PlaneRect.Position,
currentChunk.PlaneRect.Size);
GD.Print("Center Chunk Rect: " + _centerChunkRect.Position + " size: " + _centerChunkRect.Size);
// load or create adjacent chunks
_activeChunkIndices = new List<Vector2>();
_activeChunkIndices.Add(new Vector2(chunkIndex.Item1 - 1, chunkIndex.Item2 - 1));
_activeChunkIndices.Add(new Vector2(chunkIndex.Item1, chunkIndex.Item2 - 1));
_activeChunkIndices.Add(new Vector2(chunkIndex.Item1 + 1, chunkIndex.Item2 - 1));
_activeChunkIndices.Add(CenterChunkIndex + new Vector2(-1, -1));
_activeChunkIndices.Add(CenterChunkIndex + new Vector2(0, -1));
_activeChunkIndices.Add(CenterChunkIndex + new Vector2(1, -1));
_activeChunkIndices.Add(new Vector2(chunkIndex.Item1 - 1, chunkIndex.Item2));
_activeChunkIndices.Add(new Vector2(chunkIndex.Item1, chunkIndex.Item2));
_activeChunkIndices.Add(new Vector2(chunkIndex.Item1 + 1, chunkIndex.Item2));
_activeChunkIndices.Add(CenterChunkIndex + new Vector2(-1, 0));
_activeChunkIndices.Add(CenterChunkIndex);
_activeChunkIndices.Add(CenterChunkIndex + new Vector2(+1, 0));
_activeChunkIndices.Add(new Vector2(chunkIndex.Item1 - 1, chunkIndex.Item2 + 1));
_activeChunkIndices.Add(new Vector2(chunkIndex.Item1, chunkIndex.Item2 + 1));
_activeChunkIndices.Add(new Vector2(chunkIndex.Item1 + 1, chunkIndex.Item2 + 1));
_activeChunkIndices.Add(CenterChunkIndex + new Vector2(-1, +1));
_activeChunkIndices.Add(CenterChunkIndex + new Vector2(0, +1));
_activeChunkIndices.Add(CenterChunkIndex + new Vector2(+1, +1));
// clear unused chunks
_unusedWorldChunks.Clear();
_addedChunkIndices.Clear();
foreach (Vector2 oldChunkIndex in oldCachedChunks.Keys)
if (!_activeChunkIndices.Contains(oldChunkIndex))
DeactivateChunk(oldCachedChunks[oldChunkIndex]);
foreach (Vector2 activeChunkIndex in _activeChunkIndices)
{
WorldChunk chunk = GetOrCreateWorldChunk(activeChunkIndex,
new Color(GD.Randf(), GD.Randf(), GD.Randf()));
_cachedWorldChunks[activeChunkIndex] = chunk;
}
Debug.Assert(_activeChunkIndices.Count == NumChunkRows * NumChunkColumns);
foreach (var activeChunkIndex in _activeChunkIndices)
GetOrCreateWorldChunk((int)activeChunkIndex.x, (int)activeChunkIndex.y,
new Color(GD.Randf(), GD.Randf(), GD.Randf()));
// unload retired chunks
_removedChunkIndices.Clear();
_addedChunkIndices.Clear();
foreach (var cachedChunkKey in oldCachedChunks.Keys)
if (!_activeChunkIndices.Contains(cachedChunkKey))
RemoveChunk(cachedChunkKey);
foreach (var chunkKey in _activeChunkIndices)
foreach (Vector2 chunkKey in _activeChunkIndices)
if (!oldCachedChunks.ContainsKey(chunkKey))
{
_addedChunkIndices.Add(chunkKey);
var chunk = _cachedWorldChunks[chunkKey];
GenerateChunkNoiseMap(chunk);
ActivateChunk(_cachedWorldChunks[chunkKey], chunkKey);
State = GenerationState.Heightmap;
}
}
private void ActivateChunk(WorldChunk chunk, Vector2 chunkIndex)
{
chunk.SetChunkIndex(chunkIndex, HexGrid);
chunk.UpdateTileTransforms();
_addedChunkIndices.Add(chunk.ChunkIndex);
GD.Print("Generating noise for chunk " + chunk.ChunkIndex);
GenerateChunkNoiseMap(chunk);
}
private void DeactivateChunk(WorldChunk chunk)
{
GD.Print("Clearing chunk index: " + chunk.ChunkIndex);
_cachedWorldChunks.Remove(chunk.ChunkIndex);
chunk.ClearContent();
_unusedWorldChunks.Add(chunk);
}
private void GenerateChunkNoiseMap(WorldChunk chunk)
{
var chunkIndex = chunk.ChunkIndex;
Vector2 chunkIndex = chunk.ChunkIndex;
var debugChunkColor = new Color(Mathf.Abs(chunkIndex.x) / 5, Mathf.Abs(chunkIndex.y) / 5,
Mathf.RoundToInt(Mathf.Abs(chunkIndex.x + chunkIndex.y)) % 2);
var noiseImageTexture = new ImageTexture();
ImageTexture noiseImageTexture = new();
noiseImageTexture.CreateFromImage(_noiseGenerator.GetImage(ChunkSize, ChunkSize, chunkIndex * ChunkSize),
0);
// Debug Texture
var simpleImage = new Image();
simpleImage.Create(ChunkSize, ChunkSize, false, Image.Format.Rgb8);
simpleImage.Lock();
foreach (var i in Enumerable.Range(0, ChunkSize))
foreach (var j in Enumerable.Range(0, ChunkSize))
if ((i + j) % 2 == 0)
simpleImage.SetPixelv(new Vector2(i, j), Colors.Aqua);
else
simpleImage.SetPixelv(new Vector2(i, j), debugChunkColor);
simpleImage.Unlock();
// noiseImageTexture.CreateFromImage(simpleImage, 0);
chunk.SetNoisemap(noiseImageTexture);
}
@ -359,12 +383,10 @@ public class World : Spatial
}
private Tuple<int, int> GetChunkTupleFromPlaneCoord(Vector2 planeCoord)
private Vector2 GetChunkTupleFromPlaneCoord(Vector2 planeCoord)
{
var centerOffsetCoord = HexGrid.GetHexAt(planeCoord);
var chunkIndexFloat = (centerOffsetCoord.OffsetCoords / ChunkSize).Floor();
var chunkIndex = new Tuple<int, int>((int)chunkIndexFloat.x, (int)chunkIndexFloat.y);
return chunkIndex;
HexCell centerOffsetCoord = HexGrid.GetHexAt(planeCoord);
return (centerOffsetCoord.OffsetCoords / ChunkSize).Floor();
}
public void SetCenterPlaneCoord(Vector2 centerPlaneCoord)
@ -381,18 +403,18 @@ public class World : Spatial
private void UpdateWorldViewTexture()
{
var worldChunkSize = ChunkSize;
var numWorldChunkRows = NumChunkRows;
var numWorldChunkColumns = NumChunkColumns;
int worldChunkSize = ChunkSize;
int numWorldChunkRows = NumChunkRows;
int numWorldChunkColumns = NumChunkColumns;
_heightmapImage.Create(worldChunkSize * numWorldChunkColumns, worldChunkSize * numWorldChunkRows, false,
Image.Format.Rgba8);
_tileTypeMapImage.Create(worldChunkSize * numWorldChunkColumns, worldChunkSize * numWorldChunkRows, false,
Image.Format.Rgba8);
foreach (var chunkIndex in _activeChunkIndices)
foreach (Vector2 chunkIndex in _activeChunkIndices)
{
var worldChunk = GetOrCreateWorldChunk((int)chunkIndex.x, (int)chunkIndex.y, Colors.White);
WorldChunk worldChunk = GetOrCreateWorldChunk(chunkIndex, Colors.White);
_heightmapImage.BlendRect(
worldChunk.HeightmapOffscreenViewport.GetTexture().GetData(),
@ -422,9 +444,9 @@ public class World : Spatial
_chunkIndexSouthWest = Vector2.Inf;
_chunkIndexNorthEast = -Vector2.Inf;
foreach (var chunkIndex in _activeChunkIndices)
foreach (Vector2 chunkIndex in _activeChunkIndices)
{
var worldChunk = GetOrCreateWorldChunk((int)chunkIndex.x, (int)chunkIndex.y, Colors.White);
WorldChunk worldChunk = GetOrCreateWorldChunk(chunkIndex, Colors.White);
if (chunkIndex.x <= _chunkIndexSouthWest.x && chunkIndex.y <= _chunkIndexSouthWest.y)
_chunkIndexSouthWest = chunkIndex;
@ -435,71 +457,79 @@ public class World : Spatial
private void UpdateNavigationBounds()
{
var cellSouthWest = HexGrid.GetHexAtOffset(_chunkIndexSouthWest * ChunkSize);
HexCell cellSouthWest = HexGrid.GetHexAtOffset(_chunkIndexSouthWest * ChunkSize);
// Chunks have their cells ordered from south west (0,0) to north east (ChunkSize, ChunkSize). For the
// north east cell we have to add the chunk size to get to the actual corner cell.
var cellNorthEast = HexGrid.GetHexAtOffset(_chunkIndexNorthEast * ChunkSize + Vector2.One * (ChunkSize - 1));
HexCell cellNorthEast =
HexGrid.GetHexAtOffset(_chunkIndexNorthEast * ChunkSize + Vector2.One * (ChunkSize - 1));
var centerCell =
HexCell centerCell =
HexGrid.GetHexAtOffset(((cellNorthEast.OffsetCoords - cellSouthWest.OffsetCoords) / 2).Round());
var numCells = ChunkSize * Math.Max(NumChunkColumns, NumChunkRows);
int numCells = ChunkSize * Math.Max(NumChunkColumns, NumChunkRows);
HexGrid.SetBoundsOffset(cellSouthWest, ChunkSize * new Vector2(NumChunkColumns, NumChunkRows));
}
public override void _Process(float delta)
{
var oldState = State;
GenerationState oldState = State;
UpdateGenerationState();
if (oldState != GenerationState.Done && State == GenerationState.Done)
{
UpdateWorldViewTexture();
EmitSignal("OnTilesChanged", _removedChunkIndices.ToArray(), _addedChunkIndices.ToArray());
}
}
private void UpdateGenerationState()
{
FrameCounter++;
if (State == GenerationState.Heightmap)
{
var numChunksGeneratingHeightmap = 0;
foreach (var chunkIndex in _addedChunkIndices)
int numChunksGeneratingHeightmap = 0;
foreach (Vector2 chunkIndex in _addedChunkIndices)
{
var chunk = _cachedWorldChunks[chunkIndex];
WorldChunk chunk = _cachedWorldChunks[chunkIndex];
if (chunk.HeightMapFrameCount > 0) numChunksGeneratingHeightmap++;
}
if (numChunksGeneratingHeightmap == 0)
{
// assign height map images
foreach (var chunkIndex in _addedChunkIndices)
foreach (Vector2 chunkIndex in _addedChunkIndices)
{
var chunk = _cachedWorldChunks[chunkIndex];
WorldChunk chunk = _cachedWorldChunks[chunkIndex];
chunk.SetHeightmap(chunk.HeightmapOffscreenViewport.GetTexture());
}
GD.Print("Switching to TileType Generation: " + FrameCounter);
State = GenerationState.TileType;
}
}
else if (State == GenerationState.TileType)
{
var numChunksGeneratingTileType = 0;
foreach (var chunkIndex in _addedChunkIndices)
int numChunksGeneratingTileType = 0;
foreach (Vector2 chunkIndex in _addedChunkIndices)
{
var chunk = _cachedWorldChunks[chunkIndex];
WorldChunk chunk = _cachedWorldChunks[chunkIndex];
if (chunk.TileTypeMapFrameCount > 0) numChunksGeneratingTileType++;
}
if (numChunksGeneratingTileType == 0) State = GenerationState.Objects;
if (numChunksGeneratingTileType == 0)
{
GD.Print("Switching to Object Generation: " + FrameCounter);
State = GenerationState.Objects;
}
}
else if (State == GenerationState.Objects)
{
// generate objects
foreach (var chunkIndex in _addedChunkIndices) PopulateChunk(_cachedWorldChunks[chunkIndex]);
foreach (Vector2 chunkIndex in _addedChunkIndices)
PopulateChunk(_cachedWorldChunks[chunkIndex]);
_addedChunkIndices.Clear();
GD.Print("Generation done: " + FrameCounter);
State = GenerationState.Done;
}
}
@ -508,4 +538,14 @@ public class World : Spatial
{
EmitSignal("EntityClicked", entity);
}
public void OnTileClicked(HexTile3D tile)
{
EmitSignal("TileClicked", tile);
}
public void OnTileHovered(HexTile3D tile)
{
EmitSignal("TileHovered", tile);
}
}

View File

@ -1,9 +1,14 @@
using System.Diagnostics;
using System.Linq;
using Godot;
using Godot.Collections;
public class WorldChunk : Spatial
{
private readonly PackedScene _hexTile3DScene = GD.Load<PackedScene>("res://scenes/HexTile3D.tscn");
private MultiMeshInstance _multiMeshInstance;
private readonly Array<int> _tileInstanceIndices = new();
private readonly SpatialMaterial _rectMaterial = new();
private Sprite _heightmapSprite;
private TextureRect _heightmapTextureRect;
@ -11,9 +16,15 @@ public class WorldChunk : Spatial
private Sprite _noiseSprite;
private bool _showTextureOverlay;
[Export] public Vector2 ChunkIndex;
public Color DebugColor = Colors.White;
[Export] public Spatial Entities;
public Spatial Entities;
public Spatial Tiles;
private readonly HexGrid _hexGrid = new();
private readonly bool _showHexTiles;
[Export] public Texture HeightMap;
public int HeightMapFrameCount;
@ -23,10 +34,15 @@ public class WorldChunk : Spatial
public bool NoiseTextureCheckerboardOverlay = true;
// signals
// delegate void OnCoordClicked(Vector2 world_pos);
[Signal]
private delegate void TileClicked(HexTile3D tile3d);
[Signal]
private delegate void TileHovered(HexTile3D tile3d);
// other members
public Rect2 PlaneRect;
// ui elements
// scene nodes
@ -67,7 +83,7 @@ public class WorldChunk : Spatial
Debug.Assert(PlaneRectMesh != null);
if (PlaneRectMesh.Visible) _showTextureOverlay = true;
var planeRectTransform = Transform.Identity;
Transform planeRectTransform = Transform.Identity;
planeRectTransform =
planeRectTransform.Scaled(new Vector3(PlaneRect.Size.x, 0.125f, PlaneRect.Size.y));
planeRectTransform.origin.x = PlaneRect.GetCenter().x;
@ -94,6 +110,9 @@ public class WorldChunk : Spatial
Entities = (Spatial)FindNode("Entities");
Debug.Assert(Entities != null);
Tiles = (Spatial)FindNode("Tiles");
Debug.Assert(Tiles != null);
SetSize(World.ChunkSize);
}
@ -112,10 +131,98 @@ public class WorldChunk : Spatial
}
}
public void SetChunkIndex(Vector2 chunkIndex, HexGrid hexGrid)
{
ChunkIndex = chunkIndex;
float chunkSize = World.ChunkSize;
Vector2 planeCoordSouthWest = hexGrid.GetHexCenterFromOffset(chunkIndex * chunkSize);
Transform = new Transform(Basis.Identity, new Vector3(planeCoordSouthWest.x, 0, planeCoordSouthWest.y));
Vector2 localPlaneCoordSouthWest = new Vector2(-hexGrid.HexSize.x, hexGrid.HexSize.y) * 0.5f;
Vector2 localPlaneCoordNorthEast = hexGrid.GetHexCenterFromOffset(Vector2.One * chunkSize) +
new Vector2(hexGrid.HexSize.x, -hexGrid.HexSize.y) * 0.5f;
PlaneRect = new Rect2(
new Vector2(localPlaneCoordSouthWest.x, localPlaneCoordNorthEast.y),
new Vector2(localPlaneCoordNorthEast.x - localPlaneCoordSouthWest.x,
localPlaneCoordSouthWest.y - localPlaneCoordNorthEast.y)
);
}
public void InitializeTileInstances(Vector2 chunkIndex, MultiMeshInstance multiMeshInstance,
int tileInstanceIndexStart)
{
_multiMeshInstance = multiMeshInstance;
_tileInstanceIndices.Clear();
int chunkSize = World.ChunkSize;
foreach (Spatial node in Tiles.GetChildren())
node.QueueFree();
foreach (int i in Enumerable.Range(0, chunkSize))
foreach (int j in Enumerable.Range(0, chunkSize))
{
HexTile3D tile3D = (HexTile3D)_hexTile3DScene.Instance();
tile3D.Connect("TileClicked", this, nameof(OnTileClicked));
tile3D.Connect("TileHovered", this, nameof(OnTileHovered));
tile3D.Cell.OffsetCoords = new Vector2(chunkIndex * World.ChunkSize + new Vector2(i, j));
_tileInstanceIndices.Add(tileInstanceIndexStart + _tileInstanceIndices.Count);
Transform tileTransform = Transform.Identity;
Vector2 centerPlaneCoord = _hexGrid.GetHexCenterFromOffset(new Vector2(i, j));
tileTransform.origin = new Vector3(centerPlaneCoord.x, 0, centerPlaneCoord.y);
tile3D.Transform = tileTransform;
Tiles.AddChild(tile3D);
}
_multiMeshInstance.Multimesh.VisibleInstanceCount = _multiMeshInstance.Multimesh.InstanceCount;
}
public void ClearContent()
{
foreach (Spatial child in Entities.GetChildren())
child.QueueFree();
}
public void UpdateTileTransforms()
{
Transform chunkTransform = Transform.Identity;
Vector2 chunkOriginPlaneCoord = _hexGrid.GetHexCenterFromOffset(ChunkIndex * World.ChunkSize);
chunkTransform.origin = new Vector3(chunkOriginPlaneCoord.x, 0, chunkOriginPlaneCoord.y);
Transform = chunkTransform;
Basis tileOrientation = new(Vector3.Up, 90f * Mathf.Pi / 180f);
GD.Print("Updating transforms for instances of chunk " + ChunkIndex + " origin: " + chunkTransform.origin);
foreach (int i in Enumerable.Range(0, _tileInstanceIndices.Count))
{
int column = i % World.ChunkSize;
int row = i / World.ChunkSize;
Vector2 tilePlaneCoord =
_hexGrid.GetHexCenterFromOffset(new Vector2(column, row));
Transform hexTransform = new(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);
}
}
// other members
public void SaveToFile(string chunkName)
{
var image = new Image();
Image image = new();
image.CreateFromData(Size, Size, false, Image.Format.Rgba8, TileTypeMap.GetData().GetData());
image.SavePng(chunkName + "_tileType.png");
@ -157,14 +264,14 @@ public class WorldChunk : Spatial
if (NoiseTextureCheckerboardOverlay)
{
var tileTypeImage = tileTypeTexture.GetData();
Image tileTypeImage = tileTypeTexture.GetData();
tileTypeImage.Lock();
foreach (var i in Enumerable.Range(0, Size))
foreach (var j in Enumerable.Range(0, Size))
foreach (int i in Enumerable.Range(0, Size))
foreach (int j in Enumerable.Range(0, Size))
{
var textureCoord = new Vector2(i, j);
var baseColor = tileTypeImage.GetPixelv(textureCoord);
Vector2 textureCoord = new(i, j);
Color baseColor = tileTypeImage.GetPixelv(textureCoord);
if ((i + j) % 2 == 0)
tileTypeImage.SetPixelv(textureCoord, baseColor);
@ -173,7 +280,7 @@ public class WorldChunk : Spatial
}
tileTypeImage.Unlock();
var imageTexture = new ImageTexture();
ImageTexture imageTexture = new();
imageTexture.CreateFromImage(tileTypeImage, 0);
tileTypeTexture = imageTexture;
}
@ -198,4 +305,14 @@ public class WorldChunk : Spatial
PlaneRectMesh.MaterialOverride = null;
}
public void OnTileClicked(HexTile3D tile)
{
EmitSignal("TileClicked", tile);
}
public void OnTileHovered(HexTile3D tile)
{
EmitSignal("TileHovered", tile);
}
}

View File

@ -24,6 +24,8 @@ blend_mode = 3
[node name="WorldChunk" type="Spatial"]
script = ExtResource( 1 )
[node name="Tiles" type="Spatial" parent="."]
[node name="Entities" type="Spatial" parent="."]
[node name="PlaneRectMesh" type="MeshInstance" parent="."]