biome generator

This commit is contained in:
2026-04-17 15:31:28 +02:00
parent 060bccd96a
commit b77a6263aa
91 changed files with 4111 additions and 78 deletions

View File

@@ -0,0 +1,5 @@
extends Resource
class_name Biome
@export var name: String = "New Biome"
@export var available_chunks: Array[PackedScene]

View File

@@ -0,0 +1 @@
uid://wv6kcqkibium

View File

@@ -0,0 +1,302 @@
extends Node3D
@export_group("Rails")
@export var rail_path: Path3D
@export_group("Biomes")
@export var biome_list: Array[Biome]
@export_group("Grid and Area")
@export var chunk_size: float = 20.0
@export var eye_line: int = 3
@export var district_scale: float = 0.05
var board: Dictionary = {}
var last_pos_train: Vector2i = Vector2i(999999, 999999)
var noise_generator: FastNoiseLite
var altitude_generator: FastNoiseLite
var manual_biome: Biome = null
func _ready() -> void:
if biome_list.is_empty() or rail_path == null: return
noise_generator = FastNoiseLite.new()
noise_generator.noise_type = FastNoiseLite.TYPE_PERLIN
noise_generator.seed = randi()
noise_generator.frequency = district_scale
altitude_generator = FastNoiseLite.new()
altitude_generator.noise_type = FastNoiseLite.TYPE_PERLIN
altitude_generator.seed = randi()
altitude_generator.frequency = district_scale * 0.5
_update_set_pieces()
func _update_set_pieces() -> void:
var set_pieces = get_tree().get_nodes_in_group("set_pieces")
print("🔍 Found ", set_pieces.size(), " in 'set_pieces' group.")
for sp in set_pieces:
if not "exclusive_biome" in sp or sp.exclusive_biome == "":
print("⚠️ Set Piece '", sp.name, "' not have exclusive biome -> always visible.")
continue
var local_biome = ""
if manual_biome != null:
local_biome = manual_biome.nome
else:
var grid_x = roundi(sp.global_position.x / chunk_size)
var grid_z = roundi(sp.global_position.z / chunk_size)
local_biome = _get_procedural_biome_name(noise_generator.get_noise_2d(grid_x, grid_z))
print("👉 Analyze: '", sp.name, "' | Need: [", sp.exclusive_biome, "] | Current biome is: [", local_biome, "]")
if local_biome == sp.exclusive_biome:
sp.show()
sp.process_mode = Node.PROCESS_MODE_INHERIT
print(" ✅ Temple is visible.")
else:
sp.hide()
sp.process_mode = Node.PROCESS_MODE_DISABLED
print(" ❌ Temple is a invisible ghost.")
func _destroy_and_regenrate_world() -> void:
#1. Turn off old temples
_update_set_pieces()
#2. Destroy all models
for pos in board.keys():
var cella = board[pos]
if cella["tipo"] == "bioma":
if cella.has("nodo") and is_instance_valid(cella["nodo"]):
cella["nodo"].queue_free()
#3. Clean board
board.clear()
#4. Create train
last_pos_train = Vector2i(999999, 999999)
_train_radar()
last_pos_train = Vector2i(999999, 999999)
_train_radar()
func _physics_process(_delta: float) -> void:
if rail_path == null or rail_path.train_instance == null: return
var train_pos = rail_path.train_instance.global_position
var grid_x = roundi(train_pos.x / chunk_size)
var grid_z = roundi(train_pos.z / chunk_size)
var current_pos = Vector2i(grid_x, grid_z)
if current_pos != last_pos_train:
last_pos_train = current_pos
_generate_pieces_around_train(current_pos)
_clean_far_chunks(current_pos)
_train_radar()
func _generate_pieces_around_train(centro: Vector2i) -> void:
for x in range(-eye_line, eye_line + 1):
for z in range(-eye_line, eye_line + 1):
var grid_pos = centro + Vector2i(x, z)
if not board.has(grid_pos):
var ce_obstacle = _register_cell_with_laser(grid_pos)
if not ce_obstacle:
_add_compatible_biome(grid_pos)
func _choose_catalogue_by_cell(grid_pos: Vector2i) -> Array[PackedScene]:
if manual_biome != null:
return manual_biome.available_chunks
var value = noise_generator.get_noise_2d(grid_pos.x, grid_pos.y)
var normalized_value = (value + 1.0) / 2.0
var indice = clamp(int(normalized_value * biome_list.size()), 0, biome_list.size() - 1)
return biome_list[indice].available_chunks
func _get_procedural_biome_name(value: float) -> String:
if manual_biome != null:
return manual_biome.name
var normalized_value = (value + 1.0) / 2.0
var index = clamp(int(normalized_value * biome_list.size()), 0, biome_list.size() - 1)
return biome_list[index].name
func _register_cell_with_laser(grid_pos: Vector2i) -> bool:
if board.has(grid_pos): return true
var world_pos = Vector3(grid_pos.x * chunk_size, 0, grid_pos.y * chunk_size)
var space_state = get_world_3d().direct_space_state
var ray_origin = Vector3(world_pos.x, 50.0, world_pos.z)
var ray_end = Vector3(world_pos.x, -10.0, world_pos.z)
var query = PhysicsRayQueryParameters3D.create(ray_origin, ray_end)
var result = space_state.intersect_ray(query)
if result.size() > 0:
var collider = result["collider"]
var current_node = collider
var exit_found = {"north": false, "est": false, "south": false, "west": false}
var height_found = {"north": 0, "est": 0, "south": 0, "west": 0}
while current_node != null and current_node != get_tree().root:
if current_node.has_method("get_rotated_data"):
var rotation_steps = roundi(rad_to_deg(current_node.global_rotation.y) / -90.0)
var data = current_node.get_rotated_data(rotation_steps)
exit_found = data["connections"]
height_found = data["heights"]
break
current_node = current_node.get_parent()
board[grid_pos] = {"type": "obstacle", "exit": exit_found, "heights": height_found}
return true
return false
func _add_compatible_biome(grid_pos: Vector2i) -> void:
var req_conn_north = _needed_connections(grid_pos + Vector2i(0, -1), "south")
var req_conn_est = _needed_connections(grid_pos + Vector2i(1, 0), "west")
var req_conn_south = _needed_connections(grid_pos + Vector2i(0, 1), "north")
var req_conn_west = _needed_connections(grid_pos + Vector2i(-1, 0), "est")
var req_height_north = _needed_heights(grid_pos + Vector2i(0, -1), "south")
var req_height_est = _needed_heights(grid_pos + Vector2i(1, 0), "west")
var req_height_south = _needed_heights(grid_pos + Vector2i(0, 1), "north")
var req_height_west = _needed_heights(grid_pos + Vector2i(-1, 0), "est")
var height_noise = altitude_generator.get_noise_2d(grid_pos.x, grid_pos.y)
var height_target = clamp(roundi((height_noise + 1.0) / 2.0), 0, 1)
var zone_catalogue = _choose_catalogue_by_cell(grid_pos)
var valid_candidates = []
for scene in zone_catalogue:
var test_chunk = scene.instantiate()
if test_chunk.has_method("get_rotated_data"):
for rot in range(4):
var data = test_chunk.get_rotated_data(rot)
var u_conn = data["connections"]
var u_height = data["heights"]
var match_conn_n = (req_conn_north == -1) or ((req_conn_north == 1) == u_conn["north"])
var match_conn_e = (req_conn_est == -1) or ((req_conn_est == 1) == u_conn["est"])
var match_conn_s = (req_conn_south == -1) or ((req_conn_south == 1) == u_conn["south"])
var match_conn_o = (req_conn_west == -1) or ((req_conn_west == 1) == u_conn["west"])
var match_alt_n = (req_height_north == -1) or (req_height_north == u_height["north"])
var match_alt_e = (req_height_est == -1) or (req_height_est == u_height["est"])
var match_alt_s = (req_height_south == -1) or (req_height_south == u_height["south"])
var match_alt_o = (req_height_west == -1) or (req_height_west == u_height["west"])
var diff_n = abs(u_height["north"] - height_target) if req_height_north == -1 else 0
var diff_e = abs(u_height["est"] - height_target) if req_height_est == -1 else 0
var diff_s = abs(u_height["south"] - height_target) if req_height_south == -1 else 0
var diff_o = abs(u_height["west"] - height_target) if req_height_west == -1 else 0
var excessive_jumps = diff_n > 1 or diff_e > 1 or diff_s > 1 or diff_o > 1
if match_conn_n and match_conn_e and match_conn_s and match_conn_o and match_alt_n and match_alt_e and match_alt_s and match_alt_o and not excessive_jumps:
var score = 0
if req_height_north == -1 and u_height["north"] == height_target: score += 1
if req_height_est == -1 and u_height["est"] == height_target: score += 1
if req_height_south == -1 and u_height["south"] == height_target: score += 1
if req_height_west == -1 and u_height["west"] == height_target: score += 1
valid_candidates.append({"scene": scene, "rotation": rot, "data": data, "score": score})
test_chunk.queue_free()
if valid_candidates.size() > 0:
var max_score = -1
for c in valid_candidates:
if c.score > max_score: max_score = c.score
var best_candidate = []
for c in valid_candidates:
if c.score == max_score: best_candidate.append(c)
var choise = best_candidate.pick_random()
var new_chunk = choise.scene.instantiate()
add_child(new_chunk)
new_chunk.position = Vector3(grid_pos.x * chunk_size, 0, grid_pos.y * chunk_size)
new_chunk.rotation.y = choise.rotation * (-PI / 2.0)
board[grid_pos] = {"type": "biome", "exit": choise.data["connections"], "heights": choise.data["heights"], "nodo": new_chunk}
else:
push_warning("⚠️ One piece miss for position " + str(grid_pos) + "!")
var fmt_alt = func(req): return str(req) if req != -1 else "Free"
#var fmt_conn = func(req): return "Yes" if req == 1 else ("No" if req == 0 else "Free")
print("We need missed piece -> North: ", fmt_alt.call(req_height_north), " | Est: ", fmt_alt.call(req_height_est), " | South: ", fmt_alt.call(req_height_south), " | West: ", fmt_alt.call(req_height_west))
var backup = zone_catalogue[0].instantiate()
add_child(backup)
backup.position = Vector3(grid_pos.x * chunk_size, 0, grid_pos.y * chunk_size)
var safe_heights = {
"north": req_height_north if req_height_north != -1 else height_target,
"est": req_height_est if req_height_est != -1 else height_target,
"south": req_height_south if req_height_south != -1 else height_target,
"west": req_height_west if req_height_west != -1 else height_target
}
board[grid_pos] = {"type": "biome", "exit": {"north":false, "est":false, "south":false, "west":false}, "heights": safe_heights, "node": backup}
func _needed_connections(near_pos: Vector2i, side_needed: String) -> int:
if not board.has(near_pos):
_register_cell_with_laser(near_pos)
if board.has(near_pos) and board[near_pos].has("exit"):
return 1 if board[near_pos]["exit"][side_needed] else 0
return -1
func _needed_heights(near_pos: Vector2i, side_needed: String) -> int:
if board.has(near_pos) and board[near_pos].has("heights"):
return board[near_pos]["heights"][side_needed]
return -1
func _train_radar() -> void:
if rail_path == null or rail_path.curve == null or rail_path.train_instance == null: return
if manual_biome != null:
return
var train_pos = rail_path.train_instance.global_position
var grid_x = roundi(train_pos.x / chunk_size)
var grid_z = roundi(train_pos.z / chunk_size)
var current_biome = _get_procedural_biome_name(noise_generator.get_noise_2d(grid_x, grid_z))
var current_progress = rail_path.train_progress
var total_length = rail_path.curve.get_baked_length()
for step in range(1, 31):
var next_progress = wrapf(current_progress + (step * chunk_size), 0.0, total_length)
var next_local_pos = rail_path.curve.sample_baked(next_progress, true)
var next_global_pos = rail_path.to_global(next_local_pos)
var f_x = roundi(next_global_pos.x / chunk_size)
var f_z = roundi(next_global_pos.z / chunk_size)
var next_biome = _get_procedural_biome_name(noise_generator.get_noise_2d(f_x, f_z))
if next_biome != current_biome:
#distanza_cambio = step * chunk_size
#prossimo_bioma = next_biome
break
func _clean_far_chunks(centro_attuale: Vector2i) -> void:
var cells_to_remove = []
var safe_margin = 2
for grid_pos in board.keys():
var cell = board[grid_pos]
if cell["type"] == "obstacle": continue
var dist_x = abs(grid_pos.x - centro_attuale.x)
var dist_z = abs(grid_pos.y - centro_attuale.y)
if dist_x > eye_line + safe_margin or dist_z > eye_line + safe_margin:
if cell.has("node") and is_instance_valid(cell["node"]):
cell["node"].queue_free()
cells_to_remove.append(grid_pos)
for pos in cells_to_remove:
board.erase(pos)

View File

@@ -0,0 +1 @@
uid://ct2k25kslaxe5

View File

@@ -0,0 +1,6 @@
[gd_scene format=3 uid="uid://ujv2f1l4d2ps"]
[ext_resource type="Script" uid="uid://ct2k25kslaxe5" path="res://core/biome_generator/biome_generator.gd" id="1_c7ilo"]
[node name="biome_generator" type="Node3D" unique_id=1861369341]
script = ExtResource("1_c7ilo")

View File

@@ -0,0 +1,46 @@
extends Node3D
class_name ChunkInfo
@export_enum("Biome", "straight_track", "curved_track") var chunk_type: int = 0
@export_group("Set Piece Rules (Unique pieces)")
@export var exclusive_biome: String = "" # Es: "Forest"
@export_group("Base exit (no roation)")
@export var north: bool = false
@export var est: bool = false
@export var south: bool = false
@export var west: bool = false
@export_group("Maring heights (level 0, 1, 2)")
@export var height_north: int = 0
@export var height_est: int = 0
@export var height_south: int = 0
@export var height_west: int = 0
func _ready() -> void:
if exclusive_biome != "":
add_to_group("set_pieces")
func get_rotated_data(steps_90: int) -> Dictionary:
var original_exit = [north, est, south, west]
var original_height = [height_north, height_est, height_south, height_west]
var calculated_exit = []
var calculated_height = []
for i in range(4):
var index = (i - steps_90 + 4) % 4
calculated_exit.append(original_exit[index])
calculated_height.append(original_height[index])
return {
"connections": {
"north": calculated_exit[0], "est": calculated_exit[1],
"south": calculated_exit[2], "west": calculated_exit[3]
},
"heights": {
"north": calculated_height[0], "est": calculated_height[1],
"south": calculated_height[2], "west": calculated_height[3]
}
}

View File

@@ -0,0 +1 @@
uid://dg2h4kbqe8j3m

43
core/camera.tscn Normal file
View File

@@ -0,0 +1,43 @@
[gd_scene format=3 uid="uid://cv5xmnow451kl"]
[ext_resource type="Script" uid="uid://b3nmswr1iaexw" path="res://core/camera_move.gd" id="1_6doux"]
[node name="camera" type="Node3D" unique_id=310254818]
[node name="camera_front" type="Camera3D" parent="." unique_id=849013180]
transform = Transform3D(-1, 0, -8.742278e-08, 0, 1, 0, 8.742278e-08, 0, -1, 0, 2.55, 7.1)
fov = 86.0
near = 0.001
far = 5000.0
[node name="camera_top" type="Camera3D" parent="." unique_id=454024645]
transform = Transform3D(-0.9998601, 0.0062594875, -0.015508555, -0.0015509288, 0.88861585, 0.45864955, 0.016652059, 0.45860943, -0.8884819, 0, 7.4, 0.53)
fov = 82.7
near = 0.001
far = 5000.0
[node name="camera_side1" type="Camera3D" parent="." unique_id=1328559378]
transform = Transform3D(-0.18395132, 0, 0.98293537, 0, 1, 0, -0.98293537, 0, -0.18395132, -0.57, 2.257, 2.776)
fov = 60.0
[node name="camera_side2" type="Camera3D" parent="." unique_id=888646238]
transform = Transform3D(-4.371139e-08, 0.005235964, -0.9999863, 0, 0.9999863, 0.005235964, 1, 2.2887126e-10, -4.3710788e-08, 0.89, 2.207, 2.814)
fov = 96.6
far = 5000.0
[node name="camera_iso" type="Camera3D" parent="." unique_id=308091984]
transform = Transform3D(0.5778576, -0.5770964, 0.5770964, 0, 0.70710677, 0.70710677, -0.8161376, -0.40860704, 0.40860704, 225, 290, 173.7)
projection = 1
fov = 96.6
size = 35.0
near = 0.001
far = 500.0
[node name="pivot" type="Node3D" parent="." unique_id=1852377804 node_paths=PackedStringArray("camera_iso", "pivot", "camera_front", "camera_side1", "camera_side2", "camera_top")]
script = ExtResource("1_6doux")
camera_iso = NodePath("../camera_iso")
pivot = NodePath(".")
camera_front = NodePath("../camera_front")
camera_side1 = NodePath("../camera_side1")
camera_side2 = NodePath("../camera_side2")
camera_top = NodePath("../camera_top")

150
core/camera_move.gd Normal file
View File

@@ -0,0 +1,150 @@
extends Node3D
@export_group("Camera")
@export var camera_iso : Camera3D
@export var pivot : Node3D
@export var camera_front : Camera3D
@export var camera_side1 : Camera3D
@export var camera_side2 : Camera3D
@export var camera_top : Camera3D
@export_group("Camera Movememnt")
@export var move_speed : float = 20.0
@export var rotation_speed : float = 0.2
@export_group("Cinematic Mode")
@export var change_camera_time : float = 10.0
@export_group("Isometric Zoom")
@export var normal_size : float = 35.0
@export var zoom_size : float = 60.0
@export var zoom_time : float = 1.2
var is_zoomed : bool = false
var zoom_tween : Tween
var is_moving_enabled : bool = true
var is_cinematic_mode : bool = false
var cinematic_timer : float = 0.0
var cinematic_index : int = 0
var array_camera : Array[Camera3D] = []
var initial_pivot_transformation : Transform3D
func _ready():
if pivot:
initial_pivot_transformation = pivot.transform
if camera_iso: array_camera.append(camera_iso)
if camera_front: array_camera.append(camera_front)
if camera_side1: array_camera.append(camera_side1)
if camera_side2: array_camera.append(camera_side2)
if camera_top: array_camera.append(camera_top)
if camera_iso:
camera_iso.make_current()
func _input(event):
if event is InputEventKey and event.pressed and not event.echo:
#R-> Reset isometric camera
if event.keycode == KEY_R and is_moving_enabled and pivot:
pivot.transform = initial_pivot_transformation
print("Isometric camera resetted!")
return
#C-> Toggle cinematic mode
if event.keycode == KEY_C:
is_cinematic_mode = !is_cinematic_mode
if is_cinematic_mode:
cinematic_timer = 0.0
for i in range(array_camera.size()):
if array_camera[i].current:
cinematic_index = i
break
print("Cinematic Mode: enabled")
else:
print("Cinematic Mode: disabled")
return
#Z-> soft zoom
if event.keycode == KEY_Z and camera_iso:
is_zoomed = !is_zoomed
var target_size = zoom_size if is_zoomed else normal_size
if zoom_tween and zoom_tween.is_valid():
zoom_tween.kill()
zoom_tween = create_tween()
zoom_tween.set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
zoom_tween.tween_property(camera_iso, "size", target_size, zoom_time)
return
#manual controls (1-5)
var manual_action = false
if event.keycode == KEY_1 and camera_iso:
camera_iso.make_current()
is_moving_enabled = true
manual_action = true
elif event.keycode == KEY_2 and camera_front:
camera_front.make_current()
is_moving_enabled = false
manual_action = true
elif event.keycode == KEY_3 and camera_side1:
camera_side1.make_current()
is_moving_enabled = false
manual_action = true
elif event.keycode == KEY_4 and camera_side2:
camera_side2.make_current()
is_moving_enabled = false
manual_action = true
elif event.keycode == KEY_5 and camera_top:
camera_top.make_current()
is_moving_enabled = false
manual_action = true
if manual_action and is_cinematic_mode:
is_cinematic_mode = false
print("Cinematic Mode: Disabled")
if is_moving_enabled and pivot:
if event is InputEventMouseMotion and Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT):
pivot.rotate_y(deg_to_rad(-event.relative.x * rotation_speed))
func _process(delta):
if is_cinematic_mode and array_camera.size() > 0:
cinematic_timer += delta
if cinematic_timer >= change_camera_time:
cinematic_timer = 0.0
cinematic_index = (cinematic_index + 1) % array_camera.size()
var next_cam = array_camera[cinematic_index]
next_cam.make_current()
is_moving_enabled = (next_cam == camera_iso)
if not is_moving_enabled or pivot == null:
return
var input_dir = Vector3.ZERO
if Input.is_key_pressed(KEY_W): input_dir.z -= 1
if Input.is_key_pressed(KEY_S): input_dir.z += 1
if Input.is_key_pressed(KEY_A): input_dir.x -= 1
if Input.is_key_pressed(KEY_D): input_dir.x += 1
if input_dir != Vector3.ZERO:
input_dir = input_dir.normalized()
var forward = pivot.transform.basis.z
var right = pivot.transform.basis.x
forward.y = 0
right.y = 0
forward = forward.normalized()
right = right.normalized()
var motion = (forward * input_dir.z + right * input_dir.x)
pivot.position += motion * move_speed * delta

1
core/camera_move.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://b3nmswr1iaexw

View File

@@ -75,6 +75,9 @@ func _init(wind: GPUParticles3D, snow: GPUParticles3D,
set_lightning()
create_sound_players()
toggle_dust(environment_config.enable_dust)
toggle_blur(environment_config.enable_blur)
func _ready() -> void:

View File

@@ -80,8 +80,8 @@ extends Resource
#Post-process effect toggles (initial values)
@export_group("Post Process")
@export var enable_dust: bool = true #Floating dust particles overlay
@export var enable_blur: bool = true #Screen blur effect
@export var enable_dust: bool = false #Floating dust particles overlay
@export var enable_blur: bool = false #Screen blur effect
@export var enable_environment_shadows: bool = true #Cloud shadow projection on terrain
@export var blur_amount: float = 0.6

27
core/fireworks.gd Normal file
View File

@@ -0,0 +1,27 @@
extends Node3D
@onready var rocket = $"1_StartRocket"
@onready var explosion = $"2_MainExplosion"
@onready var trails = $"3_FallingTrails"
func set_color(target_color: Color) -> void:
#set colors
set_particles_node_color(rocket, Color(1.5, 1.5, 1.5))
set_particles_node_color(explosion, Color(1.2, 1.2, 1.2))
set_particles_node_color(trails, target_color)
func set_particles_node_color(node: GPUParticles3D, new_color: Color) -> void:
if node and node.draw_pass_1:
var mesh = node.draw_pass_1 as QuadMesh
if mesh and mesh.material:
var unique_mat = mesh.material.duplicate() as StandardMaterial3D
unique_mat.albedo_color = new_color
unique_mat.blend_mode = BaseMaterial3D.BLEND_MODE_ADD #Light effect (glow)
unique_mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
mesh.material = unique_mat
func turnon() -> void:
if rocket:
rocket.emitting = true
await get_tree().create_timer(6.0).timeout
queue_free()

1
core/fireworks.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://crxch6o5ottg7

104
core/fireworks.tscn Normal file
View File

@@ -0,0 +1,104 @@
[gd_scene format=3 uid="uid://dn1btv0e0ses7"]
[ext_resource type="Script" uid="uid://crxch6o5ottg7" path="res://core/fireworks.gd" id="1_cjxyo"]
[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_dp1bx"]
direction = Vector3(0, 1, 0)
spread = 0.0
initial_velocity_min = 13.0
initial_velocity_max = 14.0
gravity = Vector3(0, 0, 0)
sub_emitter_mode = 2
sub_emitter_amount_at_end = 1
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_8o1ow"]
blend_mode = 1
shading_mode = 0
albedo_color = Color(1, 0.8901961, 0.24313726, 1)
billboard_mode = 3
particles_anim_h_frames = 1
particles_anim_v_frames = 1
particles_anim_loop = false
[sub_resource type="QuadMesh" id="QuadMesh_p8l00"]
material = SubResource("StandardMaterial3D_8o1ow")
size = Vector2(0.2, 0.2)
[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_kbf61"]
direction = Vector3(0, 1, 0)
spread = 180.0
initial_velocity_min = 10.0
initial_velocity_max = 10.0
gravity = Vector3(0, 0, 0)
damping_min = 6.0000005
damping_max = 6.0000005
sub_emitter_mode = 1
sub_emitter_frequency = 30.0
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_is2bb"]
blend_mode = 1
shading_mode = 0
billboard_mode = 3
particles_anim_h_frames = 1
particles_anim_v_frames = 1
particles_anim_loop = false
[sub_resource type="QuadMesh" id="QuadMesh_f60hc"]
material = SubResource("StandardMaterial3D_is2bb")
size = Vector2(0.2, 0.2)
[sub_resource type="Gradient" id="Gradient_cjxyo"]
colors = PackedColorArray(1, 1, 1, 1, 1, 1, 1, 0)
[sub_resource type="GradientTexture1D" id="GradientTexture1D_dp1bx"]
gradient = SubResource("Gradient_cjxyo")
[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_tt4r5"]
gravity = Vector3(0, -2, 0)
scale_min = 0.5
scale_max = 0.5
color_ramp = SubResource("GradientTexture1D_dp1bx")
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_xt2tl"]
blend_mode = 1
shading_mode = 0
albedo_color = Color(0.99999994, 0.89069206, 0.2446233, 1)
billboard_mode = 3
particles_anim_h_frames = 1
particles_anim_v_frames = 1
particles_anim_loop = false
[sub_resource type="QuadMesh" id="QuadMesh_0p1ln"]
material = SubResource("StandardMaterial3D_xt2tl")
size = Vector2(0.05, 0.05)
[node name="FuocoD\'artificioRoot" type="Node3D" unique_id=669752765]
script = ExtResource("1_cjxyo")
[node name="1_StartRocket" type="GPUParticles3D" parent="." unique_id=1569398174]
emitting = false
amount = 15
sub_emitter = NodePath("../2_MainExplosion")
lifetime = 0.5
one_shot = true
explosiveness = 1.0
trail_enabled = true
process_material = SubResource("ParticleProcessMaterial_dp1bx")
draw_pass_1 = SubResource("QuadMesh_p8l00")
[node name="2_MainExplosion" type="GPUParticles3D" parent="." unique_id=863445356]
emitting = false
amount = 150
sub_emitter = NodePath("../3_FallingTrails")
lifetime = 1.5
one_shot = true
explosiveness = 1.0
process_material = SubResource("ParticleProcessMaterial_kbf61")
draw_pass_1 = SubResource("QuadMesh_f60hc")
[node name="3_FallingTrails" type="GPUParticles3D" parent="." unique_id=1969872503]
emitting = false
amount = 500
lifetime = 0.5
process_material = SubResource("ParticleProcessMaterial_tt4r5")
draw_pass_1 = SubResource("QuadMesh_0p1ln")