Adds bushes and flowers to ground tiles

Implements bush and flower spawning on ground tiles based on vegetation density.
Adds new assets for bushes and flowers, and introduces multi-mesh rendering for optimized performance.
Introduces seasonal color variations for vegetation using a shader for bushes and materials for flowers and grass.
Refactors material application into a MaterialManager to handle material assignments over multiple frames.
Moves ground tile scripts into a subfolder.
Adds floating particles to test scene.
This commit is contained in:
Dan Baker 2025-06-29 10:58:18 +01:00
parent ea5006e8a2
commit 3959333534
46 changed files with 559 additions and 77 deletions

View file

@ -3,48 +3,44 @@ extends Node
var tree_collection = preload("res://Entities/Tree/Resources/TreeData.tres") as TreeDataCollection
var tree_materials: Dictionary = {}
var grass_materials: Dictionary = {"spring": {"base": Vector3(0.196, 0.392, 0.196), "top": Vector3(0.253, 0.492, 0.253), "bottom": Vector3(0.196, 0.392, 0.196)},
"summer": {"base": null, "top": null, "bottom": null},
"autumn": {"base": null, "top": null, "bottom": null},
"winter": {"base": null, "top": null, "bottom": null}}
## TODO - Move this out into its own class
var material_application_queue: Array = []
var max_materials_per_frame: int = 10 # Adjust based on performance
var grass_materials: Dictionary = {
"spring": {
"bottom": Color(0.18, 0.35, 0.12), # #2e5920 - Dark forest base
"top": Color(0.18, 0.35, 0.12), # Color(0.35, 0.65, 0.25), # #59a640 - Bright spring green
"base": Color(0.12, 0.25, 0.08), # #1f4014 - Deep shadow green
"flower": Color(0.776, 0.835, 0.855) # rgb(198, 213, 218) - Spring flower color
},
"summer": {
"bottom": Color(0.22, 0.45, 0.18), # #38732e - Rich summer base
"top": Color(0.22, 0.45, 0.18), # Color(0.4, 0.75, 0.3), # #66bf4d - Sun-kissed green
"base": Color(0.15, 0.3, 0.12), # #264d1f - Shaded summer base
"flower": Color(0.408, 0.537, 0.886) # rgb(104, 137, 226) - Summer flower color
},
"autumn": {
"bottom": Color(0.35, 0.4, 0.15), # #596626 - Yellowing grass
"top": Color(0.42, 0.45, 0.19), # #6b7330 - Muted autumn tips
"base": Color(0.25, 0.25, 0.1), # #40401a - Dying grass base
"flower": Color(0.820, 0.337, 0.318) # rgb(209, 86, 81) - Autumn flower color
},
"winter": {
"bottom": Color(0.25, 0.3, 0.2), # #404d33 - Dormant green-brown
"top": Color(0.35, 0.4, 0.3), # #59664d - Frost-touched tips
"base": Color(0.2, 0.2, 0.15), # #333326 - Winter soil color
"flower": Color(1.0, 0.8, 0.6) # #ffd9b3 - Winter flower color
}
}
var bush_materials: Dictionary = {}
var flower_materials: Dictionary = {}
@export var tree_colours: Array
func _ready() -> void:
populate_tree_colors()
Log.pr("Tree colors populated: %d trees" % tree_materials.size())
func _process(_delta):
# Process material applications gradually over multiple frames
process_material_queue()
func process_material_queue():
var processed = 0
while material_application_queue.size() > 0 and processed < max_materials_per_frame:
var task = material_application_queue.pop_front()
# Check if the mesh instance is still valid before applying
if is_instance_valid(task.mesh_instance) and task.mesh_instance != null:
apply_material_immediately(task.mesh_instance, task.surface_index, task.material)
# If invalid, just skip this task (object was freed)
processed += 1
func queue_material_application(mesh_instance: MeshInstance3D, surface_index: int, material: StandardMaterial3D):
material_application_queue.append({
"mesh_instance": mesh_instance,
"surface_index": surface_index,
"material": material
})
func apply_material_immediately(mesh_instance: MeshInstance3D, surface_index: int, material: StandardMaterial3D):
if is_instance_valid(mesh_instance):
mesh_instance.set_surface_override_material(surface_index, material)
populate_bush_materials()
Log.pr("Bush materials populated: %d seasons" % bush_materials.size())
populate_flower_materials()
Log.pr("Flower materials populated: %d seasons" % flower_materials.size())
# Create materials once at startup
func populate_tree_colors() -> void:
@ -117,3 +113,53 @@ func get_random_trunk_material(tree_name: String, season: String) -> StandardMat
return trunk_materials[random_key]
return create_material(Color(0.6, 0.4, 0.2), "fallback_trunk")
func populate_bush_materials() -> void:
for season in Season.seasons:
bush_materials[season] = {
"1": create_bush_material(grass_materials[season]["base"], grass_materials[season]["flower"].lightened(0.1), "bush_base_%s" % season)
}
Log.pr("Bush materials populated for seasons: %s" % bush_materials.keys())
func create_bush_material(color: Color, variation: Color, material_name: String) -> ShaderMaterial:
var material = ShaderMaterial.new()
var shader = load("res://leaves.gdshader")
material.shader = shader
material.resource_name = material_name
material.set_shader_parameter("base_color", color)
material.set_shader_parameter("variation_color", variation)
return material
func get_random_bush_material(season: String) -> ShaderMaterial:
if bush_materials.has(season):
var bush_materials_season = bush_materials[season]
var material_keys = bush_materials_season.keys()
if material_keys.size() > 0:
var random_key = material_keys[randi() % material_keys.size()]
return bush_materials_season[random_key]
Log.pr("No bush materials found for season: %s, returning fallback." % season)
return create_bush_material(Color.GREEN, Color.GREEN, "fallback_bush")
func populate_flower_materials() -> void:
for season in Season.seasons:
flower_materials[season] = {
"flower": create_material(grass_materials[season]['flower'], "flower_base_%s" % season),
"stem": create_material(grass_materials[season]["top"], "flower_stem_%s" % season),
}
Log.pr("Flower materials populated for seasons: %s" % flower_materials.keys())
func get_random_flower_material(season: String) -> StandardMaterial3D:
if flower_materials.has(season):
var flower_materials_season = flower_materials[season]
var material_keys = flower_materials_season.keys()
if material_keys.size() > 0:
var random_key = material_keys[randi() % material_keys.size()]
return flower_materials_season[random_key]
Log.pr("No flower materials found for season: %s, returning fallback." % season)
return create_material(Color(1.0, 1.0, 0.5), "fallback_flower")