Zmiany
This commit is contained in:
parent
9808a03d13
commit
5dd71181bc
BIN
assets/sounds/effects/explosion.mp3
Normal file
BIN
assets/sounds/effects/explosion.mp3
Normal file
Binary file not shown.
19
assets/sounds/effects/explosion.mp3.import
Normal file
19
assets/sounds/effects/explosion.mp3.import
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="mp3"
|
||||||
|
type="AudioStreamMP3"
|
||||||
|
uid="uid://bt61nivobxmjc"
|
||||||
|
path="res://.godot/imported/explosion.mp3-9b15137be8aff53d245e350137bf182a.mp3str"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/sounds/effects/explosion.mp3"
|
||||||
|
dest_files=["res://.godot/imported/explosion.mp3-9b15137be8aff53d245e350137bf182a.mp3str"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
loop=false
|
||||||
|
loop_offset=0
|
||||||
|
bpm=0
|
||||||
|
beat_count=0
|
||||||
|
bar_beats=4
|
||||||
50
resources/effects/ship_fireball.gdshader
Normal file
50
resources/effects/ship_fireball.gdshader
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
shader_type canvas_item;
|
||||||
|
render_mode unshaded, blend_add;
|
||||||
|
|
||||||
|
uniform float phase = 0.0;
|
||||||
|
uniform float variation = 0.0;
|
||||||
|
uniform float strength = 1.0;
|
||||||
|
|
||||||
|
float hash(vec2 p) {
|
||||||
|
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
|
||||||
|
}
|
||||||
|
|
||||||
|
float noise(vec2 p) {
|
||||||
|
vec2 i = floor(p);
|
||||||
|
vec2 f = fract(p);
|
||||||
|
vec2 u = f * f * (3.0 - 2.0 * f);
|
||||||
|
return mix(mix(hash(i), hash(i + vec2(1.0, 0.0)), u.x), mix(hash(i + vec2(0.0, 1.0)), hash(i + vec2(1.0)), u.x), u.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
float fbm(vec2 p) {
|
||||||
|
float value = 0.0;
|
||||||
|
float weight = 0.5;
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
value += noise(p) * weight;
|
||||||
|
p = mat2(vec2(1.6, 1.2), vec2(-1.2, 1.6)) * p + vec2(17.0, 9.0);
|
||||||
|
weight *= 0.5;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fragment() {
|
||||||
|
vec2 p = (UV - vec2(0.5)) * 2.0;
|
||||||
|
float radius = length(p);
|
||||||
|
float age = max(phase, 0.0);
|
||||||
|
float expansion = 0.16 + 0.44 * (1.0 - exp(-age * 7.0));
|
||||||
|
vec2 flow = p * 5.0 / max(expansion, 0.12);
|
||||||
|
flow += vec2(variation, -age * 3.2);
|
||||||
|
vec2 warp = vec2(fbm(flow + vec2(age * 1.3, 0.0)), fbm(flow + vec2(8.0, -age)));
|
||||||
|
float turbulence = fbm(flow + warp * 3.0);
|
||||||
|
float edge = expansion * (0.65 + turbulence * 0.65);
|
||||||
|
float body = 1.0 - smoothstep(edge * 0.58, edge, radius);
|
||||||
|
float fade = 1.0 - smoothstep(0.35, 1.3, age);
|
||||||
|
float heat = clamp(body * (0.35 + turbulence * 0.9) * exp(-age * 1.0), 0.0, 1.0);
|
||||||
|
vec3 fire = mix(vec3(0.85, 0.12, 0.015), vec3(1.0, 0.52, 0.035), smoothstep(0.08, 0.35, heat));
|
||||||
|
fire = mix(fire, vec3(1.0, 0.88, 0.46), smoothstep(0.3, 0.6, heat));
|
||||||
|
fire = mix(fire, vec3(1.0, 0.98, 0.88), smoothstep(0.6, 0.9, heat));
|
||||||
|
float glow = exp(-radius * radius / max(0.02, expansion * expansion * 0.85)) * 0.11 * exp(-age * 3.5);
|
||||||
|
float flash = exp(-radius * radius * 35.0) * exp(-age * 26.0);
|
||||||
|
vec3 light = fire * body * fade * (0.5 + turbulence) + vec3(1.0, 0.25, 0.035) * glow + vec3(1.0, 0.97, 0.85) * flash;
|
||||||
|
COLOR = vec4(light * strength, clamp((body * fade + glow + flash) * strength, 0.0, 1.0));
|
||||||
|
}
|
||||||
1
resources/effects/ship_fireball.gdshader.uid
Normal file
1
resources/effects/ship_fireball.gdshader.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://iwpo5xctaw06
|
||||||
@ -10,13 +10,13 @@ icon = ExtResource("icon")
|
|||||||
item_id = "beam_laser"
|
item_id = "beam_laser"
|
||||||
display_name = "Laser wiązkowy"
|
display_name = "Laser wiązkowy"
|
||||||
category = 0
|
category = 0
|
||||||
description = "Oszczędny laser pulsacyjny: małe obrażenia i bardzo duży zasięg. Koszt impulsu rośnie z obrażeniami i addonami egzemplarza."
|
description = "Oszczędny laser pulsacyjny: małe obrażenia i zasięg około jednego ekranu. Koszt impulsu rośnie z obrażeniami i addonami egzemplarza."
|
||||||
primary_stat = "damage"
|
primary_stat = "damage"
|
||||||
primary_value = 3.0
|
primary_value = 3.0
|
||||||
stat_suffix = " DMG"
|
stat_suffix = " DMG"
|
||||||
weapon_kind = 2
|
weapon_kind = 2
|
||||||
fire_interval = 0.2
|
fire_interval = 0.2
|
||||||
weapon_range = 800.0
|
weapon_range = 600.0
|
||||||
energy_cost = 2.5
|
energy_cost = 2.5
|
||||||
damage_lower_roll_min = 1
|
damage_lower_roll_min = 1
|
||||||
damage_lower_roll_max = 3
|
damage_lower_roll_max = 3
|
||||||
|
|||||||
74
scripts/game/asteroid_destruction_effect.gd
Normal file
74
scripts/game/asteroid_destruction_effect.gd
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
extends Node2D
|
||||||
|
|
||||||
|
const DURATION := 1.6
|
||||||
|
var _texture: Texture2D
|
||||||
|
var _fragments: Array[Dictionary] = []
|
||||||
|
var _dust: Array[Dictionary] = []
|
||||||
|
var _elapsed := 0.0
|
||||||
|
var _rng := RandomNumberGenerator.new()
|
||||||
|
var _dust_color := Color("b4a99a")
|
||||||
|
|
||||||
|
func configure(sprite: Sprite2D) -> void:
|
||||||
|
_texture = sprite.texture
|
||||||
|
transform = sprite.global_transform
|
||||||
|
var sprite_scale := global_scale
|
||||||
|
scale = Vector2.ONE
|
||||||
|
_rng.randomize()
|
||||||
|
var dimensions := _texture.get_size()
|
||||||
|
var grid: Array[Vector2] = []
|
||||||
|
for y in 5:
|
||||||
|
for x in 5:
|
||||||
|
var vertex := Vector2(x, y) * dimensions / 4.0
|
||||||
|
if x > 0 and x < 4:
|
||||||
|
vertex.x += _rng.randf_range(-0.3, 0.3) * dimensions.x / 4.0
|
||||||
|
if y > 0 and y < 4:
|
||||||
|
vertex.y += _rng.randf_range(-0.3, 0.3) * dimensions.y / 4.0
|
||||||
|
grid.append(vertex)
|
||||||
|
for y in 4:
|
||||||
|
for x in 4:
|
||||||
|
var a := grid[y * 5 + x]
|
||||||
|
var b := grid[y * 5 + x + 1]
|
||||||
|
var c := grid[(y + 1) * 5 + x + 1]
|
||||||
|
var d := grid[(y + 1) * 5 + x]
|
||||||
|
for triangle in [[a, b, c], [a, c, d]]:
|
||||||
|
var center: Vector2 = (triangle[0] + triangle[1] + triangle[2]) / 3.0
|
||||||
|
var origin := (center - dimensions * 0.5) * sprite_scale
|
||||||
|
var points := PackedVector2Array()
|
||||||
|
var uv := PackedVector2Array()
|
||||||
|
for vertex: Vector2 in triangle:
|
||||||
|
points.append((vertex - center) * sprite_scale)
|
||||||
|
uv.append(vertex / dimensions)
|
||||||
|
var direction := origin.normalized() if origin.length_squared() > 0.1 else Vector2.RIGHT
|
||||||
|
_fragments.append({"points": points, "uv": uv, "position": origin,
|
||||||
|
"velocity": direction.rotated(_rng.randf_range(-0.35, 0.35)) * _rng.randf_range(70.0, 170.0),
|
||||||
|
"angle": 0.0, "spin": _rng.randf_range(-4.0, 4.0)})
|
||||||
|
_dust_color = Color("a1c7df") if "ice" in _texture.resource_path else Color("b4a99a")
|
||||||
|
for index in 28:
|
||||||
|
_dust.append({"direction": Vector2.RIGHT.rotated(_rng.randf_range(0.0, TAU)),
|
||||||
|
"speed": _rng.randf_range(45.0, 210.0), "size": _rng.randf_range(1.0, 3.0)})
|
||||||
|
z_index = 35
|
||||||
|
light_mask = 4
|
||||||
|
|
||||||
|
func _process(delta: float) -> void:
|
||||||
|
_elapsed += delta
|
||||||
|
if _elapsed >= DURATION:
|
||||||
|
queue_free()
|
||||||
|
return
|
||||||
|
for fragment in _fragments:
|
||||||
|
fragment.position += fragment.velocity * delta
|
||||||
|
fragment.velocity *= exp(-1.2 * delta)
|
||||||
|
fragment.angle += fragment.spin * delta
|
||||||
|
queue_redraw()
|
||||||
|
|
||||||
|
func _draw() -> void:
|
||||||
|
var fade := 1.0 - smoothstep(0.65, DURATION, _elapsed)
|
||||||
|
for fragment in _fragments:
|
||||||
|
draw_set_transform(fragment.position, fragment.angle)
|
||||||
|
draw_polygon(fragment.points, PackedColorArray([Color(1.0, 1.0, 1.0, fade)]), fragment.uv, _texture)
|
||||||
|
draw_set_transform(Vector2.ZERO)
|
||||||
|
var dust_fade := maxf(0.0, 1.0 - _elapsed / 0.8)
|
||||||
|
for mote in _dust:
|
||||||
|
var travel: Vector2 = mote.direction * mote.speed * (1.0 - exp(-2.0 * _elapsed)) / 2.0
|
||||||
|
draw_circle(travel, mote.size * (1.0 + _elapsed), Color(_dust_color, dust_fade * 0.65))
|
||||||
|
if _elapsed < 0.18:
|
||||||
|
draw_circle(Vector2.ZERO, 16.0 * (1.0 - _elapsed / 0.18), Color(0.85, 0.9, 1.0, (1.0 - _elapsed / 0.18) * 0.55))
|
||||||
1
scripts/game/asteroid_destruction_effect.gd.uid
Normal file
1
scripts/game/asteroid_destruction_effect.gd.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://dnqrd4p1yobkx
|
||||||
@ -89,7 +89,7 @@ func get_visual_size() -> float:
|
|||||||
return 112.0 if loot_tier == 2 else (68.0 if loot_tier == 1 else 56.0)
|
return 112.0 if loot_tier == 2 else (68.0 if loot_tier == 1 else 56.0)
|
||||||
|
|
||||||
func _physics_process(delta: float) -> void:
|
func _physics_process(delta: float) -> void:
|
||||||
if not is_instance_valid(_player):
|
if not is_instance_valid(_player) or bool(_player.get("_is_destroyed")):
|
||||||
velocity = velocity.move_toward(Vector2.ZERO, approach_acceleration * delta)
|
velocity = velocity.move_toward(Vector2.ZERO, approach_acceleration * delta)
|
||||||
move_and_slide()
|
move_and_slide()
|
||||||
return
|
return
|
||||||
|
|||||||
306
scripts/game/player_destruction_effect.gd
Normal file
306
scripts/game/player_destruction_effect.gd
Normal file
@ -0,0 +1,306 @@
|
|||||||
|
extends Node2D
|
||||||
|
|
||||||
|
signal finished
|
||||||
|
|
||||||
|
const CHARGE_TIME := 2.3
|
||||||
|
const DURATION := CHARGE_TIME + 3.0
|
||||||
|
const EXPLOSION_AUDIO_PATH := "res://assets/sounds/effects/explosion.mp3"
|
||||||
|
const FIRE_SHADER := preload("res://resources/effects/ship_fireball.gdshader")
|
||||||
|
static var _smoke_texture: Texture2D
|
||||||
|
var _elapsed := 0.0
|
||||||
|
var _texture: Texture2D
|
||||||
|
var _ship_scale := Vector2.ONE
|
||||||
|
var _drift := Vector2.ZERO
|
||||||
|
var _fragments: Array[Dictionary] = []
|
||||||
|
var _sparks: Array[Dictionary] = []
|
||||||
|
var _smoke: Array[Dictionary] = []
|
||||||
|
var _camera: Camera2D
|
||||||
|
var _camera_offset := Vector2.ZERO
|
||||||
|
var _detonated := false
|
||||||
|
var _smoke_timer := 0.0
|
||||||
|
var _done := false
|
||||||
|
var _rng := RandomNumberGenerator.new()
|
||||||
|
var _fireball: MeshInstance2D
|
||||||
|
var _fire_material: ShaderMaterial
|
||||||
|
var _charge_audio: AudioStreamPlayer2D
|
||||||
|
var _burns: Array[Dictionary] = []
|
||||||
|
var _ignition_timer := 0.0
|
||||||
|
|
||||||
|
func configure(sprite: Sprite2D, drift: Vector2, camera: Camera2D) -> void:
|
||||||
|
_texture = sprite.texture
|
||||||
|
global_position = sprite.global_position
|
||||||
|
global_rotation = sprite.global_rotation
|
||||||
|
_ship_scale = sprite.global_scale
|
||||||
|
_drift = drift.rotated(-global_rotation) * 0.12
|
||||||
|
_camera = camera
|
||||||
|
if is_instance_valid(_camera):
|
||||||
|
_camera_offset = _camera.offset
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
z_index = 45
|
||||||
|
light_mask = 4
|
||||||
|
_rng.randomize()
|
||||||
|
_build_fragments()
|
||||||
|
_prepare_smoke_texture()
|
||||||
|
_ignite_burn()
|
||||||
|
_ignite_burn()
|
||||||
|
_fireball = MeshInstance2D.new()
|
||||||
|
var quad := QuadMesh.new()
|
||||||
|
quad.size = Vector2(240, 240)
|
||||||
|
_fireball.mesh = quad
|
||||||
|
_fire_material = ShaderMaterial.new()
|
||||||
|
_fire_material.shader = FIRE_SHADER
|
||||||
|
_fire_material.set_shader_parameter("variation", _rng.randf_range(0, 100))
|
||||||
|
_fireball.material = _fire_material
|
||||||
|
_fireball.light_mask = 4
|
||||||
|
_fireball.z_index = 1
|
||||||
|
_fireball.hide()
|
||||||
|
add_child(_fireball)
|
||||||
|
_play_charge_sound()
|
||||||
|
|
||||||
|
func _build_fragments() -> void:
|
||||||
|
if _texture == null:
|
||||||
|
return
|
||||||
|
var dimensions := _texture.get_size()
|
||||||
|
var image := _texture.get_image()
|
||||||
|
for y in 8:
|
||||||
|
for x in 8:
|
||||||
|
var a := Vector2(x, y) * dimensions / 8.0
|
||||||
|
var b := Vector2(x + 1, y) * dimensions / 8.0
|
||||||
|
var c := Vector2(x + 1, y + 1) * dimensions / 8.0
|
||||||
|
var d := Vector2(x, y + 1) * dimensions / 8.0
|
||||||
|
for triangle in [[a, b, c], [a, c, d]]:
|
||||||
|
var center: Vector2 = (triangle[0] + triangle[1] + triangle[2]) / 3.0
|
||||||
|
if image != null and image.get_pixel(clampi(int(center.x / dimensions.x * image.get_width()), 0, image.get_width() - 1), clampi(int(center.y / dimensions.y * image.get_height()), 0, image.get_height() - 1)).a < 0.1:
|
||||||
|
continue
|
||||||
|
var origin := (center - dimensions * 0.5) * _ship_scale
|
||||||
|
var points := PackedVector2Array()
|
||||||
|
var uv := PackedVector2Array()
|
||||||
|
for vertex in triangle:
|
||||||
|
points.append((vertex - center) * _ship_scale)
|
||||||
|
uv.append(vertex / dimensions)
|
||||||
|
var wreck := origin.length() < 12.0
|
||||||
|
var radial := origin.normalized() if origin.length_squared() > 0.1 else Vector2.RIGHT.rotated(_rng.randf_range(0, TAU))
|
||||||
|
_fragments.append({"points": points, "uv": uv, "position": origin, "velocity": radial.rotated(_rng.randf_range(-0.45, 0.45)) * _rng.randf_range(65, 155) + _drift if not wreck else radial * 5.0, "angle": 0.0, "spin": _rng.randf_range(-5, 5) if not wreck else _rng.randf_range(-0.3, 0.3), "wreck": wreck})
|
||||||
|
|
||||||
|
func _process(delta: float) -> void:
|
||||||
|
if _done and _elapsed >= DURATION + 1.8:
|
||||||
|
_smoke.clear()
|
||||||
|
_sparks.clear()
|
||||||
|
set_process(false)
|
||||||
|
queue_redraw()
|
||||||
|
return
|
||||||
|
_elapsed += delta
|
||||||
|
if not _detonated and _elapsed < CHARGE_TIME:
|
||||||
|
_ignition_timer -= delta
|
||||||
|
if _ignition_timer <= 0.0:
|
||||||
|
_ignite_burn()
|
||||||
|
var progress := _elapsed / CHARGE_TIME
|
||||||
|
_ignition_timer = _rng.randf_range(0.1, 0.22) * lerpf(1.0, 0.55, progress)
|
||||||
|
for index in range(_burns.size() - 1, -1, -1):
|
||||||
|
var burn := _burns[index]
|
||||||
|
burn["age"] += delta
|
||||||
|
var age := clampf(float(burn["age"]) / float(burn["life"]), 0, 1)
|
||||||
|
var intensity := sin(age * PI)
|
||||||
|
var wobble := sin(_elapsed * 23 + float(burn["phase"]))
|
||||||
|
var flame: MeshInstance2D = burn["visual"]
|
||||||
|
flame.position = burn["position"] + Vector2.UP.rotated(float(burn["angle"])) * 4 * intensity
|
||||||
|
flame.rotation = float(burn["angle"]) + wobble * 0.3
|
||||||
|
flame.scale = Vector2(0.8 + intensity * 0.3, 0.7 + intensity * 0.4 + wobble * 0.1)
|
||||||
|
var material: ShaderMaterial = burn["material"]
|
||||||
|
material.set_shader_parameter("phase", 0.12 + age * 0.65)
|
||||||
|
material.set_shader_parameter("strength", intensity * (1.4 + wobble * 0.3))
|
||||||
|
if age >= 1.0:
|
||||||
|
flame.queue_free()
|
||||||
|
_burns.remove_at(index)
|
||||||
|
if _elapsed >= CHARGE_TIME and not _detonated:
|
||||||
|
_detonated = true
|
||||||
|
for burn in _burns:
|
||||||
|
burn["visual"].queue_free()
|
||||||
|
_burns.clear()
|
||||||
|
_play_explosion_sound()
|
||||||
|
for index in 16:
|
||||||
|
var outward := Vector2.RIGHT.rotated(_rng.randf_range(0, TAU))
|
||||||
|
_smoke.append({"position": outward * _rng.randf_range(4, 18), "velocity": outward * _rng.randf_range(25, 65), "age": -0.08, "life": _rng.randf_range(1.8, 2.8), "radius": _rng.randf_range(15, 28)})
|
||||||
|
for index in 75:
|
||||||
|
var direction := Vector2.RIGHT.rotated(_rng.randf_range(0, TAU))
|
||||||
|
_sparks.append({"position": direction * _rng.randf_range(3, 24), "velocity": direction * _rng.randf_range(80, 330), "life": _rng.randf_range(0.45, 1.8), "age": 0.0})
|
||||||
|
if _detonated:
|
||||||
|
for fragment in _fragments:
|
||||||
|
fragment["position"] += fragment["velocity"] * delta
|
||||||
|
fragment["velocity"] *= exp(-0.7 * delta)
|
||||||
|
fragment["angle"] += float(fragment["spin"]) * delta
|
||||||
|
_smoke_timer += delta
|
||||||
|
var smoke_interval := 0.065 if _detonated else 0.12
|
||||||
|
while _smoke_timer >= smoke_interval and _elapsed < CHARGE_TIME + 2.5:
|
||||||
|
_smoke_timer -= smoke_interval
|
||||||
|
_emit_smoke()
|
||||||
|
for index in range(_sparks.size() - 1, -1, -1):
|
||||||
|
var spark := _sparks[index]
|
||||||
|
spark["position"] += spark["velocity"] * delta
|
||||||
|
spark["velocity"] *= exp(-1.3 * delta)
|
||||||
|
spark["age"] += delta
|
||||||
|
if float(spark["age"]) >= float(spark["life"]):
|
||||||
|
_sparks.remove_at(index)
|
||||||
|
for index in range(_smoke.size() - 1, -1, -1):
|
||||||
|
var puff := _smoke[index]
|
||||||
|
puff["position"] += puff["velocity"] * delta
|
||||||
|
puff["age"] += delta
|
||||||
|
if float(puff["age"]) >= float(puff["life"]):
|
||||||
|
_smoke.remove_at(index)
|
||||||
|
if not _done:
|
||||||
|
_update_camera()
|
||||||
|
var blast_age := maxf(0, _elapsed - CHARGE_TIME)
|
||||||
|
_fireball.visible = _detonated and blast_age < 1.3
|
||||||
|
_fire_material.set_shader_parameter("phase", blast_age)
|
||||||
|
queue_redraw()
|
||||||
|
if _elapsed >= DURATION and not _done:
|
||||||
|
_done = true
|
||||||
|
_restore_camera()
|
||||||
|
finished.emit()
|
||||||
|
|
||||||
|
func _hull_burn_position() -> Vector2:
|
||||||
|
if not _fragments.is_empty():
|
||||||
|
return _fragments[_rng.randi_range(0, _fragments.size() - 1)]["position"]
|
||||||
|
return Vector2.ZERO
|
||||||
|
|
||||||
|
func _ignite_burn() -> void:
|
||||||
|
var origin := _hull_burn_position()
|
||||||
|
var angle := origin.angle() + PI * 0.5 + _rng.randf_range(-0.65, 0.65)
|
||||||
|
var flame := MeshInstance2D.new()
|
||||||
|
var quad := QuadMesh.new()
|
||||||
|
quad.size = Vector2(_rng.randf_range(24, 34), _rng.randf_range(36, 52))
|
||||||
|
flame.mesh = quad
|
||||||
|
var material := ShaderMaterial.new()
|
||||||
|
material.shader = FIRE_SHADER
|
||||||
|
material.set_shader_parameter("variation", _rng.randf_range(0, 100))
|
||||||
|
material.set_shader_parameter("strength", 0.0)
|
||||||
|
flame.material = material
|
||||||
|
flame.position = origin
|
||||||
|
flame.rotation = angle
|
||||||
|
flame.z_index = 1
|
||||||
|
add_child(flame)
|
||||||
|
_burns.append({"position": origin, "angle": angle, "age": 0.0, "life": _rng.randf_range(0.3, 0.7), "phase": _rng.randf_range(0, TAU), "visual": flame, "material": material})
|
||||||
|
var direction := Vector2.UP.rotated(angle)
|
||||||
|
for index in _rng.randi_range(3, 6):
|
||||||
|
_sparks.append({"position": origin, "velocity": direction.rotated(_rng.randf_range(-0.9, 0.9)) * _rng.randf_range(25, 100), "life": _rng.randf_range(0.18, 0.5), "age": 0.0})
|
||||||
|
|
||||||
|
func _emit_smoke() -> void:
|
||||||
|
var position := Vector2.ZERO
|
||||||
|
if not _detonated:
|
||||||
|
position = _burns[_rng.randi_range(0, _burns.size() - 1)]["position"] if not _burns.is_empty() else _hull_burn_position()
|
||||||
|
elif not _fragments.is_empty() and _rng.randf() < 0.7:
|
||||||
|
position = _fragments[_rng.randi_range(0, _fragments.size() - 1)]["position"]
|
||||||
|
_smoke.append({"position": position, "velocity": Vector2(_rng.randf_range(-12, 12), _rng.randf_range(-22, 8)), "age": 0.0, "life": _rng.randf_range(0.9, 1.7), "radius": _rng.randf_range(5, 12)})
|
||||||
|
|
||||||
|
func _update_camera() -> void:
|
||||||
|
if not is_instance_valid(_camera):
|
||||||
|
return
|
||||||
|
var time := _elapsed - CHARGE_TIME
|
||||||
|
var strength := 0.8 if time < 0.0 else 7.0 * exp(-5.0 * time)
|
||||||
|
_camera.offset = _camera_offset + Vector2(sin(_elapsed * 83), cos(_elapsed * 67)) * strength
|
||||||
|
|
||||||
|
func _restore_camera() -> void:
|
||||||
|
if is_instance_valid(_camera):
|
||||||
|
_camera.offset = _camera_offset
|
||||||
|
|
||||||
|
func _exit_tree() -> void:
|
||||||
|
_restore_camera()
|
||||||
|
|
||||||
|
func _draw() -> void:
|
||||||
|
if _texture == null:
|
||||||
|
return
|
||||||
|
var time := maxf(0.0, _elapsed - CHARGE_TIME)
|
||||||
|
for puff in _smoke:
|
||||||
|
var progress: float = float(puff["age"]) / float(puff["life"])
|
||||||
|
var opacity := (1.0 - progress) * minf(1, float(puff["age"]) * 8.0)
|
||||||
|
var radius := float(puff["radius"]) * (1 + maxf(0, progress) * 1.5)
|
||||||
|
var soot := Color("6c3e25").lerp(Color("3c4149"), clampf(progress * 3, 0, 1))
|
||||||
|
soot.a = maxf(0, opacity) * 0.7
|
||||||
|
draw_texture_rect(_smoke_texture, Rect2(Vector2(puff["position"]) - Vector2.ONE * radius, Vector2.ONE * radius * 2), false, soot)
|
||||||
|
if not _detonated:
|
||||||
|
var progress := clampf(_elapsed / CHARGE_TIME, 0, 1)
|
||||||
|
var pulse := (sin(_elapsed * 19) + 1.0) * 0.5
|
||||||
|
var dimensions := _texture.get_size() * _ship_scale
|
||||||
|
draw_texture_rect(_texture, Rect2(-dimensions * 0.5, dimensions), false, Color(1, 0.95 - progress * 0.1 + pulse * 0.05, 0.88 - progress * 0.08 + pulse * 0.1))
|
||||||
|
_draw_sparks()
|
||||||
|
return
|
||||||
|
for fragment in _fragments:
|
||||||
|
var fade := 1.0 if bool(fragment["wreck"]) else clampf((3.4 - time) / 1.1, 0, 1)
|
||||||
|
var shade := lerpf(1.0, 0.22 if bool(fragment["wreck"]) else 0.55, minf(1, time))
|
||||||
|
draw_set_transform(fragment["position"], fragment["angle"])
|
||||||
|
draw_polygon(fragment["points"], PackedColorArray([Color(shade, shade * 0.85, shade * 0.7, fade)]), fragment["uv"], _texture)
|
||||||
|
if time < 1.7 and not bool(fragment["wreck"]):
|
||||||
|
draw_line(Vector2.ZERO, -Vector2(fragment["velocity"]).normalized().rotated(-float(fragment["angle"])) * 9, Color(1, 0.35, 0.06, (1.0 - time / 1.7) * 0.7), 2, true)
|
||||||
|
draw_set_transform(Vector2.ZERO)
|
||||||
|
if time < 0.55:
|
||||||
|
var fade := 1.0 - time / 0.55
|
||||||
|
for layer in 3:
|
||||||
|
draw_arc(Vector2.ZERO, 18 + time * 230 + layer * 2, 0, TAU, 96, Color(0.85, 0.75, 0.6, fade * 0.08), 2, true)
|
||||||
|
_draw_sparks()
|
||||||
|
if time > 0.8:
|
||||||
|
for index in 4:
|
||||||
|
var flicker := maxf(0, sin(_elapsed * 17 + index * 8)) * clampf((3.3 - time) / 2, 0, 1)
|
||||||
|
draw_circle(Vector2.RIGHT.rotated(index * 2.3) * (4 + index * 2), 2 + flicker * 2, Color(1, 0.28, 0.04, flicker * 0.75))
|
||||||
|
|
||||||
|
func _draw_sparks() -> void:
|
||||||
|
for spark in _sparks:
|
||||||
|
var fade := 1 - float(spark["age"]) / float(spark["life"])
|
||||||
|
var length := clampf(Vector2(spark["velocity"]).length() * 0.03, 2, 8)
|
||||||
|
draw_line(spark["position"], spark["position"] - Vector2(spark["velocity"]).normalized() * length, Color(1, 0.65 + fade * 0.2, 0.2, fade), 1.2, true)
|
||||||
|
|
||||||
|
func _prepare_smoke_texture() -> void:
|
||||||
|
if _smoke_texture != null:
|
||||||
|
return
|
||||||
|
var image := Image.create(96, 96, false, Image.FORMAT_RGBA8)
|
||||||
|
var noise := FastNoiseLite.new()
|
||||||
|
noise.seed = 71539
|
||||||
|
noise.frequency = 0.06
|
||||||
|
noise.fractal_octaves = 4
|
||||||
|
for y in 96:
|
||||||
|
for x in 96:
|
||||||
|
var p := (Vector2(x, y) - Vector2(47.5, 47.5)) / 47.5
|
||||||
|
var radial := 1.0 - smoothstep(0.2, 1.0, p.length())
|
||||||
|
var density := clampf(noise.get_noise_2d(x, y) * 0.8 + 0.55, 0, 1)
|
||||||
|
image.set_pixel(x, y, Color(1, 1, 1, radial * density))
|
||||||
|
_smoke_texture = ImageTexture.create_from_image(image)
|
||||||
|
|
||||||
|
func _play_explosion_sound() -> void:
|
||||||
|
if is_instance_valid(_charge_audio):
|
||||||
|
_charge_audio.stop()
|
||||||
|
_charge_audio.queue_free()
|
||||||
|
var audio := AudioStreamPlayer2D.new()
|
||||||
|
audio.name = "ExplosionAudio"
|
||||||
|
audio.stream = load(EXPLOSION_AUDIO_PATH) as AudioStream
|
||||||
|
if audio.stream == null:
|
||||||
|
audio.free()
|
||||||
|
return
|
||||||
|
audio.volume_db = -6
|
||||||
|
audio.max_distance = 1800
|
||||||
|
add_child(audio)
|
||||||
|
audio.finished.connect(audio.queue_free)
|
||||||
|
audio.play()
|
||||||
|
|
||||||
|
func _play_charge_sound() -> void:
|
||||||
|
var rate := 22050
|
||||||
|
var frames := int(rate * CHARGE_TIME)
|
||||||
|
var data := PackedByteArray()
|
||||||
|
data.resize(frames * 2)
|
||||||
|
var filtered := 0.0
|
||||||
|
for index in frames:
|
||||||
|
var t := float(index) / rate
|
||||||
|
filtered = lerpf(filtered, _rng.randf_range(-1, 1), 0.16)
|
||||||
|
var value := sin(TAU * (80 + t * 70 / CHARGE_TIME) * t) * 0.06 * (t / CHARGE_TIME) + filtered * 0.025
|
||||||
|
data.encode_s16(index * 2, int(clampf(value, -1, 1) * 25000))
|
||||||
|
var sound := AudioStreamWAV.new()
|
||||||
|
sound.format = AudioStreamWAV.FORMAT_16_BITS
|
||||||
|
sound.mix_rate = rate
|
||||||
|
sound.data = data
|
||||||
|
var audio := AudioStreamPlayer2D.new()
|
||||||
|
_charge_audio = audio
|
||||||
|
audio.stream = sound
|
||||||
|
audio.volume_db = -8
|
||||||
|
audio.max_distance = 1800
|
||||||
|
add_child(audio)
|
||||||
|
audio.finished.connect(audio.queue_free)
|
||||||
|
audio.play()
|
||||||
1
scripts/game/player_destruction_effect.gd.uid
Normal file
1
scripts/game/player_destruction_effect.gd.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://b3vtjeqhycmqk
|
||||||
@ -8,6 +8,7 @@ const CAPTAIN_BALANCE := preload("res://scripts/captain/captain_balance.gd")
|
|||||||
const SHIP_CATALOG := preload("res://scripts/game/ship_catalog.gd")
|
const SHIP_CATALOG := preload("res://scripts/game/ship_catalog.gd")
|
||||||
const COMBAT_FLASH := preload("res://scripts/game/combat_flash.gd")
|
const COMBAT_FLASH := preload("res://scripts/game/combat_flash.gd")
|
||||||
const MINI_BOOST_ENERGY_PER_SECOND := 20.0
|
const MINI_BOOST_ENERGY_PER_SECOND := 20.0
|
||||||
|
const DESTRUCTION_EFFECT := preload("res://scripts/game/player_destruction_effect.gd")
|
||||||
|
|
||||||
@export var max_speed := 76.0
|
@export var max_speed := 76.0
|
||||||
@export var reverse_max_speed := 210.0
|
@export var reverse_max_speed := 210.0
|
||||||
@ -212,19 +213,45 @@ func _update_shield_visual() -> void:
|
|||||||
_shield_visual.call("set_shield_values", shields, max_shields)
|
_shield_visual.call("set_shield_values", shields, max_shields)
|
||||||
|
|
||||||
func _destroy_ship() -> void:
|
func _destroy_ship() -> void:
|
||||||
|
if _is_destroyed:
|
||||||
|
return
|
||||||
_is_destroyed = true
|
_is_destroyed = true
|
||||||
|
var death_velocity := velocity
|
||||||
velocity = Vector2.ZERO
|
velocity = Vector2.ZERO
|
||||||
collision_layer = 0
|
collision_layer = 0
|
||||||
collision_mask = 0
|
collision_mask = 0
|
||||||
var tween := create_tween()
|
_mini_boost_active = false
|
||||||
tween.tween_property(self, "modulate:a", 0.0, 1.1)
|
var explosion := Node2D.new()
|
||||||
_restart_level_after_destruction()
|
explosion.name = "PlayerDestructionEffect"
|
||||||
|
explosion.set_script(DESTRUCTION_EFFECT)
|
||||||
|
explosion.call("configure", _sprite, death_velocity, get_node_or_null("Camera2D"))
|
||||||
|
get_parent().add_child(explosion)
|
||||||
|
explosion.connect("finished", _show_defeat_screen)
|
||||||
|
_sprite.hide()
|
||||||
|
for node_name in ["ShieldVisual", "EngineVisual", "EnergyIndicator", "SolarEnergyEffect"]:
|
||||||
|
var visual := get_node_or_null(node_name) as CanvasItem
|
||||||
|
if visual != null:
|
||||||
|
visual.hide()
|
||||||
|
$EngineAudio.stop()
|
||||||
|
$EngineAudio.set_process(false)
|
||||||
|
$EngineVisual.set_process(false)
|
||||||
|
$ShieldVisual.set_process(false)
|
||||||
|
var radiation := get_node_or_null("RadiationStatus")
|
||||||
|
if radiation != null:
|
||||||
|
radiation.set_physics_process(false)
|
||||||
|
|
||||||
|
func _show_defeat_screen() -> void:
|
||||||
|
if not is_inside_tree() or get_parent().has_node("DefeatOverlay"):
|
||||||
|
return
|
||||||
|
var overlay := CanvasLayer.new()
|
||||||
|
overlay.set_script(preload("res://scripts/ui/defeat_overlay.gd"))
|
||||||
|
overlay.set("ship", self)
|
||||||
|
get_parent().add_child(overlay)
|
||||||
|
|
||||||
func _restart_level_after_destruction() -> void:
|
func _restart_level_after_destruction() -> void:
|
||||||
await get_tree().create_timer(1.1).timeout
|
|
||||||
if is_inside_tree():
|
if is_inside_tree():
|
||||||
$ShipInventory.preserve_for_restart()
|
$ShipInventory.preserve_for_restart()
|
||||||
get_tree().reload_current_scene()
|
get_tree().reload_current_scene.call_deferred()
|
||||||
|
|
||||||
func _physics_process(delta: float) -> void:
|
func _physics_process(delta: float) -> void:
|
||||||
if _is_destroyed or is_docked:
|
if _is_destroyed or is_docked:
|
||||||
|
|||||||
@ -4,6 +4,7 @@ const GENERATOR := preload("res://scripts/items/item_generator.gd")
|
|||||||
const ASTEROID_TEXTURES := [preload("res://assets/asteroids/asteroid_rock.png"), preload("res://assets/asteroids/asteroid_metal.png"), preload("res://assets/asteroids/asteroid_ice.png")]
|
const ASTEROID_TEXTURES := [preload("res://assets/asteroids/asteroid_rock.png"), preload("res://assets/asteroids/asteroid_metal.png"), preload("res://assets/asteroids/asteroid_ice.png")]
|
||||||
const WRECK_TEXTURES := [preload("res://assets/wrecks/wreck_frigate.png"), preload("res://assets/wrecks/wreck_freighter.png"), preload("res://assets/wrecks/wreck_interceptor.png")]
|
const WRECK_TEXTURES := [preload("res://assets/wrecks/wreck_frigate.png"), preload("res://assets/wrecks/wreck_freighter.png"), preload("res://assets/wrecks/wreck_interceptor.png")]
|
||||||
const LOOT := preload("res://scripts/items/loot_pickup.gd")
|
const LOOT := preload("res://scripts/items/loot_pickup.gd")
|
||||||
|
const ASTEROID_DESTRUCTION := preload("res://scripts/game/asteroid_destruction_effect.gd")
|
||||||
var kind := "wreck"
|
var kind := "wreck"
|
||||||
var poi_id := ""
|
var poi_id := ""
|
||||||
var reward_seed := 1
|
var reward_seed := 1
|
||||||
@ -147,6 +148,13 @@ func claim() -> bool:
|
|||||||
popup.position = position + Vector2(-80, -65)
|
popup.position = position + Vector2(-80, -65)
|
||||||
get_parent().add_child.call_deferred(popup)
|
get_parent().add_child.call_deferred(popup)
|
||||||
set_physics_process(false)
|
set_physics_process(false)
|
||||||
|
if kind == "asteroid" and is_instance_valid(_asteroid_sprite):
|
||||||
|
var debris := Node2D.new()
|
||||||
|
debris.set_script(ASTEROID_DESTRUCTION)
|
||||||
|
debris.name = "AsteroidDebris"
|
||||||
|
debris.call("configure", _asteroid_sprite)
|
||||||
|
# The POI may be destroyed during a physics query; add debris outside it.
|
||||||
|
get_parent().add_child.call_deferred(debris)
|
||||||
queue_free()
|
queue_free()
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
|||||||
62
scripts/ui/defeat_overlay.gd
Normal file
62
scripts/ui/defeat_overlay.gd
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
extends CanvasLayer
|
||||||
|
|
||||||
|
var ship: CharacterBody2D
|
||||||
|
var _retrying := false
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
layer = 40
|
||||||
|
name = "DefeatOverlay"
|
||||||
|
var root := Control.new()
|
||||||
|
add_child(root)
|
||||||
|
root.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||||
|
var shade := ColorRect.new()
|
||||||
|
shade.color = Color(0.015, 0.025, 0.045, 0.55)
|
||||||
|
root.add_child(shade)
|
||||||
|
shade.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||||
|
var center := CenterContainer.new()
|
||||||
|
root.add_child(center)
|
||||||
|
center.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||||
|
var panel := PanelContainer.new()
|
||||||
|
panel.custom_minimum_size.x = 390
|
||||||
|
var style := StyleBoxFlat.new()
|
||||||
|
style.bg_color = Color("0b1520f5")
|
||||||
|
style.border_color = Color("bf604d")
|
||||||
|
style.set_border_width_all(1)
|
||||||
|
style.border_width_top = 3
|
||||||
|
style.set_content_margin_all(24)
|
||||||
|
style.set_corner_radius_all(5)
|
||||||
|
panel.add_theme_stylebox_override("panel", style)
|
||||||
|
center.add_child(panel)
|
||||||
|
var contents := VBoxContainer.new()
|
||||||
|
contents.add_theme_constant_override("separation", 14)
|
||||||
|
panel.add_child(contents)
|
||||||
|
var title := Label.new()
|
||||||
|
title.text = "STATEK ZNISZCZONY"
|
||||||
|
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||||
|
title.add_theme_font_size_override("font_size", 20)
|
||||||
|
title.add_theme_color_override("font_color", Color("ffae8c"))
|
||||||
|
contents.add_child(title)
|
||||||
|
var note := Label.new()
|
||||||
|
note.text = "Układ %d\nWyposażenie i zapasy zostają zachowane." % GameState.system_level
|
||||||
|
note.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||||
|
note.add_theme_font_size_override("font_size", 11)
|
||||||
|
note.add_theme_color_override("font_color", Color("a6bdce"))
|
||||||
|
contents.add_child(note)
|
||||||
|
var retry := Button.new()
|
||||||
|
retry.name = "RetryButton"
|
||||||
|
retry.text = "SPRÓBUJ PONOWNIE [R]"
|
||||||
|
preload("res://scripts/ui/hud_button_style.gd").apply(retry)
|
||||||
|
var key := InputEventKey.new()
|
||||||
|
key.physical_keycode = KEY_R
|
||||||
|
retry.shortcut = Shortcut.new()
|
||||||
|
retry.shortcut.events = [key]
|
||||||
|
retry.pressed.connect(_retry)
|
||||||
|
contents.add_child(retry)
|
||||||
|
root.modulate.a = 0
|
||||||
|
create_tween().tween_property(root, "modulate:a", 1.0, 0.3)
|
||||||
|
|
||||||
|
func _retry() -> void:
|
||||||
|
if _retrying or not is_instance_valid(ship):
|
||||||
|
return
|
||||||
|
_retrying = true
|
||||||
|
ship.call("_restart_level_after_destruction")
|
||||||
1
scripts/ui/defeat_overlay.gd.uid
Normal file
1
scripts/ui/defeat_overlay.gd.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://coeyame45wvng
|
||||||
63
tests/items/player_destruction_test.gd
Normal file
63
tests/items/player_destruction_test.gd
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
extends Node2D
|
||||||
|
|
||||||
|
var _finished_count := 0
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
GameState.start_new_run()
|
||||||
|
var game := preload("res://scenes/game/game.tscn").instantiate()
|
||||||
|
add_child(game)
|
||||||
|
game.set_physics_process(false)
|
||||||
|
var ship: CharacterBody2D = game.get_node("PlayerShip")
|
||||||
|
ship.set_physics_process(false)
|
||||||
|
ship.get_node("WeaponController").set_physics_process(false)
|
||||||
|
var camera: Camera2D = ship.get_node("Camera2D")
|
||||||
|
camera.offset = Vector2(3, 5)
|
||||||
|
ship.velocity = Vector2(100, -40)
|
||||||
|
ship.call("_destroy_ship")
|
||||||
|
assert(ship.get("_is_destroyed") and ship.collision_layer == 0 and ship.velocity == Vector2.ZERO)
|
||||||
|
assert(not ship.get_node("Sprite2D").visible and not ship.get_node("ShieldVisual").visible)
|
||||||
|
var effect: Node = game.get_node("PlayerDestructionEffect")
|
||||||
|
effect.set_process(false)
|
||||||
|
assert(effect.get("_texture") == ship.get_node("Sprite2D").texture)
|
||||||
|
assert(effect.get("_fragments").size() >= 4)
|
||||||
|
assert(not game.has_node("DefeatOverlay"))
|
||||||
|
assert(not effect.has_node("ExplosionAudio"))
|
||||||
|
effect.finished.connect(func() -> void: _finished_count += 1)
|
||||||
|
effect._process(0.2)
|
||||||
|
assert(not effect.get("_detonated") and camera.offset != Vector2(3, 5))
|
||||||
|
for step in 18:
|
||||||
|
effect._process(0.1)
|
||||||
|
assert(not effect.get("_detonated") and not game.has_node("DefeatOverlay"))
|
||||||
|
assert(effect.get("_smoke").size() > 0)
|
||||||
|
effect._process(0.35)
|
||||||
|
assert(effect.get("_detonated") and effect.get("_sparks").size() > 30)
|
||||||
|
assert(effect.has_node("ExplosionAudio"))
|
||||||
|
assert(effect.get_node("ExplosionAudio").stream.resource_path == "res://assets/sounds/effects/explosion.mp3")
|
||||||
|
assert(not game.has_node("DefeatOverlay"))
|
||||||
|
effect._process(2.7)
|
||||||
|
assert(not game.has_node("DefeatOverlay"))
|
||||||
|
effect._process(0.3)
|
||||||
|
assert(game.has_node("DefeatOverlay") and _finished_count == 1)
|
||||||
|
assert(camera.offset == Vector2(3, 5))
|
||||||
|
effect._process(0.1)
|
||||||
|
ship.call("_destroy_ship")
|
||||||
|
assert(_finished_count == 1)
|
||||||
|
var deaths := 0
|
||||||
|
for child in game.get_children():
|
||||||
|
if child.name == "PlayerDestructionEffect":
|
||||||
|
deaths += 1
|
||||||
|
assert(deaths == 1)
|
||||||
|
for chassis in preload("res://scripts/game/ship_catalog.gd").SHIPS:
|
||||||
|
var source := Sprite2D.new()
|
||||||
|
source.texture = load(preload("res://scripts/game/ship_catalog.gd").texture_path(chassis))
|
||||||
|
source.scale = Vector2.ONE * 64.0 / source.texture.get_width()
|
||||||
|
var variant := Node2D.new()
|
||||||
|
variant.set_script(preload("res://scripts/game/player_destruction_effect.gd"))
|
||||||
|
variant.configure(source, Vector2.ZERO, null)
|
||||||
|
add_child(variant)
|
||||||
|
variant.set_process(false)
|
||||||
|
assert(variant.get("_fragments").size() >= 4)
|
||||||
|
source.free()
|
||||||
|
variant.queue_free()
|
||||||
|
print("PLAYER_DESTRUCTION_TEST: PASS (ship texture fragments, collision shutdown, overload, detonation, delayed defeat, camera restoration, single death)")
|
||||||
|
get_tree().quit()
|
||||||
1
tests/items/player_destruction_test.gd.uid
Normal file
1
tests/items/player_destruction_test.gd.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://xkpr5tm2f1ed
|
||||||
4
tests/items/player_destruction_test.tscn
Normal file
4
tests/items/player_destruction_test.tscn
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[gd_scene load_steps=2 format=3]
|
||||||
|
[ext_resource type="Script" path="res://tests/items/player_destruction_test.gd" id="1"]
|
||||||
|
[node name="PlayerDestructionTest" type="Node2D"]
|
||||||
|
script = ExtResource("1")
|
||||||
@ -80,7 +80,11 @@ func _ready() -> void:
|
|||||||
assert(menu.visible)
|
assert(menu.visible)
|
||||||
player.position = initial_data["player_start"]
|
player.position = initial_data["player_start"]
|
||||||
player.call("take_damage", 100000.0)
|
player.call("take_damage", 100000.0)
|
||||||
await get_tree().create_timer(1.3).timeout
|
await get_tree().create_timer(preload("res://scripts/game/player_destruction_effect.gd").DURATION + 0.2).timeout
|
||||||
|
assert(game.has_node("DefeatOverlay"))
|
||||||
|
game.get_node("DefeatOverlay").call("_retry")
|
||||||
|
await get_tree().process_frame
|
||||||
|
await get_tree().process_frame
|
||||||
game = get_tree().current_scene as Node2D
|
game = get_tree().current_scene as Node2D
|
||||||
_freeze(game)
|
_freeze(game)
|
||||||
player = game.get_node("PlayerShip") as CharacterBody2D
|
player = game.get_node("PlayerShip") as CharacterBody2D
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user