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 // Resources
private PackedScene _tileHighlightScene; private PackedScene _tileHighlightScene;
private TileInstanceManager _tileInstanceManager;
private ShaderMaterial _tileMaterial; private ShaderMaterial _tileMaterial;
private Label _tileOffsetLabel; private Label _tileOffsetLabel;
private World _world; private World _world;
@ -46,7 +45,7 @@ public class Game : Spatial
public override void _Ready() public override void _Ready()
{ {
// debugStatsContainer // debugStatsContainer
var debugStatsContainer = (Container)FindNode("DebugStatsContainer"); Container debugStatsContainer = (Container)FindNode("DebugStatsContainer");
_framesPerSecondLabel = debugStatsContainer.GetNode<Label>("fps_label"); _framesPerSecondLabel = debugStatsContainer.GetNode<Label>("fps_label");
_centerLabel = debugStatsContainer.GetNode<Label>("center_label"); _centerLabel = debugStatsContainer.GetNode<Label>("center_label");
@ -60,7 +59,7 @@ public class Game : Spatial
_numCoordsRemovedLabel = debugStatsContainer.GetNode<Label>("num_coords_removed_label"); _numCoordsRemovedLabel = debugStatsContainer.GetNode<Label>("num_coords_removed_label");
// UI elements // UI elements
var worldGeneratorContainer = (Container)FindNode("WorldGeneratorContainer"); Container worldGeneratorContainer = (Container)FindNode("WorldGeneratorContainer");
_worldTextureRect = worldGeneratorContainer.GetNode<TextureRect>("WorldTextureRect"); _worldTextureRect = worldGeneratorContainer.GetNode<TextureRect>("WorldTextureRect");
_heightTextureRect = worldGeneratorContainer.GetNode<TextureRect>("HeightTextureRect"); _heightTextureRect = worldGeneratorContainer.GetNode<TextureRect>("HeightTextureRect");
_generateWorldButton = worldGeneratorContainer.GetNode<Button>("WorldGenerateButton"); _generateWorldButton = worldGeneratorContainer.GetNode<Button>("WorldGenerateButton");
@ -77,10 +76,9 @@ public class Game : Spatial
_cameraOffset = _camera.GlobalTranslation - _player.GlobalTranslation; _cameraOffset = _camera.GlobalTranslation - _player.GlobalTranslation;
_world = (World)FindNode("World"); _world = (World)FindNode("World");
_tileInstanceManager = (TileInstanceManager)FindNode("TileInstanceManager");
// populate UI values // populate UI values
var generatorWorldSizeSlider = worldGeneratorContainer.GetNode<Slider>("HBoxContainer/WorldSizeSlider"); Slider generatorWorldSizeSlider = worldGeneratorContainer.GetNode<Slider>("HBoxContainer/WorldSizeSlider");
// resources // resources
_tileHighlightScene = GD.Load<PackedScene>("utils/TileHighlight.tscn"); _tileHighlightScene = GD.Load<PackedScene>("utils/TileHighlight.tscn");
@ -88,7 +86,7 @@ public class Game : Spatial
Debug.Assert(_tileMaterial != null); Debug.Assert(_tileMaterial != null);
_blackWhitePatternTexture = new ImageTexture(); _blackWhitePatternTexture = new ImageTexture();
var image = new Image(); Image image = new Image();
image.Load("assets/4x4checker.png"); image.Load("assets/4x4checker.png");
_blackWhitePatternTexture.CreateFromImage(image, (uint)(Texture.FlagsEnum.Mipmaps | Texture.FlagsEnum.Repeat)); _blackWhitePatternTexture.CreateFromImage(image, (uint)(Texture.FlagsEnum.Mipmaps | Texture.FlagsEnum.Repeat));
@ -104,8 +102,8 @@ public class Game : Spatial
_player.TaskQueueComponent.Connect("StartInteraction", _interactionSystem, _player.TaskQueueComponent.Connect("StartInteraction", _interactionSystem,
nameof(_interactionSystem.OnStartInteraction)); nameof(_interactionSystem.OnStartInteraction));
_player.Connect("GoldCountChanged", this, nameof(OnGoldCountChanged)); _player.Connect("GoldCountChanged", this, nameof(OnGoldCountChanged));
_tileInstanceManager.Connect("TileClicked", this, nameof(OnTileClicked)); _world.Connect("TileClicked", this, nameof(OnTileClicked));
_tileInstanceManager.Connect("TileHovered", this, nameof(OnTileHovered)); _world.Connect("TileHovered", this, nameof(OnTileHovered));
_world.Connect("OnWorldViewTileTypeImageChanged", this, nameof(OnWorldViewTileTypeImageChanged)); _world.Connect("OnWorldViewTileTypeImageChanged", this, nameof(OnWorldViewTileTypeImageChanged));
_world.Connect("OnHeightmapImageChanged", this, nameof(OnHeightmapImageChanged)); _world.Connect("OnHeightmapImageChanged", this, nameof(OnHeightmapImageChanged));
@ -118,7 +116,7 @@ public class Game : Spatial
// perform dependency injection // perform dependency injection
//_streamContainer.SetWorld(_tileWorld);Clicked //_streamContainer.SetWorld(_tileWorld);Clicked
var worldInfoComponent = _player.GetNode<WorldInfoComponent>("WorldInfo"); WorldInfoComponent worldInfoComponent = _player.GetNode<WorldInfoComponent>("WorldInfo");
UpdateCurrentTile(); UpdateCurrentTile();
} }
@ -134,9 +132,9 @@ public class Game : Spatial
public void UpdateCurrentTile() public void UpdateCurrentTile()
{ {
// cast a ray from the camera to center // cast a ray from the camera to center
var cameraNormal = _camera.ProjectRayNormal(_camera.GetViewport().Size * 0.5f); Vector3 cameraNormal = _camera.ProjectRayNormal(_camera.GetViewport().Size * 0.5f);
var cameraPosition = _camera.ProjectRayOrigin(_camera.GetViewport().Size * 0.5f); Vector3 cameraPosition = _camera.ProjectRayOrigin(_camera.GetViewport().Size * 0.5f);
var cameraDir = cameraNormal - cameraPosition; Vector3 cameraDir = cameraNormal - cameraPosition;
Vector3 centerCoord; Vector3 centerCoord;
@ -166,13 +164,13 @@ public class Game : Spatial
UpdateCurrentTile(); UpdateCurrentTile();
var tileHighlightTransform = Transform.Identity; Transform tileHighlightTransform = Transform.Identity;
var currentTileCenter = _hexGrid.GetHexCenter(_currentTile); Vector2 currentTileCenter = _hexGrid.GetHexCenter(_currentTile);
tileHighlightTransform.origin.x = currentTileCenter.x; tileHighlightTransform.origin.x = currentTileCenter.x;
tileHighlightTransform.origin.z = currentTileCenter.y; tileHighlightTransform.origin.z = currentTileCenter.y;
_tileHighlight.Transform = tileHighlightTransform; _tileHighlight.Transform = tileHighlightTransform;
var cameraTransform = _camera.Transform; Transform cameraTransform = _camera.Transform;
cameraTransform.origin = _player.GlobalTranslation + _cameraOffset; cameraTransform.origin = _player.GlobalTranslation + _cameraOffset;
_camera.Transform = cameraTransform; _camera.Transform = cameraTransform;
} }
@ -181,7 +179,7 @@ public class Game : Spatial
public void OnGenerateButton() public void OnGenerateButton()
{ {
GD.Print("Generating"); GD.Print("Generating");
var worldSizeSlider = (Slider)FindNode("WorldSizeSlider"); Slider worldSizeSlider = (Slider)FindNode("WorldSizeSlider");
if (worldSizeSlider == null) GD.PrintErr("Could not find 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, public void OnAreaInputEvent(Node camera, InputEvent inputEvent, Vector3 position, Vector3 normal,
int shapeIndex) int shapeIndex)
{ {
var cellAtCursor = _hexGrid.GetHexAt(new Vector2(position.x, position.z)); HexCell cellAtCursor = _hexGrid.GetHexAt(new Vector2(position.x, position.z));
var highlightTransform = Transform.Identity; Transform highlightTransform = Transform.Identity;
_mouseWorldLabel.Text = position.ToString("F3"); _mouseWorldLabel.Text = position.ToString("F3");
_mouseTileOffsetLabel.Text = cellAtCursor.OffsetCoords.ToString("N"); _mouseTileOffsetLabel.Text = cellAtCursor.OffsetCoords.ToString("N");
@ -213,7 +211,7 @@ public class Game : Spatial
public void OnTileHovered(HexTile3D tile) public void OnTileHovered(HexTile3D tile)
{ {
var highlightTransform = tile.GlobalTransform; Transform highlightTransform = tile.GlobalTransform;
_mouseTileHighlight.Transform = highlightTransform; _mouseTileHighlight.Transform = highlightTransform;
_mouseWorldLabel.Text = highlightTransform.origin.ToString("F3"); _mouseWorldLabel.Text = highlightTransform.origin.ToString("F3");
_mouseTileOffsetLabel.Text = tile.OffsetCoords.ToString("N"); _mouseTileOffsetLabel.Text = tile.OffsetCoords.ToString("N");
@ -226,7 +224,7 @@ public class Game : Spatial
{ {
GD.Print("Clicked on entity at " + entity.GlobalTranslation); GD.Print("Clicked on entity at " + entity.GlobalTranslation);
var mountPoint = (Spatial)entity.FindNode("MountPoint"); Spatial mountPoint = (Spatial)entity.FindNode("MountPoint");
if (mountPoint != null) if (mountPoint != null)
{ {
_player.TaskQueueComponent.Reset(); _player.TaskQueueComponent.Reset();
@ -239,7 +237,7 @@ public class Game : Spatial
public void ResetGameState() public void ResetGameState()
{ {
var playerStartTransform = Transform.Identity; Transform playerStartTransform = Transform.Identity;
playerStartTransform.origin.y = 0; playerStartTransform.origin.y = 0;
_player.Transform = playerStartTransform; _player.Transform = playerStartTransform;
_player.TaskQueueComponent.Reset(); _player.TaskQueueComponent.Reset();
@ -250,9 +248,9 @@ public class Game : Spatial
foreach (Spatial entity in GetNode("Entities").GetChildren()) foreach (Spatial entity in GetNode("Entities").GetChildren())
{ {
var entityTransform = entity.Transform; Transform entityTransform = entity.Transform;
var entityPlanePos = new Vector2(entityTransform.origin.x, entityTransform.origin.z); Vector2 entityPlanePos = new Vector2(entityTransform.origin.x, entityTransform.origin.z);
var entityOffsetCoordinates = _hexGrid.GetHexAt(entityPlanePos).OffsetCoords; Vector2 entityOffsetCoordinates = _hexGrid.GetHexAt(entityPlanePos).OffsetCoords;
entityTransform.origin.y = 0; entityTransform.origin.y = 0;
entity.Transform = entityTransform; entity.Transform = entityTransform;
} }
@ -260,7 +258,7 @@ public class Game : Spatial
private void OnHeightmapImageChanged(Image heightmapImage) private void OnHeightmapImageChanged(Image heightmapImage)
{ {
var newHeightmapTexture = new ImageTexture(); ImageTexture newHeightmapTexture = new ImageTexture();
newHeightmapTexture.CreateFromImage(heightmapImage, newHeightmapTexture.CreateFromImage(heightmapImage,
(uint)(Texture.FlagsEnum.Mipmaps | Texture.FlagsEnum.Repeat)); (uint)(Texture.FlagsEnum.Mipmaps | Texture.FlagsEnum.Repeat));
@ -269,7 +267,7 @@ public class Game : Spatial
private void OnWorldViewTileTypeImageChanged(Image viewTileTypeImage) private void OnWorldViewTileTypeImageChanged(Image viewTileTypeImage)
{ {
var newWorldTexture = new ImageTexture(); ImageTexture newWorldTexture = new ImageTexture();
newWorldTexture.CreateFromImage(viewTileTypeImage, newWorldTexture.CreateFromImage(viewTileTypeImage,
(uint)(Texture.FlagsEnum.Mipmaps | Texture.FlagsEnum.Repeat)); (uint)(Texture.FlagsEnum.Mipmaps | Texture.FlagsEnum.Repeat));
@ -283,7 +281,7 @@ public class Game : Spatial
public void OnGoldCountChanged(int goldCount) public void OnGoldCountChanged(int goldCount)
{ {
var animationPlayer = _gameUi.GetNode<AnimationPlayer>("AnimationPlayer"); AnimationPlayer animationPlayer = _gameUi.GetNode<AnimationPlayer>("AnimationPlayer");
_goldCountLabel.Text = goldCount.ToString(); _goldCountLabel.Text = goldCount.ToString();
animationPlayer.CurrentAnimation = "FlashLabel"; animationPlayer.CurrentAnimation = "FlashLabel";
animationPlayer.Seek(0); 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://entities/Player.tscn" type="PackedScene" id=2]
[ext_resource path="res://scenes/Camera.tscn" type="PackedScene" id=3] [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://ui/DebugStatsContainer.gd" type="Script" id=6]
[ext_resource path="res://scenes/World.cs" type="Script" id=7] [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/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://entities/Chest.tscn" type="PackedScene" id=11]
[ext_resource path="res://ui/WorldGeneratorUI.gd" type="Script" id=12] [ext_resource path="res://ui/WorldGeneratorUI.gd" type="Script" id=12]
[ext_resource path="res://assets/Environment/HexTileMesh.tres" type="CylinderMesh" id=13] [ext_resource path="res://assets/Environment/HexTileMesh.tres" type="CylinderMesh" id=13]
@ -358,6 +357,7 @@ flip_v = true
visible = false visible = false
[node name="Camera" parent="." instance=ExtResource( 3 )] [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="."] [node name="InteractionSystem" type="Node" parent="."]
script = ExtResource( 15 ) script = ExtResource( 15 )
@ -370,33 +370,27 @@ WorldNode = NodePath("../World")
[node name="WorldInfo" parent="Player" index="2"] [node name="WorldInfo" parent="Player" index="2"]
WorldPath = NodePath("../../World") WorldPath = NodePath("../../World")
[node name="ToolAttachement" parent="Player/Geometry/Armature/Skeleton" index="5"] [node name="ToolAttachement" parent="Player/Geometry/PirateAsset/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 ) 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"] [node name="AnimationTree" parent="Player/Geometry" index="2"]
parameters/playback = SubResource( 26 ) parameters/playback = SubResource( 26 )
[node name="Entities" type="Spatial" parent="."] [node name="Entities" type="Spatial" parent="."]
visible = false
[node name="Axe" parent="Entities" instance=ExtResource( 14 )] [node name="Axe" parent="Entities" instance=ExtResource( 14 )]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 1.79762, 0, 0 ) transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 1.79762, 0, 0 )
input_ray_pickable = false input_ray_pickable = false
[node name="Chest" parent="Entities" instance=ExtResource( 11 )] [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="."] [node name="World" type="Spatial" parent="."]
script = ExtResource( 7 ) script = ExtResource( 7 )
[node name="Chunks" type="Spatial" parent="World"] [node name="Chunks" type="Spatial" parent="World"]
[node name="TileInstanceManager" type="Spatial" parent="World"] [node name="TileMultiMeshInstance" type="MultiMeshInstance" parent="World"]
script = ExtResource( 10 )
ShowHexTiles = true
World = NodePath("..")
[node name="TileMultiMeshInstance" type="MultiMeshInstance" parent="World/TileInstanceManager"]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -2.5, 0 ) transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -2.5, 0 )
multimesh = SubResource( 27 ) 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"] [connection signal="toggled" from="Generator Container/WorldGeneratorContainer/ShowTexturesCheckButton" to="Generator Container/WorldGeneratorContainer" method="_on_ShowTexturesCheckButton_toggled"]
[editable path="Player"] [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 // constants
public const int ChunkSize = 18; public const int ChunkSize = 12;
public const int NumChunkRows = 3; public const int NumChunkRows = 3;
public const int NumChunkColumns = NumChunkRows; public const int NumChunkColumns = NumChunkRows;
private static readonly Color RockColor = new(0.5f, 0.5f, 0.4f); 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 DarkGrassColor = new(0.05882353f, 0.5411765f, 0.05882353f);
private static readonly Color LightWaterColor = new(0.05882353f, 0.05882353f, 0.8627451f); 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 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 Image _heightmapImage = new();
private readonly List<Vector2> _removedChunkIndices = new(); private readonly List<Vector2> _removedChunkIndices = new();
private readonly Image _tileTypeMapImage = new(); private readonly Image _tileTypeMapImage = new();
private int FrameCounter;
// referenced scenes // referenced scenes
private readonly PackedScene _worldChunkScene = GD.Load<PackedScene>("res://scenes/WorldChunk.tscn"); private readonly PackedScene _worldChunkScene = GD.Load<PackedScene>("res://scenes/WorldChunk.tscn");
@ -48,7 +50,8 @@ public class World : Spatial
private OpenSimplexNoise _noiseGenerator = new(); private OpenSimplexNoise _noiseGenerator = new();
private Array<Spatial> _rockAssets; private Array<Spatial> _rockAssets;
private TileInstanceManager _tileInstanceManager; private MultiMeshInstance _tileMultiMeshInstance;
private int _usedTileMeshInstances;
private Array<Spatial> _treeAssets; private Array<Spatial> _treeAssets;
private ImageTexture _viewTileTypeTexture; private ImageTexture _viewTileTypeTexture;
public Vector2 CenterChunkIndex = Vector2.Zero; public Vector2 CenterChunkIndex = Vector2.Zero;
@ -83,6 +86,13 @@ public class World : Spatial
[Signal] [Signal]
public delegate void EntityClicked(Entity entity); public delegate void EntityClicked(Entity entity);
// signals
[Signal]
private delegate void TileClicked(HexTile3D tile3d);
[Signal]
private delegate void TileHovered(HexTile3D tile3d);
public World() public World()
{ {
Debug.Assert(ChunkSize % 2 == 0); Debug.Assert(ChunkSize % 2 == 0);
@ -96,10 +106,11 @@ public class World : Spatial
Chunks = (Spatial)FindNode("Chunks"); Chunks = (Spatial)FindNode("Chunks");
Debug.Assert(Chunks != null); Debug.Assert(Chunks != null);
_tileInstanceManager = (TileInstanceManager)FindNode("TileInstanceManager"); _tileMultiMeshInstance = (MultiMeshInstance)FindNode("TileMultiMeshInstance");
Debug.Assert(_tileInstanceManager != null); Debug.Assert(_tileMultiMeshInstance != null);
_tileInstanceManager.TileMultiMeshInstance.Multimesh.InstanceCount = _tileMultiMeshInstance.Multimesh.InstanceCount =
ChunkSize * ChunkSize * NumChunkColumns * NumChunkRows; ChunkSize * ChunkSize * NumChunkColumns * NumChunkRows;
_usedTileMeshInstances = 0;
InitNoiseGenerator(); InitNoiseGenerator();
@ -128,65 +139,72 @@ public class World : Spatial
_noiseGenerator.Lacunarity = 2; _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)]; chunk = _unusedWorldChunks.First();
return cachedChunk; _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.SetSize(ChunkSize);
result.InitializeTileInstances(chunkIndex, _tileMultiMeshInstance, _usedTileMeshInstances);
_usedTileMeshInstances += result.Tiles.GetChildCount();
var offsetCoordSouthWest = new Vector2(xIndex, yIndex) * ChunkSize; result.SetChunkIndex(chunkIndex, HexGrid);
var offsetCoordNorthEast = offsetCoordSouthWest + new Vector2(1, 1) * (ChunkSize - 1); result.UpdateTileTransforms();
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.DebugColor = debugColor; result.DebugColor = debugColor;
result.DebugColor.a = 0.6f; result.DebugColor.a = 0.6f;
Chunks.AddChild(result);
var chunkIndex = new Vector2(xIndex, yIndex);
_cachedWorldChunks.Add(chunkIndex, result); _cachedWorldChunks.Add(chunkIndex, result);
return result; return result;
} }
private bool IsColorEqualApprox(Color colorA, Color colorB) 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; 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; if (randomGenerator.NextDouble() < 1.0 - probability) return null;
var assetIndex = randomGenerator.Next(assets.Count); int assetIndex = randomGenerator.Next(assets.Count);
var assetInstance = (Spatial)assets[assetIndex].Duplicate(); Spatial assetInstance = (Spatial)assets[assetIndex].Duplicate();
var assetTransform = Transform.Identity; Transform assetTransform = Transform.Identity;
assetTransform.origin = HexGrid.GetHexCenterVec3FromOffset(offsetCoord); assetTransform.origin = HexGrid.GetHexCenterVec3FromOffset(textureCoord);
// TODO: assetTransform.origin.y = GetHeightAtOffset(offsetCoord); // TODO: assetTransform.origin.y = GetHeightAtOffset(offsetCoord);
assetTransform.origin.y = 0; assetTransform.origin.y = 0;
assetTransform.basis = assetTransform.basis =
@ -198,30 +216,30 @@ public class World : Spatial
private void PopulateChunk(WorldChunk chunk) 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(); tileTypeImage.Lock();
foreach (var textureCoordU in Enumerable.Range(0, chunk.Size)) foreach (int textureCoordU in Enumerable.Range(0, chunk.Size))
foreach (var textureCoordV in Enumerable.Range(0, chunk.Size)) foreach (int textureCoordV in Enumerable.Range(0, chunk.Size))
{ {
var colorValue = tileTypeImage.GetPixel(textureCoordU, textureCoordV); Color colorValue = tileTypeImage.GetPixel(textureCoordU, textureCoordV);
var textureCoord = new Vector2(textureCoordU, textureCoordV); Vector2 textureCoord = new(textureCoordU, textureCoordV);
var offsetCoord = chunk.ChunkIndex * ChunkSize + textureCoord; Vector2 offsetCoord = chunk.ChunkIndex * ChunkSize + textureCoord;
if (IsColorEqualApprox(colorValue, RockColor)) 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); if (rockAsset != null) chunk.Entities.AddChild(rockAsset);
// TODO: MarkCellUnwalkable(cell); // TODO: MarkCellUnwalkable(cell);
} }
else if (IsColorEqualApprox(colorValue, GrassColor) || IsColorEqualApprox(colorValue, DarkGrassColor)) 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); 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) if (treeAsset != null)
{ {
chunk.Entities.AddChild(treeAsset); chunk.Entities.AddChild(treeAsset);
@ -268,83 +286,89 @@ public class World : Spatial
return; return;
} }
GD.Print("Update Chunks: " + FrameCounter);
// mark all chunks as retired // mark all chunks as retired
Godot.Collections.Dictionary<Vector2, WorldChunk> oldCachedChunks = new(_cachedWorldChunks); Godot.Collections.Dictionary<Vector2, WorldChunk> oldCachedChunks = new(_cachedWorldChunks);
// set new center chunk // set new center chunk
var chunkIndex = GetChunkTupleFromPlaneCoord(planeCoord); CenterChunkIndex = GetChunkTupleFromPlaneCoord(planeCoord);
CenterChunkIndex = new Vector2(chunkIndex.Item1, chunkIndex.Item2);
var currentChunk = GetOrCreateWorldChunk(chunkIndex.Item1, chunkIndex.Item2, WorldChunk currentChunk = GetOrCreateWorldChunk(CenterChunkIndex,
new Color(GD.Randf(), GD.Randf(), GD.Randf())); 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 // load or create adjacent chunks
_activeChunkIndices = new List<Vector2>(); _activeChunkIndices = new List<Vector2>();
_activeChunkIndices.Add(new Vector2(chunkIndex.Item1 - 1, chunkIndex.Item2 - 1)); _activeChunkIndices.Add(CenterChunkIndex + new Vector2(-1, -1));
_activeChunkIndices.Add(new Vector2(chunkIndex.Item1, chunkIndex.Item2 - 1)); _activeChunkIndices.Add(CenterChunkIndex + new Vector2(0, -1));
_activeChunkIndices.Add(new Vector2(chunkIndex.Item1 + 1, chunkIndex.Item2 - 1)); _activeChunkIndices.Add(CenterChunkIndex + new Vector2(1, -1));
_activeChunkIndices.Add(new Vector2(chunkIndex.Item1 - 1, chunkIndex.Item2)); _activeChunkIndices.Add(CenterChunkIndex + new Vector2(-1, 0));
_activeChunkIndices.Add(new Vector2(chunkIndex.Item1, chunkIndex.Item2)); _activeChunkIndices.Add(CenterChunkIndex);
_activeChunkIndices.Add(new Vector2(chunkIndex.Item1 + 1, chunkIndex.Item2)); _activeChunkIndices.Add(CenterChunkIndex + new Vector2(+1, 0));
_activeChunkIndices.Add(new Vector2(chunkIndex.Item1 - 1, chunkIndex.Item2 + 1)); _activeChunkIndices.Add(CenterChunkIndex + new Vector2(-1, +1));
_activeChunkIndices.Add(new Vector2(chunkIndex.Item1, chunkIndex.Item2 + 1)); _activeChunkIndices.Add(CenterChunkIndex + new Vector2(0, +1));
_activeChunkIndices.Add(new Vector2(chunkIndex.Item1 + 1, chunkIndex.Item2 + 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); Debug.Assert(_activeChunkIndices.Count == NumChunkRows * NumChunkColumns);
foreach (var activeChunkIndex in _activeChunkIndices) foreach (Vector2 chunkKey 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)
if (!oldCachedChunks.ContainsKey(chunkKey)) if (!oldCachedChunks.ContainsKey(chunkKey))
{ {
_addedChunkIndices.Add(chunkKey); ActivateChunk(_cachedWorldChunks[chunkKey], chunkKey);
var chunk = _cachedWorldChunks[chunkKey];
GenerateChunkNoiseMap(chunk);
State = GenerationState.Heightmap; 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) 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, ImageTexture noiseImageTexture = new();
Mathf.RoundToInt(Mathf.Abs(chunkIndex.x + chunkIndex.y)) % 2);
var noiseImageTexture = new ImageTexture();
noiseImageTexture.CreateFromImage(_noiseGenerator.GetImage(ChunkSize, ChunkSize, chunkIndex * ChunkSize), noiseImageTexture.CreateFromImage(_noiseGenerator.GetImage(ChunkSize, ChunkSize, chunkIndex * ChunkSize),
0); 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); 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); HexCell centerOffsetCoord = HexGrid.GetHexAt(planeCoord);
var chunkIndexFloat = (centerOffsetCoord.OffsetCoords / ChunkSize).Floor(); return (centerOffsetCoord.OffsetCoords / ChunkSize).Floor();
var chunkIndex = new Tuple<int, int>((int)chunkIndexFloat.x, (int)chunkIndexFloat.y);
return chunkIndex;
} }
public void SetCenterPlaneCoord(Vector2 centerPlaneCoord) public void SetCenterPlaneCoord(Vector2 centerPlaneCoord)
@ -381,18 +403,18 @@ public class World : Spatial
private void UpdateWorldViewTexture() private void UpdateWorldViewTexture()
{ {
var worldChunkSize = ChunkSize; int worldChunkSize = ChunkSize;
var numWorldChunkRows = NumChunkRows; int numWorldChunkRows = NumChunkRows;
var numWorldChunkColumns = NumChunkColumns; int numWorldChunkColumns = NumChunkColumns;
_heightmapImage.Create(worldChunkSize * numWorldChunkColumns, worldChunkSize * numWorldChunkRows, false, _heightmapImage.Create(worldChunkSize * numWorldChunkColumns, worldChunkSize * numWorldChunkRows, false,
Image.Format.Rgba8); Image.Format.Rgba8);
_tileTypeMapImage.Create(worldChunkSize * numWorldChunkColumns, worldChunkSize * numWorldChunkRows, false, _tileTypeMapImage.Create(worldChunkSize * numWorldChunkColumns, worldChunkSize * numWorldChunkRows, false,
Image.Format.Rgba8); 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( _heightmapImage.BlendRect(
worldChunk.HeightmapOffscreenViewport.GetTexture().GetData(), worldChunk.HeightmapOffscreenViewport.GetTexture().GetData(),
@ -422,9 +444,9 @@ public class World : Spatial
_chunkIndexSouthWest = Vector2.Inf; _chunkIndexSouthWest = Vector2.Inf;
_chunkIndexNorthEast = -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) if (chunkIndex.x <= _chunkIndexSouthWest.x && chunkIndex.y <= _chunkIndexSouthWest.y)
_chunkIndexSouthWest = chunkIndex; _chunkIndexSouthWest = chunkIndex;
@ -435,71 +457,79 @@ public class World : Spatial
private void UpdateNavigationBounds() 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 // 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. // 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()); 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)); HexGrid.SetBoundsOffset(cellSouthWest, ChunkSize * new Vector2(NumChunkColumns, NumChunkRows));
} }
public override void _Process(float delta) public override void _Process(float delta)
{ {
var oldState = State; GenerationState oldState = State;
UpdateGenerationState(); UpdateGenerationState();
if (oldState != GenerationState.Done && State == GenerationState.Done) if (oldState != GenerationState.Done && State == GenerationState.Done)
{
UpdateWorldViewTexture(); UpdateWorldViewTexture();
EmitSignal("OnTilesChanged", _removedChunkIndices.ToArray(), _addedChunkIndices.ToArray());
}
} }
private void UpdateGenerationState() private void UpdateGenerationState()
{ {
FrameCounter++;
if (State == GenerationState.Heightmap) if (State == GenerationState.Heightmap)
{ {
var numChunksGeneratingHeightmap = 0; int numChunksGeneratingHeightmap = 0;
foreach (var chunkIndex in _addedChunkIndices) foreach (Vector2 chunkIndex in _addedChunkIndices)
{ {
var chunk = _cachedWorldChunks[chunkIndex]; WorldChunk chunk = _cachedWorldChunks[chunkIndex];
if (chunk.HeightMapFrameCount > 0) numChunksGeneratingHeightmap++; if (chunk.HeightMapFrameCount > 0) numChunksGeneratingHeightmap++;
} }
if (numChunksGeneratingHeightmap == 0) if (numChunksGeneratingHeightmap == 0)
{ {
// assign height map images // 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()); chunk.SetHeightmap(chunk.HeightmapOffscreenViewport.GetTexture());
} }
GD.Print("Switching to TileType Generation: " + FrameCounter);
State = GenerationState.TileType; State = GenerationState.TileType;
} }
} }
else if (State == GenerationState.TileType) else if (State == GenerationState.TileType)
{ {
var numChunksGeneratingTileType = 0; int numChunksGeneratingTileType = 0;
foreach (var chunkIndex in _addedChunkIndices) foreach (Vector2 chunkIndex in _addedChunkIndices)
{ {
var chunk = _cachedWorldChunks[chunkIndex]; WorldChunk chunk = _cachedWorldChunks[chunkIndex];
if (chunk.TileTypeMapFrameCount > 0) numChunksGeneratingTileType++; 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) else if (State == GenerationState.Objects)
{ {
// generate 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; State = GenerationState.Done;
} }
} }
@ -508,4 +538,14 @@ public class World : Spatial
{ {
EmitSignal("EntityClicked", entity); 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.Diagnostics;
using System.Linq; using System.Linq;
using Godot; using Godot;
using Godot.Collections;
public class WorldChunk : Spatial 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 readonly SpatialMaterial _rectMaterial = new();
private Sprite _heightmapSprite; private Sprite _heightmapSprite;
private TextureRect _heightmapTextureRect; private TextureRect _heightmapTextureRect;
@ -11,9 +16,15 @@ public class WorldChunk : Spatial
private Sprite _noiseSprite; private Sprite _noiseSprite;
private bool _showTextureOverlay; private bool _showTextureOverlay;
[Export] public Vector2 ChunkIndex; [Export] public Vector2 ChunkIndex;
public Color DebugColor = Colors.White; 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; [Export] public Texture HeightMap;
public int HeightMapFrameCount; public int HeightMapFrameCount;
@ -23,10 +34,15 @@ public class WorldChunk : Spatial
public bool NoiseTextureCheckerboardOverlay = true; public bool NoiseTextureCheckerboardOverlay = true;
// signals // signals
// delegate void OnCoordClicked(Vector2 world_pos); [Signal]
private delegate void TileClicked(HexTile3D tile3d);
[Signal]
private delegate void TileHovered(HexTile3D tile3d);
// other members // other members
public Rect2 PlaneRect; public Rect2 PlaneRect;
// ui elements // ui elements
// scene nodes // scene nodes
@ -67,7 +83,7 @@ public class WorldChunk : Spatial
Debug.Assert(PlaneRectMesh != null); Debug.Assert(PlaneRectMesh != null);
if (PlaneRectMesh.Visible) _showTextureOverlay = true; if (PlaneRectMesh.Visible) _showTextureOverlay = true;
var planeRectTransform = Transform.Identity; Transform planeRectTransform = Transform.Identity;
planeRectTransform = planeRectTransform =
planeRectTransform.Scaled(new Vector3(PlaneRect.Size.x, 0.125f, PlaneRect.Size.y)); planeRectTransform.Scaled(new Vector3(PlaneRect.Size.x, 0.125f, PlaneRect.Size.y));
planeRectTransform.origin.x = PlaneRect.GetCenter().x; planeRectTransform.origin.x = PlaneRect.GetCenter().x;
@ -94,6 +110,9 @@ public class WorldChunk : Spatial
Entities = (Spatial)FindNode("Entities"); Entities = (Spatial)FindNode("Entities");
Debug.Assert(Entities != null); Debug.Assert(Entities != null);
Tiles = (Spatial)FindNode("Tiles");
Debug.Assert(Tiles != null);
SetSize(World.ChunkSize); 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 // other members
public void SaveToFile(string chunkName) public void SaveToFile(string chunkName)
{ {
var image = new Image(); Image image = new();
image.CreateFromData(Size, Size, false, Image.Format.Rgba8, TileTypeMap.GetData().GetData()); image.CreateFromData(Size, Size, false, Image.Format.Rgba8, TileTypeMap.GetData().GetData());
image.SavePng(chunkName + "_tileType.png"); image.SavePng(chunkName + "_tileType.png");
@ -157,14 +264,14 @@ public class WorldChunk : Spatial
if (NoiseTextureCheckerboardOverlay) if (NoiseTextureCheckerboardOverlay)
{ {
var tileTypeImage = tileTypeTexture.GetData(); Image tileTypeImage = tileTypeTexture.GetData();
tileTypeImage.Lock(); tileTypeImage.Lock();
foreach (var i in Enumerable.Range(0, Size)) foreach (int i in Enumerable.Range(0, Size))
foreach (var j in Enumerable.Range(0, Size)) foreach (int j in Enumerable.Range(0, Size))
{ {
var textureCoord = new Vector2(i, j); Vector2 textureCoord = new(i, j);
var baseColor = tileTypeImage.GetPixelv(textureCoord); Color baseColor = tileTypeImage.GetPixelv(textureCoord);
if ((i + j) % 2 == 0) if ((i + j) % 2 == 0)
tileTypeImage.SetPixelv(textureCoord, baseColor); tileTypeImage.SetPixelv(textureCoord, baseColor);
@ -173,7 +280,7 @@ public class WorldChunk : Spatial
} }
tileTypeImage.Unlock(); tileTypeImage.Unlock();
var imageTexture = new ImageTexture(); ImageTexture imageTexture = new();
imageTexture.CreateFromImage(tileTypeImage, 0); imageTexture.CreateFromImage(tileTypeImage, 0);
tileTypeTexture = imageTexture; tileTypeTexture = imageTexture;
} }
@ -198,4 +305,14 @@ public class WorldChunk : Spatial
PlaneRectMesh.MaterialOverride = null; 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"] [node name="WorldChunk" type="Spatial"]
script = ExtResource( 1 ) script = ExtResource( 1 )
[node name="Tiles" type="Spatial" parent="."]
[node name="Entities" type="Spatial" parent="."] [node name="Entities" type="Spatial" parent="."]
[node name="PlaneRectMesh" type="MeshInstance" parent="."] [node name="PlaneRectMesh" type="MeshInstance" parent="."]