Added SceneTileChunk that is used in WorldView to manage state needed for the visible part of a chunk.

WorldChunkRefactoring
Martin Felis 2023-10-23 21:22:53 +02:00
parent 0ce8a0ddd6
commit da09c66cb3
12 changed files with 431 additions and 44 deletions

BIN
assets/TestHeightmap.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="StreamTexture"
path.s3tc="res://.import/TestHeightmap.png-6cbcc94e211273dff5857931034e368e.s3tc.stex"
path.etc2="res://.import/TestHeightmap.png-6cbcc94e211273dff5857931034e368e.etc2.stex"
metadata={
"imported_formats": [ "s3tc", "etc2" ],
"vram_texture": true
}
[deps]
source_file="res://assets/TestHeightmap.png"
dest_files=[ "res://.import/TestHeightmap.png-6cbcc94e211273dff5857931034e368e.s3tc.stex", "res://.import/TestHeightmap.png-6cbcc94e211273dff5857931034e368e.etc2.stex" ]
[params]
compress/mode=2
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=true
flags/filter=true
flags/mipmaps=true
flags/anisotropic=false
flags/srgb=1
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
process/invert_color=false
process/normal_map_invert_y=false
stream=false
size_limit=0
detect_3d=false
svg/scale=1.0

View File

@ -0,0 +1,5 @@
[gd_resource type="StreamTexture" format=2]
[resource]
flags = 20
load_path = "res://.import/TestHeightmap.png-6cbcc94e211273dff5857931034e368e.stex"

View File

@ -25,7 +25,7 @@ flags/repeat=true
flags/filter=true
flags/mipmaps=true
flags/anisotropic=false
flags/srgb=2
flags/srgb=1
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false

View File

@ -122,6 +122,8 @@ public class Game : Spatial
_player.TaskQueueComponent.Connect("StartInteraction", _interactionSystem,
nameof(_interactionSystem.OnStartInteraction));
_player.Connect("GoldCountChanged", this, nameof(OnGoldCountChanged));
_worldView.Connect("TileClicked", this, nameof(OnTileClicked));
_worldView.Connect("TileHovered", this, nameof(OnTileHovered));
// register entity events
foreach (Node node in GetNode("Entities").GetChildren())

View File

@ -329,6 +329,7 @@ flip_v = true
visible = false
[node name="StreamContainer" parent="." instance=ExtResource( 1 )]
visible = false
ShowHexTiles = true
[node name="Camera" parent="." instance=ExtResource( 3 )]
@ -339,6 +340,9 @@ script = ExtResource( 15 )
[node name="Player" parent="." instance=ExtResource( 2 )]
TileWorldNode = NodePath("../TileWorld")
[node name="Skeleton" parent="Player/Geometry/Armature" index="0"]
bones/4/bound_children = [ ]
[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 )

View File

@ -23,7 +23,6 @@ script = ExtResource( 1 )
[node name="Mesh" type="MeshInstance" parent="." groups=["GameGeometry"]]
transform = Transform( -4.37114e-08, 0, 1, 0, 1, 0, -1, 0, -4.37114e-08, 0, -2.5, 0 )
visible = false
mesh = ExtResource( 3 )
material/0 = ExtResource( 2 )

View File

@ -5,7 +5,6 @@
[node name="Spatial" type="Spatial"]
[node name="HexTile3D1" parent="." instance=ExtResource( 1 )]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0 )
[node name="HexTile3D2" parent="." instance=ExtResource( 1 )]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0.866 )

View File

@ -2,15 +2,27 @@ using Godot;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Godot.Collections;
public class World : Spatial
{
public enum GenerationState
{
Undefined,
Heightmap,
TileType,
Objects,
Done
}
public GenerationState State = GenerationState.Done;
// referenced scenes
private PackedScene _worldChunkScene = GD.Load<PackedScene>("res://scenes/WorldChunk.tscn");
// constants
public const int ChunkSize = 16;
public int Seed = 0;
public HexGrid HexGrid = new HexGrid();
public Spatial Chunks;
public Color DebugColor;
@ -34,13 +46,15 @@ public class World : Spatial
private Vector2 _centerPlaneCoord;
private int[] _centerChunkCoord = { 0, 0 };
private int[] _previousCenterChunkCoord = { 0, 0 };
private Rect2 _centerChunkRect2 = new Rect2();
private Rect2 _centerChunkRect = new Rect2();
private Random _debugColorRandom = new Random();
private Godot.Collections.Dictionary<Vector2, WorldChunk> _cachedWorldChunks;
private List<Vector2> _activeChunkIndices = new();
private List<Vector2> _addedChunkIndices = new();
private List<Vector2> _removedChunkIndices = new();
private OpenSimplexNoise noiseGenerator = new();
public World()
{
Debug.Assert(ChunkSize % 2 == 0);
@ -53,10 +67,23 @@ public class World : Spatial
{
Chunks = (Spatial)FindNode("Chunks");
Debug.Assert(Chunks != null);
InitNoiseGenerator();
SetCenterPlaneCoord(Vector2.Zero);
}
public void InitNoiseGenerator()
{
noiseGenerator = new OpenSimplexNoise();
noiseGenerator.Seed = Seed;
noiseGenerator.Octaves = 1;
noiseGenerator.Period = 10;
noiseGenerator.Persistence = 0.5f;
noiseGenerator.Lacunarity = 2;
}
public WorldChunk GetOrCreateWorldChunk(int xIndex, int yIndex, Color debugColor)
{
if (IsTileCached(xIndex, yIndex))
@ -76,6 +103,7 @@ public class World : Spatial
private WorldChunk CreateWorldChunk(int xIndex, int yIndex, Color debugColor)
{
WorldChunk result = (WorldChunk)_worldChunkScene.Instance();
result.SetSize(ChunkSize);
Vector2 offsetCoordSouthWest = new Vector2(xIndex, yIndex) * ChunkSize;
Vector2 offsetCoordNorthEast = offsetCoordSouthWest + new Vector2(1, 1) * (ChunkSize - 1);
@ -102,6 +130,12 @@ public class World : Spatial
public void UpdateCenterChunkFromPlaneCoord(Vector2 planeCoord)
{
if (State != GenerationState.Done)
{
GD.PrintErr("Cannot update chunk to new planeCoord " + planeCoord + ": Chunk generation not yet finished!");
return;
}
// mark all chunks as retired
Godot.Collections.Dictionary<Vector2, WorldChunk> oldCachedChunks = new(_cachedWorldChunks);
@ -109,7 +143,7 @@ public class World : Spatial
var chunkIndex = GetChunkTupleFromPlaneCoord(planeCoord);
WorldChunk currentChunk = GetOrCreateWorldChunk(chunkIndex.Item1, chunkIndex.Item2,
new Color(GD.Randf(), GD.Randf(), GD.Randf()));
_centerChunkRect2 = currentChunk.PlaneRect;
_centerChunkRect = currentChunk.PlaneRect;
// load or create adjacent chunks
_activeChunkIndices = new List<Vector2>();
@ -149,8 +183,11 @@ public class World : Spatial
_addedChunkIndices.Add(chunkKey);
}
}
EmitSignal("OnTilesChanged", _removedChunkIndices.ToArray(), _addedChunkIndices.ToArray());
if (_addedChunkIndices.Count > 0)
{
State = GenerationState.Heightmap;
}
}
private void RemoveChunk(Vector2 cachedChunkKey)
@ -178,15 +215,72 @@ public class World : Spatial
public void SetCenterPlaneCoord(Vector2 centerPlaneCoord)
{
if (!_centerChunkRect2.HasPoint(centerPlaneCoord))
if (!_centerChunkRect.HasPoint(centerPlaneCoord))
{
UpdateCenterChunkFromPlaneCoord(centerPlaneCoord);
}
}
// // Called every frame. 'delta' is the elapsed time since the previous frame.
// public override void _Process(float delta)
// {
//
// }
public override void _Process(float delta)
{
GenerationState oldState = State;
if (State == GenerationState.Heightmap)
{
// generate heightmap for all new chunks
foreach (Vector2 chunkIndex in _addedChunkIndices)
{
WorldChunk chunk = _cachedWorldChunks[chunkIndex];
Color debugChunkColor = new Color(Mathf.Abs(chunkIndex.x) / 5, Mathf.Abs(chunkIndex.y) / 5, Mathf.RoundToInt(Mathf.Abs(chunkIndex.x + chunkIndex.y)) % 2);
GD.Print("Generating for offset " + chunkIndex + " chunk: " + chunk + " debugChunkColor: " + debugChunkColor);
ImageTexture noiseImageTexture = new ImageTexture();
noiseImageTexture.CreateFromImage(noiseGenerator.GetImage(ChunkSize, ChunkSize, chunkIndex * ChunkSize), (uint) 0);
// Debug Texture
Image simpleImage = new Image();
simpleImage.Create(ChunkSize, ChunkSize, false, Image.Format.Rgb8);
simpleImage.Lock();
foreach (int i in Enumerable.Range(0, ChunkSize))
{
foreach (int 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.SetHeightmap(noiseImageTexture);
}
// assign height map images
State = GenerationState.TileType;
} else if (State == GenerationState.TileType)
{
// assign tile type images
State = GenerationState.Objects;
} else if (State == GenerationState.Objects)
{
// generate objects
State = GenerationState.Done;
}
if (oldState != GenerationState.Done && State == GenerationState.Done)
{
EmitSignal("OnTilesChanged", _removedChunkIndices.ToArray(), _addedChunkIndices.ToArray());
}
}
}

View File

@ -3,21 +3,22 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
public class WorldChunk : Spatial
{
// ui elements
// scene nodes
private MeshInstance PlaneRectMesh;
// resources
// exports
[Export] public Texture TileTypeMap;
[Export] public Texture NavigationMap;
[Export] public Texture HeightMap;
[Export] public readonly int size = 32;
[Export] public int Size = 32;
[Export] public Vector2 ChunkAddress;
// [Export] public Vector2 Size = new Vector2(1, 1);
@ -28,58 +29,127 @@ public class WorldChunk : Spatial
// other members
public Rect2 PlaneRect;
public Color DebugColor = Colors.White;
public Viewport TileTypeOffscreenViewport;
public bool NoiseTextureCheckerboardOverlay = true;
private TextureRect _heightmapRect;
private SpatialMaterial _rectMaterial = new SpatialMaterial();
public WorldChunk()
{
}
public WorldChunk(int size)
{
SetSize(size);
}
public void SetSize(int size)
{
Size = size;
if (TileTypeOffscreenViewport != null)
{
TileTypeOffscreenViewport.Size = Vector2.One * size;
}
}
// other members
public void SaveToFile(String chunkName)
{
Image image = new Image();
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.CreateFromData(size, size, false, Image.Format.Rgba8, NavigationMap.GetData().GetData());
image.CreateFromData(Size, Size, false, Image.Format.Rgba8, NavigationMap.GetData().GetData());
image.SavePng(chunkName + "_navigationMap.png");
image.CreateFromData(size, size, false, Image.Format.Rgba8, HeightMap.GetData().GetData());
image.CreateFromData(Size, Size, false, Image.Format.Rgba8, HeightMap.GetData().GetData());
image.SavePng(chunkName + "_heightMap.png");
}
public void LoadFromFile(String chunkName)
{
}
// Called when the node enters the scene tree for the first time.
public override void _Ready()
{
PlaneRectMesh = (MeshInstance) FindNode("PlaneRectMesh");
PlaneRectMesh = (MeshInstance)FindNode("PlaneRectMesh");
Debug.Assert(PlaneRectMesh != null);
Transform planeRectTransform = Transform.Identity;
planeRectTransform = planeRectTransform.Scaled(new Vector3(PlaneRect.Size.x * 0.5f, 1, PlaneRect.Size.y * 0.5f));
planeRectTransform =
planeRectTransform.Scaled(new Vector3(PlaneRect.Size.x, 0.125f, PlaneRect.Size.y));
planeRectTransform.origin.x = PlaneRect.GetCenter().x;
planeRectTransform.origin.z = PlaneRect.GetCenter().y;
planeRectTransform.origin.z = PlaneRect.GetCenter().y;
PlaneRectMesh.Transform = planeRectTransform;
PlaneRectMesh.MaterialOverride = new SpatialMaterial();
((SpatialMaterial)PlaneRectMesh.MaterialOverride).AlbedoColor = DebugColor;
((SpatialMaterial)PlaneRectMesh.MaterialOverride).FlagsTransparent = true;
// PlaneRectMesh.MaterialOverride = new SpatialMaterial();
// ((SpatialMaterial)PlaneRectMesh.MaterialOverride).AlbedoColor = DebugColor;
// ((SpatialMaterial)PlaneRectMesh.MaterialOverride).FlagsTransparent = true;
TileTypeOffscreenViewport = (Viewport)FindNode("TileTypeOffscreenViewport");
TileTypeOffscreenViewport.Size = Vector2.One * Size;
Debug.Assert(TileTypeOffscreenViewport != null);
_heightmapRect = (TextureRect)FindNode("HeightmapTexture");
}
public void SetHeightmap(Texture texture)
{
GD.Print("Setting HeightmapRect Texture: " + _heightmapRect + " with size " + _heightmapRect.GetSize());
_heightmapRect.Texture = texture;
}
// // Called every frame. 'delta' is the elapsed time since the previous frame.
// public override void _Process(float delta)
// {
//
// }
}
// Called every frame. 'delta' is the elapsed time since the previous frame.
public override void _Process(float delta)
{
Texture tileTypeTexture = TileTypeOffscreenViewport.GetTexture();
if (NoiseTextureCheckerboardOverlay)
{
Image tileTypeImage = tileTypeTexture.GetData();
tileTypeImage.Lock();
foreach (int i in Enumerable.Range(0, Size))
{
foreach (int j in Enumerable.Range(0, Size))
{
Vector2 textureCoord = new Vector2(i, j);
Color baseColor = tileTypeImage.GetPixelv(textureCoord);
if ((i + j) % 2 == 0)
{
tileTypeImage.SetPixelv(textureCoord, baseColor);
}
else
{
tileTypeImage.SetPixelv(textureCoord, baseColor * 0.6f);
}
}
}
tileTypeImage.Unlock();
ImageTexture imageTexture = new ImageTexture();
imageTexture.CreateFromImage(tileTypeImage, (uint) 0);
tileTypeTexture = imageTexture;
}
_rectMaterial.AlbedoTexture = tileTypeTexture;
_rectMaterial.FlagsTransparent = true;
// _rectMaterial.AlbedoTexture = _heightmapRect.Texture;
_rectMaterial.Uv1Scale = new Vector3(-3, -2, 1);
_rectMaterial.Uv1Offset = Vector3.One * 2;
//RectMaterial.Uv1Triplanar = true;
PlaneRectMesh.SetSurfaceMaterial(0, _rectMaterial);
TileTypeOffscreenViewport.RenderTargetUpdateMode = Viewport.UpdateMode.Once;
PlaneRectMesh.MaterialOverride = null;
}
}

View File

@ -1,11 +1,29 @@
[gd_scene load_steps=4 format=2]
[gd_scene load_steps=9 format=2]
[ext_resource path="res://scenes/WorldChunk.cs" type="Script" id=1]
[ext_resource path="res://materials/IslandColorRampShader.tres" type="Material" id=2]
[ext_resource path="res://assets/TestHeightmap.tres" type="Texture" id=3]
[ext_resource path="res://assets/4x4checkerColor.png" type="Texture" id=4]
[sub_resource type="CubeMesh" id=27]
size = Vector3( 2, 0.5, 2 )
size = Vector3( 1, 1, 1 )
[sub_resource type="SpatialMaterial" id=28]
flags_transparent = true
albedo_color = Color( 1, 1, 1, 0.501961 )
albedo_texture = ExtResource( 4 )
uv1_scale = Vector3( -3, -2, 0 )
uv1_offset = Vector3( 2, 2, 0 )
[sub_resource type="OpenSimplexNoise" id=29]
octaves = 1
period = 9.0
persistence = 0.0
[sub_resource type="NoiseTexture" id=30]
width = 8
height = 8
noise = SubResource( 29 )
[node name="WorldChunk" type="Spatial"]
script = ExtResource( 1 )
@ -13,5 +31,37 @@ script = ExtResource( 1 )
[node name="Entities" type="Spatial" parent="."]
[node name="PlaneRectMesh" type="MeshInstance" parent="."]
transform = Transform( 2, 0, 0, 0, 0.125, 0, 0, 0, 2, 0, -0.1, 0 )
mesh = SubResource( 27 )
material/0 = SubResource( 28 )
[node name="TileTypeOffscreenViewport" type="Viewport" parent="."]
size = Vector2( 8, 8 )
transparent_bg = true
handle_input_locally = false
hdr = false
disable_3d = true
usage = 0
render_target_v_flip = true
render_target_update_mode = 3
shadow_atlas_quad_0 = 0
shadow_atlas_quad_1 = 0
shadow_atlas_quad_2 = 0
shadow_atlas_quad_3 = 0
[node name="NoiseTexture" type="TextureRect" parent="TileTypeOffscreenViewport"]
visible = false
margin_right = 40.0
margin_bottom = 40.0
texture = SubResource( 30 )
[node name="IslandShader" type="TextureRect" parent="TileTypeOffscreenViewport"]
material = ExtResource( 2 )
margin_right = 100.0
margin_bottom = 100.0
[node name="HeightmapTexture" type="TextureRect" parent="TileTypeOffscreenViewport/IslandShader"]
use_parent_material = true
margin_right = 100.0
margin_bottom = 100.0
texture = ExtResource( 3 )

View File

@ -1,3 +1,4 @@
using System.Linq;
using Godot;
using Godot.Collections;
@ -14,10 +15,62 @@ public class WorldView : Spatial
[Export] public Vector2 ViewCenterPlaneCoord;
// signals
// delegate void OnCoordClicked(Vector2 world_pos);
[Signal]
delegate void TileClicked(HexTile3D tile3d);
[Signal]
delegate void TileHovered(HexTile3D tile3d);
// other members
private World _world;
private class SceneTileChunk : Spatial
{
private Vector2 _chunkIndex = Vector2.Inf;
public Vector2 ChunkIndex
{
get
{
return _chunkIndex;
}
set
{
Transform chunkTransform = Transform.Identity;
Vector2 chunkOriginPlaneCoord = HexGrid.GetHexCenterFromOffset(value * global::World.ChunkSize);
chunkTransform.origin = new Vector3(chunkOriginPlaneCoord.x, 0, chunkOriginPlaneCoord.y);
Transform = chunkTransform;
_chunkIndex = value;
}
}
public Array<HexTile3D> TileNodes = new();
private PackedScene _hexTile3DScene = GD.Load<PackedScene>("res://scenes/HexTile3D.tscn");
private HexGrid HexGrid = new();
public SceneTileChunk(Vector2 chunkIndex, int size)
{
foreach (int i in Enumerable.Range(0, size))
{
foreach (int j in Enumerable.Range(0, size))
{
HexTile3D tile3D = (HexTile3D)_hexTile3DScene.Instance();
Transform tileTransform = Transform.Identity;
Vector2 centerPlaneCoord = HexGrid.GetHexCenterFromOffset(new Vector2(i, j));
tileTransform.origin = new Vector3(centerPlaneCoord.x, 0, centerPlaneCoord.y);
tile3D.Transform = tileTransform;
TileNodes.Add(tile3D);
AddChild(tile3D);
}
}
ChunkIndex = chunkIndex;
}
}
private Array<SceneTileChunk> _sceneTileChunks = new Array<SceneTileChunk>();
// Called when the node enters the scene tree for the first time.
public override void _Ready()
@ -27,14 +80,88 @@ public class WorldView : Spatial
_world.Connect("OnTilesChanged", this, nameof(HandleWorldTileChange));
}
public override void _Process(float delta)
{
}
SceneTileChunk CreateSceneTileChunk(Vector2 chunkIndex)
{
SceneTileChunk sceneTileChunk = new SceneTileChunk(chunkIndex, global::World.ChunkSize);
foreach (HexTile3D hexTile3D in sceneTileChunk.TileNodes)
{
hexTile3D.Connect("TileClicked", this, nameof(OnTileClicked));
hexTile3D.Connect("TileHovered", this, nameof(OnTileHovered));
}
return sceneTileChunk;
}
SceneTileChunk RemoveChunkFromScene(Vector2 chunkIndex)
{
foreach (Spatial child in GetChildren())
{
SceneTileChunk sceneTileChunk = child as SceneTileChunk;
if (sceneTileChunk == null)
{
RemoveChild(child);
continue;
}
if (sceneTileChunk.ChunkIndex == chunkIndex)
{
return sceneTileChunk;
}
}
return null;
}
private void HandleWorldTileChange(Array<Vector2> removedChunkIndices, Array<Vector2> addedChunkIndices)
{
Array<SceneTileChunk> removedChunks = new();
foreach (Vector2 chunkIndex in removedChunkIndices)
{
SceneTileChunk chunk = RemoveChunkFromScene(chunkIndex);
if (chunk != null)
{
removedChunks.Add(chunk);
}
}
foreach (Vector2 chunkIndex in addedChunkIndices)
{
SceneTileChunk sceneTileChunk = null;
if (removedChunks.Count > 0)
{
sceneTileChunk = removedChunks[^1];
removedChunks.RemoveAt(removedChunks.Count - 1);
GD.Print("Reused SceneTileChunk");
}
else
{
sceneTileChunk = CreateSceneTileChunk(chunkIndex);
AddChild(sceneTileChunk);
GD.Print("Created SceneTileChunk");
}
sceneTileChunk.ChunkIndex = chunkIndex;
_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);
}
}