Chunk based world generation works.

Still missing:
- world population (trees, grass, rocks)
- chunk saving/loading
WorldChunkRefactoring
Martin Felis 2023-10-30 22:20:32 +01:00
parent d918da6fd6
commit a0a3fea598
10 changed files with 117 additions and 23 deletions

View File

@ -153,7 +153,7 @@ public class NavigationComponent : Spatial
} }
// Ensure the last point coincides with the target position // Ensure the last point coincides with the target position
if ((_planningPathWorldNavigationPoints.Last().WorldPosition - toPositionWorld).LengthSquared() < if (_planningPathWorldNavigationPoints.Count > 0 && (_planningPathWorldNavigationPoints.Last().WorldPosition - toPositionWorld).LengthSquared() <
0.5f * 0.5f) 0.5f * 0.5f)
{ {
_planningPathWorldNavigationPoints[_planningPathWorldNavigationPoints.Count - 1].WorldPosition = toPositionWorld; _planningPathWorldNavigationPoints[_planningPathWorldNavigationPoints.Count - 1].WorldPosition = toPositionWorld;

View File

@ -6,4 +6,6 @@
[resource] [resource]
shader = ExtResource( 2 ) shader = ExtResource( 2 )
shader_param/TextureSize = 4 shader_param/TextureSize = 4
shader_param/CoordinateOffsetU = null
shader_param/CoordinateOffsetV = null
shader_param/MapAlbedoTexture = ExtResource( 1 ) shader_param/MapAlbedoTexture = ExtResource( 1 )

View File

@ -4,3 +4,11 @@
[resource] [resource]
shader = ExtResource( 1 ) shader = ExtResource( 1 )
shader_param/DeepWaterColor = Color( 0, 0, 0.6, 1 )
shader_param/WaterColor = Color( 0, 0, 0.7, 1 )
shader_param/LightWaterColor = Color( 0, 0, 1, 1 )
shader_param/SandColor = Color( 0.8, 0.8, 0.1, 1 )
shader_param/GrassColor = Color( 0, 0.6, 0, 1 )
shader_param/ForestColor = Color( 0, 0.4, 0, 1 )
shader_param/RockColor = Color( 0.5, 0.5, 0.4, 1 )
shader_param/SnowColor = Color( 1, 1, 1, 1 )

View File

@ -3,4 +3,5 @@
[ext_resource path="res://materials/shader/IslandHeightmapFalloffShader.gdshader" type="Shader" id=1] [ext_resource path="res://materials/shader/IslandHeightmapFalloffShader.gdshader" type="Shader" id=1]
[resource] [resource]
render_priority = -1
shader = ExtResource( 1 ) shader = ExtResource( 1 )

View File

@ -3,6 +3,8 @@ render_mode specular_schlick_ggx, async_visible;
uniform sampler2D MapAlbedoTexture : hint_black_albedo; uniform sampler2D MapAlbedoTexture : hint_black_albedo;
uniform int TextureSize: hint_range(0, 1024, 4); uniform int TextureSize: hint_range(0, 1024, 4);
uniform int CoordinateOffsetU: hint_range(-32000, 32000);
uniform int CoordinateOffsetV: hint_range(-32000, 32000);
varying vec2 map_coord; varying vec2 map_coord;
const mat2 _HexAffineInverse = mat2(vec2(1.333333, -0.6666667), vec2(0, -1.154701)); const mat2 _HexAffineInverse = mat2(vec2(1.333333, -0.6666667), vec2(0, -1.154701));
@ -41,13 +43,12 @@ void vertex() {
vec3 origin = vec4(WORLD_MATRIX * vec4(0, 0, 0, 1)).xyz; vec3 origin = vec4(WORLD_MATRIX * vec4(0, 0, 0, 1)).xyz;
vec3 axial_coords = vec3(_HexAffineInverse * origin.xz, 0); vec3 axial_coords = vec3(_HexAffineInverse * origin.xz, 0);
map_coord = axial_to_offset(axial_coords.xy); map_coord = axial_to_offset(axial_coords.xy) - vec2(ivec2(CoordinateOffsetU, CoordinateOffsetV));
} }
void fragment() { void fragment() {
float size = float(TextureSize); float size = float(TextureSize);
vec2 texel_offset = vec2(ceil(size / 2.0)); ivec2 texel_coord = ivec2(mod(map_coord.xy, vec2(size)));
ivec2 texel_coord = ivec2(mod(map_coord.xy - texel_offset, vec2(size)));
vec4 texel_value = texelFetch(MapAlbedoTexture, texel_coord, 0); vec4 texel_value = texelFetch(MapAlbedoTexture, texel_coord, 0);
ALBEDO = texelFetch(MapAlbedoTexture, texel_coord, 0).rgb; ALBEDO = texelFetch(MapAlbedoTexture, texel_coord, 0).rgb;

View File

@ -1,28 +1,38 @@
shader_type canvas_item; shader_type canvas_item;
render_mode skip_vertex_transform; render_mode skip_vertex_transform;
uniform vec4 DeepWaterColor : hint_color = vec4(0, 0, 0.6, 1);
uniform vec4 WaterColor : hint_color = vec4(0, 0, 0.7, 1);
uniform vec4 LightWaterColor : hint_color = vec4(0, 0, 1, 1);
uniform vec4 SandColor : hint_color = vec4(0.8, 0.8, 0.1, 1);
uniform vec4 GrassColor : hint_color = vec4(0, 0.6, 0, 1);
uniform vec4 ForestColor : hint_color = vec4(0, 0.4, 0, 1);
uniform vec4 RockColor : hint_color = vec4(0.5, 0.5, 0.4, 1);
uniform vec4 SnowColor : hint_color = vec4(1);
uniform sampler2D TextureMask : hint_albedo;
void vertex() { void vertex() {
VERTEX = (WORLD_MATRIX * (EXTRA_MATRIX * vec4(VERTEX, 0.0, 1.0))).xy; VERTEX = (WORLD_MATRIX * (EXTRA_MATRIX * vec4(VERTEX, 0.0, 1.0))).xy;
} }
vec3 color_ramp(float h) { vec3 color_ramp(float h) {
if (h < 0.4f) { if (h < 0.1f) {
return vec3(0, 0, 0.6); return DeepWaterColor.rgb;
} else if (h < 0.12f) {
return WaterColor.rgb;
} else if (h < 0.45f) { } else if (h < 0.45f) {
return vec3(0, 0, 0.7); return LightWaterColor.rgb;
} else if (h < 0.5f) { // } else if (h < 0.25){
return vec3(0, 0, 1); // return SandColor.rgb;
} else if (h < 0.55){ } else if (h < 0.75){
return vec3(0.8, 0.8, 0.1); return GrassColor.rgb;
} else if (h < 0.6){ // } else if (h < 0.78){
return vec3(0, 0.6, 0); // return ForestColor.rgb;
} else if (h < 0.78){
return vec3(0, 0.4, 0);
} else if (h < 1.0){ } else if (h < 1.0){
return vec3(0.5, 0.5, 0.4); return RockColor.rgb;
} }
return vec3(1.0); return SnowColor.rgb;
} }
vec3 rgbToGrayscale(vec3 color) { vec3 rgbToGrayscale(vec3 color) {
@ -42,9 +52,11 @@ float borderFalloffCircle(vec2 uv) {
void fragment() { void fragment() {
vec4 texture_color = texture(TEXTURE, UV); vec4 texture_color = texture(TEXTURE, UV);
COLOR.rgb = vec3(abs(UV.x - 0.5) * 2.0); COLOR.rgb = vec3(abs(UV.x - 0.5) * 2.0);
// COLOR.rgb = color_ramp(texture_color.r * borderFalloffCircle(UV) * 1.8); // COLOR.rgb = color_ramp(texture_color.r * borderFalloffCircle(UV) * 1.8);
COLOR.rgb = color_ramp(texture_color.r); COLOR.rgb = color_ramp(texture_color.r * texture(TextureMask, UV).r);
COLOR.a = 0.8;
// COLOR.rgb = vec3(1, 0, 0);
} }

View File

@ -124,6 +124,7 @@ public class Game : Spatial
_player.Connect("GoldCountChanged", this, nameof(OnGoldCountChanged)); _player.Connect("GoldCountChanged", this, nameof(OnGoldCountChanged));
_worldView.Connect("TileClicked", this, nameof(OnTileClicked)); _worldView.Connect("TileClicked", this, nameof(OnTileClicked));
_worldView.Connect("TileHovered", this, nameof(OnTileHovered)); _worldView.Connect("TileHovered", this, nameof(OnTileHovered));
_worldView.Connect("OnWorldViewTileTypeImageChanged", this, nameof(OnWorldViewTileTypeImageChanged));
// register entity events // register entity events
foreach (Node node in GetNode("Entities").GetChildren()) foreach (Node node in GetNode("Entities").GetChildren())
@ -356,6 +357,20 @@ public class Game : Spatial
_heightTextureRect.Texture = newHeightTexture; _heightTextureRect.Texture = newHeightTexture;
} }
private void OnWorldViewTileTypeImageChanged(Image viewTileTypeImage)
{
ImageTexture newWorldTexture = new ImageTexture();
newWorldTexture.CreateFromImage(viewTileTypeImage,
(uint)(Texture.FlagsEnum.Mipmaps | Texture.FlagsEnum.Repeat));
_worldTextureRect.Texture = newWorldTexture;
_tileMaterial.SetShaderParam("MapAlbedoTexture", newWorldTexture);
_tileMaterial.SetShaderParam("TextureSize", (int)newWorldTexture.GetSize().x);
_tileMaterial.SetShaderParam("CoordinateOffsetU", (int) _worldView.WorldTextureCoordinateOffset.x);
_tileMaterial.SetShaderParam("CoordinateOffsetV", (int) _worldView.WorldTextureCoordinateOffset.y);
}
public void OnGoldCountChanged(int goldCount) public void OnGoldCountChanged(int goldCount)
{ {
AnimationPlayer animationPlayer = _gameUi.GetNode<AnimationPlayer>("AnimationPlayer"); AnimationPlayer animationPlayer = _gameUi.GetNode<AnimationPlayer>("AnimationPlayer");

View File

@ -10,6 +10,7 @@
[ext_resource path="res://scenes/TileWorld.tscn" type="PackedScene" id=8] [ext_resource path="res://scenes/TileWorld.tscn" type="PackedScene" id=8]
[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/WorldView.cs" type="Script" id=10] [ext_resource path="res://scenes/WorldView.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://ui/WorldGeneratorUI.gd" type="Script" id=12]
[ext_resource path="res://entities/Axe.tscn" type="PackedScene" id=14] [ext_resource path="res://entities/Axe.tscn" type="PackedScene" id=14]
[ext_resource path="res://systems/InteractionSystem.cs" type="Script" id=15] [ext_resource path="res://systems/InteractionSystem.cs" type="Script" id=15]
@ -355,9 +356,13 @@ parameters/playback = SubResource( 26 )
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 )]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -3.27709, 0, 1.02593 )
[node name="WorldView" type="Spatial" parent="."] [node name="WorldView" type="Spatial" parent="."]
script = ExtResource( 10 ) script = ExtResource( 10 )
World = NodePath("../World") World = NodePath("../World")
ShowHexTiles = true
[node name="World" type="Spatial" parent="."] [node name="World" type="Spatial" parent="."]
script = ExtResource( 7 ) script = ExtResource( 7 )

View File

@ -16,12 +16,15 @@ public class World : Spatial
Done Done
} }
public GenerationState State = GenerationState.Done; public GenerationState State = GenerationState.Done;
public Vector2 CenterChunkIndex = Vector2.Zero;
// referenced scenes // referenced scenes
private PackedScene _worldChunkScene = GD.Load<PackedScene>("res://scenes/WorldChunk.tscn"); private PackedScene _worldChunkScene = GD.Load<PackedScene>("res://scenes/WorldChunk.tscn");
// constants // constants
public const int ChunkSize = 16; public const int ChunkSize = 16;
public const int NumChunkRows = 3;
public const int NumChunkColumns = NumChunkRows;
public int Seed = 0; public int Seed = 0;
public HexGrid HexGrid = new HexGrid(); public HexGrid HexGrid = new HexGrid();
public Spatial Chunks; public Spatial Chunks;
@ -44,10 +47,7 @@ public class World : Spatial
// other members // other members
private Vector2 _centerPlaneCoord; private Vector2 _centerPlaneCoord;
private int[] _centerChunkCoord = { 0, 0 };
private int[] _previousCenterChunkCoord = { 0, 0 };
private Rect2 _centerChunkRect = new Rect2(); private Rect2 _centerChunkRect = new Rect2();
private Random _debugColorRandom = new Random();
private Godot.Collections.Dictionary<Vector2, WorldChunk> _cachedWorldChunks; private Godot.Collections.Dictionary<Vector2, WorldChunk> _cachedWorldChunks;
private List<Vector2> _activeChunkIndices = new(); private List<Vector2> _activeChunkIndices = new();
private List<Vector2> _addedChunkIndices = new(); private List<Vector2> _addedChunkIndices = new();
@ -141,6 +141,8 @@ public class World : Spatial
// set new center chunk // set new center chunk
var chunkIndex = GetChunkTupleFromPlaneCoord(planeCoord); var chunkIndex = GetChunkTupleFromPlaneCoord(planeCoord);
CenterChunkIndex = new Vector2(chunkIndex.Item1, chunkIndex.Item2);
WorldChunk currentChunk = GetOrCreateWorldChunk(chunkIndex.Item1, chunkIndex.Item2, WorldChunk currentChunk = GetOrCreateWorldChunk(chunkIndex.Item1, chunkIndex.Item2,
new Color(GD.Randf(), GD.Randf(), GD.Randf())); new Color(GD.Randf(), GD.Randf(), GD.Randf()));
_centerChunkRect = currentChunk.PlaneRect; _centerChunkRect = currentChunk.PlaneRect;
@ -159,6 +161,8 @@ public class World : Spatial
_activeChunkIndices.Add(new Vector2(chunkIndex.Item1, chunkIndex.Item2 + 1)); _activeChunkIndices.Add(new Vector2(chunkIndex.Item1, chunkIndex.Item2 + 1));
_activeChunkIndices.Add(new Vector2(chunkIndex.Item1 + 1, chunkIndex.Item2 + 1)); _activeChunkIndices.Add(new Vector2(chunkIndex.Item1 + 1, chunkIndex.Item2 + 1));
Debug.Assert(_activeChunkIndices.Count == NumChunkRows * NumChunkColumns);
foreach(Vector2 activeChunkIndex in _activeChunkIndices) foreach(Vector2 activeChunkIndex in _activeChunkIndices)
{ {
GetOrCreateWorldChunk((int) activeChunkIndex.x, (int) activeChunkIndex.y, new Color(GD.Randf(), GD.Randf(), GD.Randf())); GetOrCreateWorldChunk((int) activeChunkIndex.x, (int) activeChunkIndex.y, new Color(GD.Randf(), GD.Randf(), GD.Randf()));

View File

@ -1,6 +1,8 @@
using System.Linq; using System.Linq;
using Godot; using Godot;
using Godot.Collections; using Godot.Collections;
using Vector2 = Godot.Vector2;
using Vector3 = Godot.Vector3;
public class WorldView : Spatial public class WorldView : Spatial
{ {
@ -20,9 +22,15 @@ public class WorldView : Spatial
delegate void TileClicked(HexTile3D tile3d); delegate void TileClicked(HexTile3D tile3d);
[Signal] [Signal]
delegate void TileHovered(HexTile3D tile3d); delegate void TileHovered(HexTile3D tile3d);
[Signal]
delegate void OnWorldViewTileTypeImageChanged(Image viewTileTypeImage);
// other members // other members
public Vector2 WorldTextureCoordinateOffset = Vector2.Zero;
private World _world; private World _world;
private ImageTexture _viewTileTypeTexture;
private Image _viewTileTypeImage = new();
private class SceneTileChunk : Spatial private class SceneTileChunk : Spatial
{ {
@ -119,6 +127,42 @@ public class WorldView : Spatial
return null; return null;
} }
private void UpdateWorldViewTexture()
{
int worldChunkSize = global::World.ChunkSize;
int numWorldChunkRows = global::World.NumChunkRows;
int numWorldChunkColumns = global::World.NumChunkColumns;
_viewTileTypeImage.Create(worldChunkSize * numWorldChunkColumns, worldChunkSize * numWorldChunkRows, false, Image.Format.Rgba8);
Vector2 chunkIndexSouthWest = Vector2.Inf;
Vector2 chunkIndexNorthEast = -Vector2.Inf;
foreach (SceneTileChunk chunk in _sceneTileChunks)
{
WorldChunk worldChunk = _world.GetOrCreateWorldChunk((int) chunk.ChunkIndex.x, (int)chunk.ChunkIndex.y, Colors.White);
if (chunk.ChunkIndex.x <= chunkIndexSouthWest.x && chunk.ChunkIndex.y <= chunkIndexSouthWest.y)
{
chunkIndexSouthWest = chunk.ChunkIndex;
} else if (chunk.ChunkIndex.x >= chunkIndexNorthEast.x && chunk.ChunkIndex.y >= chunkIndexNorthEast.y)
{
chunkIndexNorthEast = chunk.ChunkIndex;
}
_viewTileTypeImage.BlendRect(
worldChunk.TileTypeOffscreenViewport.GetTexture().GetData(),
new Rect2(Vector2.Zero, Vector2.One * worldChunkSize),
(chunk.ChunkIndex - _world.CenterChunkIndex + Vector2.One) * worldChunkSize);
}
_viewTileTypeTexture = new ImageTexture();
_viewTileTypeTexture.CreateFromImage(_viewTileTypeImage);
WorldTextureCoordinateOffset = chunkIndexSouthWest * worldChunkSize;
EmitSignal("OnWorldViewTileTypeImageChanged", _viewTileTypeImage);
}
private void HandleWorldTileChange(Array<Vector2> removedChunkIndices, Array<Vector2> addedChunkIndices) private void HandleWorldTileChange(Array<Vector2> removedChunkIndices, Array<Vector2> addedChunkIndices)
{ {
Array<SceneTileChunk> removedChunks = new(); Array<SceneTileChunk> removedChunks = new();
@ -161,6 +205,8 @@ public class WorldView : Spatial
_sceneTileChunks.Add(sceneTileChunk); _sceneTileChunks.Add(sceneTileChunk);
} }
UpdateWorldViewTexture();
GD.Print("Removed Chunks " + removedChunkIndices.Count); GD.Print("Removed Chunks " + removedChunkIndices.Count);
GD.Print("Added Chunks " + addedChunkIndices.Count); GD.Print("Added Chunks " + addedChunkIndices.Count);
GD.Print("Removed chunk count: " + removedChunks.Count); GD.Print("Removed chunk count: " + removedChunks.Count);