Implements procedural ground tile generation

Adds procedural ground tile generation with chunking for improved performance.

Includes:
- Ground tile entity with debug text and cell information
- Grass and tree placement based on cell data
- Ground shader for visual representation
- Chunk loading and unloading system based on player position
This commit is contained in:
Dan Baker 2025-06-24 13:14:21 +01:00
parent 95665f54eb
commit b5bf7619e6
21 changed files with 532 additions and 149 deletions

View file

@ -0,0 +1,16 @@
[gd_resource type="ShaderMaterial" load_steps=3 format=3 uid="uid://b1miqvl8lus75"]
[ext_resource type="Shader" uid="uid://dbduq0qcaxmyi" path="res://grass.gdshader" id="1_vnnwo"]
[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_vnnwo"]
[resource]
render_priority = 0
shader = ExtResource("1_vnnwo")
shader_parameter/top_color = Color(0.102963, 0.465909, 0.382031, 1)
shader_parameter/bottom_color = Color(0.121494, 0.374954, 0.315972, 1)
shader_parameter/ambient_occlusion_factor = 0.0
shader_parameter/specular_strength = 0.0
shader_parameter/player_displacement_strength = 1.0
shader_parameter/player_displacement_size = 1.0
shader_parameter/wind_noise = SubResource("NoiseTexture2D_vnnwo")

View file

@ -1,4 +1,4 @@
[gd_scene load_steps=65 format=4 uid="uid://bwsugg4p50fjr"]
[gd_scene load_steps=62 format=4 uid="uid://bwsugg4p50fjr"]
[ext_resource type="Environment" uid="uid://cm77bbr0io118" path="res://Stages/Test3D/new_environment.tres" id="1_8ph61"]
[ext_resource type="Script" uid="uid://bwed2dwogfmxv" path="res://Entities/Player/scripts/player.gd" id="1_d602n"]
@ -7,7 +7,7 @@
[ext_resource type="Shader" uid="uid://bsemnmdracd4m" path="res://Common/shaders/outline.gdshader" id="4_feu7y"]
[ext_resource type="PackedScene" uid="uid://c66kiiacr2bym" path="res://Stages/Test3D/assets/grass.glb" id="6_8ph61"]
[ext_resource type="PackedScene" uid="uid://dg6v7qap28a5o" path="res://Stages/Test3D/assets/rock-a.glb" id="7_8537y"]
[ext_resource type="Shader" uid="uid://dbduq0qcaxmyi" path="res://grass.gdshader" id="7_hvb1l"]
[ext_resource type="Material" uid="uid://b1miqvl8lus75" path="res://Stages/Test3D/GrassMaterialOverride.tres" id="7_caaui"]
[ext_resource type="PackedScene" uid="uid://ctw4ktjdpiva7" path="res://Stages/Test3D/assets/rock-b.glb" id="8_h8fbl"]
[ext_resource type="ArrayMesh" uid="uid://r2imjo7o2734" path="res://Stages/Test3D/assets/stylizedGrassMeshes/grass2.res" id="8_xvexm"]
[ext_resource type="PackedScene" uid="uid://nsnthin0ekva" path="res://Stages/Test3D/assets/rock-c.glb" id="9_rt72s"]
@ -40,7 +40,8 @@ script/source = "@tool
extends DirectionalLight3D
func _process(_delta):
(%PostProcessing as MeshInstance3D).mesh.surface_get_material(0).set_shader_parameter('light_direction', -global_basis.z)
pass
#(%PostProcessing as MeshInstance3D).mesh.surface_get_material(0).set_shader_parameter('light_direction', -global_basis.z)
"
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_tfa5t"]
@ -374,26 +375,6 @@ material = SubResource("ShaderMaterial_tfa5t")
flip_faces = true
size = Vector2(2, 2)
[sub_resource type="FastNoiseLite" id="FastNoiseLite_hvb1l"]
[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_xvexm"]
noise = SubResource("FastNoiseLite_hvb1l")
[sub_resource type="ShaderMaterial" id="ShaderMaterial_ukp6m"]
render_priority = 0
shader = ExtResource("7_hvb1l")
shader_parameter/top_color = Color(0.415686, 0.694118, 0.266667, 1)
shader_parameter/bottom_color = Color(0.176471, 0.333333, 0.0941176, 1)
shader_parameter/ambient_occlusion_factor = 0.5
shader_parameter/specular_strength = 0.4
shader_parameter/player_displacement_strength = 1.0
shader_parameter/player_displacement_size = 0.53
shader_parameter/wind_direction = Vector3(0.4, -0.3, 0.81)
shader_parameter/wind_strength = 0.3
shader_parameter/wind_noise = SubResource("NoiseTexture2D_xvexm")
shader_parameter/wind_noise_size = 0.05
shader_parameter/wind_noise_speed = 0.1
[sub_resource type="MultiMesh" id="MultiMesh_hvb1l"]
transform_format = 1
instance_count = 10000
@ -785,7 +766,8 @@ script = ExtResource("2_sdmks")
transform = Transform3D(1, 0, 0, 0, 1, -1.49012e-07, 0, 1.19209e-07, 1, 0.0410548, 0.237644, 3.45114)
projection = 1
current = true
size = 3.0
size = 4.0
near = 0.01
far = 100.0
[node name="PostProcessing" type="MeshInstance3D" parent="SubViewportContainer/SubViewport/Player/CameraPivot/Camera3D"]
@ -803,7 +785,7 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.247314, 0, 0.404175)
[node name="MultiMesh3D" type="MultiMeshInstance3D" parent="SubViewportContainer/SubViewport/Environment"]
unique_name_in_owner = true
material_override = SubResource("ShaderMaterial_ukp6m")
material_override = ExtResource("7_caaui")
cast_shadow = 0
multimesh = SubResource("MultiMesh_hvb1l")
@ -850,7 +832,7 @@ draw_pass_1 = SubResource("QuadMesh_hvb1l")
[node name="OmniLight3D" type="OmniLight3D" parent="SubViewportContainer/SubViewport/VFX/Fire"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.000509977, 0.121094, -0.00151992)
light_color = Color(0.89, 0.461613, 0.2136, 1)
light_energy = 0.779335
light_energy = 0.763335
light_indirect_energy = 1.084
light_volumetric_fog_energy = 3.764
light_size = 0.105

View file

@ -3,108 +3,108 @@ extends Node3D
@export var map_width: int = 500
@export var map_height: int = 500
@export var tile_size: float = 2.0
@export var chunk_size: int = 2
@export var chunk_size: int = 5
@export var view_distance: int = 2
@export var tiles_per_frame: int = 1 # How many tiles to create per frame
@export var tiles_per_frame: int = 5 # How many tiles to create per frame
var loaded_chunks: Dictionary = {}
var player_position: Vector3
var last_chunk_pos: Vector2i = Vector2i(-999, -999)
var loading_chunks: Dictionary = {} # Track chunks currently being loaded
func _ready() -> void:
Log.pr('Testing optimized tile map generation')
create_tiles_chunked()
Log.pr('Testing optimized tile map generation')
create_tiles_chunked()
func create_tiles_chunked() -> void:
Log.pr('Setting up chunked tile system...')
var player = %Player
if player:
player_position = player.global_position
update_chunks(player_position)
else:
Log.pr("Player not found, loading chunks around origin")
update_chunks(Vector3.ZERO)
Log.pr('Setting up chunked tile system...')
var player = %Player
if player:
player_position = player.global_position
update_chunks(player_position)
else:
Log.pr("Player not found, loading chunks around origin")
update_chunks(Vector3.ZERO)
func update_chunks(player_pos: Vector3) -> void:
player_position = player_pos
var current_chunk = Vector2i(
int(player_pos.x / (chunk_size * tile_size)),
int(player_pos.z / (chunk_size * tile_size))
)
if current_chunk == last_chunk_pos:
return
last_chunk_pos = current_chunk
Log.pr("CHUNK CHANGE - New chunk: ", current_chunk)
# Determine which chunks should be loaded
var chunks_to_load: Array[Vector2i] = []
for x in range(-view_distance, view_distance + 1):
for z in range(-view_distance, view_distance + 1):
chunks_to_load.append(current_chunk + Vector2i(x, z))
# LOADING new chunks
Log.pr("Chunks to load: ", chunks_to_load.size())
for chunk_pos in chunks_to_load:
if chunk_pos not in loaded_chunks and chunk_pos not in loading_chunks:
Log.pr("Loading new: ", chunk_pos)
loading_chunks[chunk_pos] = true
load_chunk_async(chunk_pos)
# UNLOADING far chunks
var chunks_to_unload: Array[Vector2i] = []
for chunk_pos in loaded_chunks.keys():
if chunk_pos not in chunks_to_load:
chunks_to_unload.append(chunk_pos)
Log.pr("Chunks to unload: ", chunks_to_unload.size())
for chunk_pos in chunks_to_unload:
Log.pr("Unloading: ", chunk_pos)
unload_chunk(chunk_pos)
player_position = player_pos
var current_chunk = Vector2i(
int(player_pos.x / (chunk_size * tile_size)),
int(player_pos.z / (chunk_size * tile_size))
)
if current_chunk == last_chunk_pos:
return
last_chunk_pos = current_chunk
Log.pr("CHUNK CHANGE - New chunk: ", current_chunk)
# Determine which chunks should be loaded
var chunks_to_load: Array[Vector2i] = []
for x in range(-view_distance, view_distance + 1):
for z in range(-view_distance, view_distance + 1):
chunks_to_load.append(current_chunk + Vector2i(x, z))
# LOADING new chunks
Log.pr("Chunks to load: ", chunks_to_load.size())
for chunk_pos in chunks_to_load:
if chunk_pos not in loaded_chunks and chunk_pos not in loading_chunks:
Log.pr("Loading new: ", chunk_pos)
loading_chunks[chunk_pos] = true
load_chunk_async(chunk_pos)
# UNLOADING far chunks
var chunks_to_unload: Array[Vector2i] = []
for chunk_pos in loaded_chunks.keys():
if chunk_pos not in chunks_to_load:
chunks_to_unload.append(chunk_pos)
Log.pr("Chunks to unload: ", chunks_to_unload.size())
for chunk_pos in chunks_to_unload:
Log.pr("Unloading: ", chunk_pos)
unload_chunk(chunk_pos)
func load_chunk_async(chunk_pos: Vector2i) -> void:
if not ground_tile:
Log.pr("ground_tile is null!")
loading_chunks.erase(chunk_pos)
return
var chunk_node = Node3D.new()
chunk_node.name = "Chunk_%d_%d" % [chunk_pos.x, chunk_pos.y]
add_child(chunk_node)
var start_x = chunk_pos.x * chunk_size * tile_size - (chunk_size * tile_size) / 2.0
var start_z = chunk_pos.y * chunk_size * tile_size - (chunk_size * tile_size) / 2.0
var tiles_created = 0
var tiles_this_frame = 0
for x in range(chunk_size):
for z in range(chunk_size):
var tile_instance = ground_tile.instantiate()
tile_instance.set_grid_location((chunk_pos.x * chunk_size) + x, (chunk_pos.y * chunk_size) + z)
tile_instance.position = Vector3(
start_x + (x * tile_size),
0,
start_z + (z * tile_size)
)
chunk_node.add_child(tile_instance)
tiles_created += 1
tiles_this_frame += 1
# Yield after creating a certain number of tiles
if tiles_this_frame >= tiles_per_frame:
tiles_this_frame = 0
await get_tree().process_frame
Log.pr("Created ", tiles_created, " tiles in chunk ", chunk_pos)
loaded_chunks[chunk_pos] = chunk_node
loading_chunks.erase(chunk_pos)
if not ground_tile:
Log.pr("ground_tile is null!")
loading_chunks.erase(chunk_pos)
return
var chunk_node = Node3D.new()
chunk_node.name = "Chunk_%d_%d" % [chunk_pos.x, chunk_pos.y]
add_child(chunk_node)
var start_x = chunk_pos.x * chunk_size * tile_size - (chunk_size * tile_size) / 2.0
var start_z = chunk_pos.y * chunk_size * tile_size - (chunk_size * tile_size) / 2.0
var tiles_created = 0
var tiles_this_frame = 0
for x in range(chunk_size):
for z in range(chunk_size):
var tile_instance = ground_tile.instantiate()
tile_instance.set_grid_location((chunk_pos.x * chunk_size) + x, (chunk_pos.y * chunk_size) + z)
tile_instance.position = Vector3(
start_x + (x * tile_size),
0,
start_z + (z * tile_size)
)
chunk_node.add_child(tile_instance)
tiles_created += 1
tiles_this_frame += 1
# Yield after creating a certain number of tiles
if tiles_this_frame >= tiles_per_frame:
tiles_this_frame = 0
await get_tree().process_frame
Log.pr("Created ", tiles_created, " tiles in chunk ", chunk_pos)
loaded_chunks[chunk_pos] = chunk_node
loading_chunks.erase(chunk_pos)
func unload_chunk(chunk_pos: Vector2i) -> void:
if chunk_pos in loaded_chunks:
Log.pr("Unloading chunk: ", chunk_pos)
loaded_chunks[chunk_pos].queue_free()
loaded_chunks.erase(chunk_pos)
if chunk_pos in loaded_chunks:
Log.pr("Unloading chunk: ", chunk_pos)
loaded_chunks[chunk_pos].queue_free()
loaded_chunks.erase(chunk_pos)