feat: added rocket noises and smoke trails and adjusted AoE of rockets and damage falloff on AoE
This commit is contained in:
@@ -14,7 +14,7 @@ func _physics_process(delta: float) -> void:
|
||||
fuse_time -= delta
|
||||
if fuse_time <= 0.0:
|
||||
_explode(global_position)
|
||||
queue_free()
|
||||
destroy()
|
||||
return
|
||||
|
||||
# Apply gravity drop if any
|
||||
|
||||
@@ -1,16 +1,141 @@
|
||||
extends Projectile
|
||||
class_name ExplosiveProjectile
|
||||
|
||||
var explosion_radius: float = 5.0
|
||||
var explosion_radius: float = 2.5
|
||||
var explosion_knockback: float = 20.0
|
||||
var can_self_damage: bool = false
|
||||
var falloff_curve: float = 1.0 # 1.0 is linear, 2.0 is quadratic (steep drop)
|
||||
var flight_sound: AudioStreamPlayer3D
|
||||
var smoke_sys: GPUParticles3D
|
||||
var jet_sys: GPUParticles3D
|
||||
|
||||
static var _smoke_tex: GradientTexture2D
|
||||
|
||||
static func get_smoke_texture() -> GradientTexture2D:
|
||||
if _smoke_tex: return _smoke_tex
|
||||
_smoke_tex = GradientTexture2D.new()
|
||||
_smoke_tex.fill = GradientTexture2D.FILL_RADIAL
|
||||
_smoke_tex.fill_from = Vector2(0.5, 0.5)
|
||||
_smoke_tex.fill_to = Vector2(1.0, 1.0)
|
||||
|
||||
var g = Gradient.new()
|
||||
g.add_point(0.0, Color(1, 1, 1, 1))
|
||||
g.add_point(0.5, Color(1, 1, 1, 0.5))
|
||||
g.add_point(1.0, Color(1, 1, 1, 0))
|
||||
_smoke_tex.gradient = g
|
||||
_smoke_tex.width = 64
|
||||
_smoke_tex.height = 64
|
||||
return _smoke_tex
|
||||
|
||||
func _ready() -> void:
|
||||
super._ready()
|
||||
_setup_effects()
|
||||
|
||||
func destroy() -> void:
|
||||
if is_instance_valid(smoke_sys):
|
||||
smoke_sys.emitting = false
|
||||
var p = smoke_sys.get_parent()
|
||||
if p:
|
||||
var trans = smoke_sys.global_transform
|
||||
p.remove_child(smoke_sys)
|
||||
get_tree().current_scene.add_child(smoke_sys)
|
||||
smoke_sys.global_transform = trans
|
||||
var timer = smoke_sys.get_tree().create_timer(smoke_sys.lifetime)
|
||||
timer.timeout.connect(smoke_sys.queue_free)
|
||||
|
||||
if is_instance_valid(jet_sys):
|
||||
jet_sys.emitting = false
|
||||
var p = jet_sys.get_parent()
|
||||
if p:
|
||||
var trans = jet_sys.global_transform
|
||||
p.remove_child(jet_sys)
|
||||
get_tree().current_scene.add_child(jet_sys)
|
||||
jet_sys.global_transform = trans
|
||||
var timer = jet_sys.get_tree().create_timer(jet_sys.lifetime)
|
||||
timer.timeout.connect(jet_sys.queue_free)
|
||||
|
||||
super.destroy()
|
||||
|
||||
func _setup_effects() -> void:
|
||||
# Audio
|
||||
flight_sound = AudioStreamPlayer3D.new()
|
||||
flight_sound.stream = load("res://assets/sounds/rocket_flight.wav")
|
||||
flight_sound.autoplay = true
|
||||
if flight_sound.stream is AudioStreamWAV:
|
||||
flight_sound.stream.loop_mode = AudioStreamWAV.LOOP_FORWARD
|
||||
flight_sound.unit_size = 50.0
|
||||
flight_sound.max_db = 6.0
|
||||
flight_sound.volume_db = 6.0
|
||||
add_child(flight_sound)
|
||||
flight_sound.play()
|
||||
|
||||
# Jet Fire Particles
|
||||
jet_sys = GPUParticles3D.new()
|
||||
var jet_mat = ParticleProcessMaterial.new()
|
||||
jet_mat.direction = Vector3(0, 0, 1) # Backwards (since model looks at -Z)
|
||||
jet_mat.spread = 10.0
|
||||
jet_mat.initial_velocity_min = 1.0
|
||||
jet_mat.initial_velocity_max = 2.0
|
||||
jet_mat.gravity = Vector3.ZERO
|
||||
|
||||
var jet_mesh = BoxMesh.new()
|
||||
jet_mesh.size = Vector3(0.05, 0.05, 0.05)
|
||||
var mat = StandardMaterial3D.new()
|
||||
mat.albedo_color = Color(1.0, 0.6, 0.0)
|
||||
mat.emission_enabled = true
|
||||
mat.emission = Color(1.0, 0.5, 0.0)
|
||||
jet_mesh.material = mat
|
||||
jet_sys.draw_pass_1 = jet_mesh
|
||||
jet_sys.process_material = jet_mat
|
||||
jet_sys.amount = 16
|
||||
jet_sys.lifetime = 0.2
|
||||
jet_sys.position = Vector3(0, 0, 0.1)
|
||||
add_child(jet_sys)
|
||||
|
||||
# Smoke Trail
|
||||
smoke_sys = GPUParticles3D.new()
|
||||
var smoke_mat = ParticleProcessMaterial.new()
|
||||
smoke_mat.direction = Vector3(0, 0, 1)
|
||||
smoke_mat.spread = 5.0
|
||||
smoke_mat.initial_velocity_min = 0.0
|
||||
smoke_mat.initial_velocity_max = 0.5
|
||||
smoke_mat.gravity = Vector3(0, 0.5, 0)
|
||||
|
||||
var grad = Gradient.new()
|
||||
grad.add_point(0.0, Color(0.5, 0.5, 0.5, 0.8))
|
||||
grad.add_point(1.0, Color(0.8, 0.8, 0.8, 0.0))
|
||||
var grad_tex = GradientTexture1D.new()
|
||||
grad_tex.gradient = grad
|
||||
smoke_mat.color_ramp = grad_tex
|
||||
|
||||
var curve = Curve.new()
|
||||
curve.add_point(Vector2(0.0, 1.0))
|
||||
curve.add_point(Vector2(1.0, 3.0))
|
||||
var curve_tex = CurveTexture.new()
|
||||
curve_tex.curve = curve
|
||||
smoke_mat.scale_curve = curve_tex
|
||||
|
||||
var smoke_mesh = QuadMesh.new()
|
||||
smoke_mesh.size = Vector2(0.2, 0.2)
|
||||
var s_mat = StandardMaterial3D.new()
|
||||
s_mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||
s_mat.vertex_color_use_as_albedo = true
|
||||
s_mat.albedo_texture = get_smoke_texture()
|
||||
s_mat.billboard_mode = BaseMaterial3D.BILLBOARD_PARTICLES
|
||||
s_mat.billboard_keep_scale = true
|
||||
smoke_mesh.material = s_mat
|
||||
smoke_sys.draw_pass_1 = smoke_mesh
|
||||
smoke_sys.process_material = smoke_mat
|
||||
smoke_sys.amount = 32
|
||||
smoke_sys.lifetime = 1.5
|
||||
smoke_sys.position = Vector3(0, 0, 0.15)
|
||||
add_child(smoke_sys)
|
||||
|
||||
func _on_hit(result: Dictionary) -> void:
|
||||
if not result.collider.has_method("take_damage"):
|
||||
ImpactSpawner.spawn(get_tree(), "crater", result.position, result.normal, explosion_radius * 1.5)
|
||||
_explode(result.position)
|
||||
queue_free()
|
||||
destroy()
|
||||
|
||||
func _explode(pos: Vector3) -> void:
|
||||
var space_state = get_world_3d().direct_space_state
|
||||
@@ -49,10 +174,12 @@ func _explode(pos: Vector3) -> void:
|
||||
|
||||
# Normalised distance (0 = epicenter, 1 = edge)
|
||||
var ndist = dist / explosion_radius
|
||||
var intensity = clampf(1.0 - pow(ndist, falloff_curve), 0.0, 1.0)
|
||||
var falloff = clampf(pow(ndist, falloff_curve), 0.0, 1.0)
|
||||
var damage_intensity = lerpf(1.0, 0.33, falloff)
|
||||
var knockback_intensity = 1.0 - falloff
|
||||
|
||||
var final_damage = damage * intensity
|
||||
var final_knockback = explosion_knockback * intensity
|
||||
var final_damage = damage * damage_intensity
|
||||
var final_knockback = explosion_knockback * knockback_intensity
|
||||
|
||||
# Knockback direction: from explosion pos to target
|
||||
var dir = (target_pos - pos).normalized()
|
||||
|
||||
@@ -8,6 +8,88 @@ var initial_aim_dir: Vector3 = Vector3.ZERO
|
||||
var cruise_speed: float = 60.0
|
||||
var ignition_timer: float = 0.5
|
||||
var ignited: bool = false
|
||||
var effects_started: bool = false
|
||||
|
||||
func _setup_effects() -> void:
|
||||
pass # Wait until ignited
|
||||
|
||||
func _start_homing_effects() -> void:
|
||||
if effects_started: return
|
||||
effects_started = true
|
||||
|
||||
# Audio
|
||||
flight_sound = AudioStreamPlayer3D.new()
|
||||
flight_sound.stream = load("res://assets/sounds/swarm_flight.wav")
|
||||
flight_sound.autoplay = true
|
||||
if flight_sound.stream is AudioStreamWAV:
|
||||
flight_sound.stream.loop_mode = AudioStreamWAV.LOOP_FORWARD
|
||||
flight_sound.unit_size = 50.0
|
||||
flight_sound.max_db = 6.0
|
||||
flight_sound.volume_db = 6.0
|
||||
add_child(flight_sound)
|
||||
flight_sound.play()
|
||||
|
||||
# Jet Fire Particles
|
||||
jet_sys = GPUParticles3D.new()
|
||||
var jet_mat = ParticleProcessMaterial.new()
|
||||
jet_mat.direction = Vector3(0, 0, 1)
|
||||
jet_mat.spread = 10.0
|
||||
jet_mat.initial_velocity_min = 0.5
|
||||
jet_mat.initial_velocity_max = 1.0
|
||||
jet_mat.gravity = Vector3.ZERO
|
||||
|
||||
var jet_mesh = BoxMesh.new()
|
||||
jet_mesh.size = Vector3(0.02, 0.02, 0.02)
|
||||
var mat = StandardMaterial3D.new()
|
||||
mat.albedo_color = Color(1.0, 0.6, 0.0)
|
||||
mat.emission_enabled = true
|
||||
mat.emission = Color(1.0, 0.5, 0.0)
|
||||
jet_mesh.material = mat
|
||||
jet_sys.draw_pass_1 = jet_mesh
|
||||
jet_sys.process_material = jet_mat
|
||||
jet_sys.amount = 16
|
||||
jet_sys.lifetime = 0.15
|
||||
jet_sys.position = Vector3(0, 0, 0.05)
|
||||
add_child(jet_sys)
|
||||
|
||||
# Smoke Trail
|
||||
smoke_sys = GPUParticles3D.new()
|
||||
var smoke_mat = ParticleProcessMaterial.new()
|
||||
smoke_mat.direction = Vector3(0, 0, 1)
|
||||
smoke_mat.spread = 5.0
|
||||
smoke_mat.initial_velocity_min = 0.0
|
||||
smoke_mat.initial_velocity_max = 0.2
|
||||
smoke_mat.gravity = Vector3(0, 0.5, 0)
|
||||
|
||||
var grad = Gradient.new()
|
||||
grad.add_point(0.0, Color(0.5, 0.5, 0.5, 0.8))
|
||||
grad.add_point(1.0, Color(0.8, 0.8, 0.8, 0.0))
|
||||
var grad_tex = GradientTexture1D.new()
|
||||
grad_tex.gradient = grad
|
||||
smoke_mat.color_ramp = grad_tex
|
||||
|
||||
var curve = Curve.new()
|
||||
curve.add_point(Vector2(0.0, 1.0))
|
||||
curve.add_point(Vector2(1.0, 3.0))
|
||||
var curve_tex = CurveTexture.new()
|
||||
curve_tex.curve = curve
|
||||
smoke_mat.scale_curve = curve_tex
|
||||
|
||||
var smoke_mesh = QuadMesh.new()
|
||||
smoke_mesh.size = Vector2(0.08, 0.08)
|
||||
var s_mat = StandardMaterial3D.new()
|
||||
s_mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||
s_mat.vertex_color_use_as_albedo = true
|
||||
s_mat.albedo_texture = ExplosiveProjectile.get_smoke_texture()
|
||||
s_mat.billboard_mode = BaseMaterial3D.BILLBOARD_PARTICLES
|
||||
s_mat.billboard_keep_scale = true
|
||||
smoke_mesh.material = s_mat
|
||||
smoke_sys.draw_pass_1 = smoke_mesh
|
||||
smoke_sys.process_material = smoke_mat
|
||||
smoke_sys.amount = 32
|
||||
smoke_sys.lifetime = 1.0
|
||||
smoke_sys.position = Vector3(0, 0, 0.05)
|
||||
add_child(smoke_sys)
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if not ignited:
|
||||
@@ -15,6 +97,7 @@ func _physics_process(delta: float) -> void:
|
||||
if ignition_timer <= 0.0:
|
||||
ignited = true
|
||||
drop_gravity = 0.0
|
||||
_start_homing_effects()
|
||||
|
||||
if ignited and is_instance_valid(weapon_source):
|
||||
var target_dir: Vector3 = initial_aim_dir
|
||||
|
||||
+1
-1
@@ -76,7 +76,7 @@ func _spawn_custom_projectile(origin: Vector3, fire_dir: Vector3) -> void:
|
||||
proj.bounciness = 0.4 # Heavy, loses momentum on bounce
|
||||
|
||||
# Explosive stats
|
||||
proj.explosion_radius = 12.0 # Very large AoE
|
||||
proj.explosion_radius = 10.0 # Very large AoE
|
||||
proj.explosion_knockback = 60.0 # Massive knockback
|
||||
proj.falloff_curve = 2.0 # Steep dropoff: "Should be an instant kill in closest 50%"
|
||||
# (0-50% = dist<6. 6/12 = 0.5. 1.0 - 0.5^2 = 0.75 multiplier. 200 * 0.75 = 150 damage -> 1 shot kill)
|
||||
|
||||
@@ -21,7 +21,7 @@ func _ready() -> void:
|
||||
_previous_position = global_position
|
||||
# Automatically destroy after 5 seconds to prevent leaks
|
||||
var t = get_tree().create_timer(5.0)
|
||||
t.timeout.connect(queue_free)
|
||||
t.timeout.connect(destroy)
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
# Apply gravity drop if any
|
||||
@@ -33,7 +33,7 @@ func _physics_process(delta: float) -> void:
|
||||
distance_traveled += movement.length()
|
||||
|
||||
if distance_traveled > max_range:
|
||||
queue_free()
|
||||
destroy()
|
||||
return
|
||||
|
||||
_check_collision()
|
||||
@@ -63,4 +63,7 @@ func _on_hit(result: Dictionary) -> void:
|
||||
var size = 0.4 if impact_type == "plasma" else 0.15
|
||||
ImpactSpawner.spawn(get_tree(), impact_type, result.position, result.normal, size)
|
||||
|
||||
destroy()
|
||||
|
||||
func destroy() -> void:
|
||||
queue_free()
|
||||
|
||||
@@ -98,7 +98,7 @@ func _spawn_custom_projectile(origin: Vector3, fire_dir: Vector3) -> void:
|
||||
proj.owner_player = player
|
||||
|
||||
# Explosive stats
|
||||
proj.explosion_radius = 8.0 # Moderate AoE
|
||||
proj.explosion_radius = 4.0 # Moderate AoE
|
||||
proj.explosion_knockback = 45.0 # Strong knockback!
|
||||
proj.can_self_damage = false # As requested
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ func _fire() -> void:
|
||||
var tween = create_tween()
|
||||
tween.tween_property(muzzle_flash, "light_energy", 0.0, 0.1)
|
||||
|
||||
var origin = camera.global_position + camera.global_transform.basis * Vector3(0.4, -0.2, -0.6)
|
||||
var _origin = camera.global_position + camera.global_transform.basis * Vector3(0.4, -0.2, -0.6)
|
||||
var base_dir = -camera.global_transform.basis.z
|
||||
|
||||
_current_aim_dir = base_dir
|
||||
@@ -162,7 +162,7 @@ func _spawn_custom_projectile(origin: Vector3, fire_dir: Vector3) -> void:
|
||||
|
||||
# Logic
|
||||
proj.weapon_source = self
|
||||
proj.turn_speed = 4.0 # Slower turn radius so they sweep nicely
|
||||
proj.turn_speed = 6.5 # Slower turn radius so they sweep nicely
|
||||
|
||||
proj.position = origin # Start at origin to spread outward around camera
|
||||
proj.logical_source = origin
|
||||
@@ -184,7 +184,7 @@ func _spawn_custom_projectile(origin: Vector3, fire_dir: Vector3) -> void:
|
||||
proj.owner_player = player
|
||||
|
||||
# Explosive stats
|
||||
proj.explosion_radius = 3.0 # Tiny AoE effect
|
||||
proj.explosion_radius = 1.5 # Tiny AoE effect
|
||||
proj.explosion_knockback = 5.0
|
||||
proj.can_self_damage = false
|
||||
|
||||
|
||||
Reference in New Issue
Block a user