Mondherz-Ancients (Saumkern-Kristall als Herz, Sichelklingen, Ringe, Krone, Teamwappen, Energieadern) mit den Stufen stabil/instabil/kritisch, kurzem Zusammenbruch und bleibender Ruine; der Sieg wartet nie auf die Grafik. Turmfamilie aus einem Blender-Generator: Grenzwacht (Tower 1), Bastion (Tower 2), Kernwacht (Basisturm in der Formensprache des Ancients), mit Schadensstufen, Einsturz, Ruine und sichtbarem Angriff (Zielring, Aufladen, Bolzen, Einschlag) - der Schaden bleibt vollständig in der Simulation. Bossgrotte Mondschlund mit Tor, Treppe in die Tiefe, Altar, Säulen, Felsen und fünf Zuständen; der Generator hält Abstand zur Top-Lane, die Höhle braucht eine kleine Geländesenke (nur Darstellung). Superboss Schlundkrone mit 24 Knochen und 11 Clips (inkl. Brüllen und Sieg), Adern in Teamfarbe, dasselbe Modell für Grotte und Lane. Gemeinsame LN-Materialfamilie (Stein, Metall, Kristall, Rune, Stoff, Mondwasser, Kreaturenhaut), Occlusion-Fade ohne dithernde Schatten. Keine Regeländerung. Geprüft: verify_g2 245/245, verify_g1 443/443, Regeln 91/91 und 73/73, Stress 17/17, Balance 40 Partien identisch mit G1, verify_m1 38/38, verify_m2 14/14, Netz 43/43 und Loopback 12/12, Legacy 209 und 463. 21 Ansichten vorher/nachher (1920x1080), Videos Turmkampf und Bossbegegnung, Leistung G1/G2 auf demselben Mac (+0,5 ms in vollen Kampfansichten). Pakete 0.17.0-g2 für macOS (Universal, ad-hoc signiert), Windows x64/ARM64 und Linux; macOS nativ und unter Rosetta, beide Windows-Pakete in Windows 11 ARM (Parallels, OpenGL-Fallback) mit --lune-smoke bestanden. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
424 lines
18 KiB
GDScript
424 lines
18 KiB
GDScript
extends Node3D
|
|
## One Ancient (G2 "Mondherz"): the monumental heart of a base. Reads the authoritative HP
|
|
## and shows four states - stable, unstable (cracks, uneasy energy), critical (heavy cracks,
|
|
## leaking energy, a leaning blade, a fallen fragment, sparks and smoke) and the collapse into
|
|
## a lasting ruin. The collapse is presentation only: the match has already been decided and
|
|
## processed when it starts, and it never delays anything.
|
|
|
|
const Materials = preload("res://scripts/luneraria/view/lune_materials.gd")
|
|
const Fx = preload("res://scripts/luneraria/view/lune_fx.gd")
|
|
enum State { STABLE, UNSTABLE, CRITICAL, COLLAPSING, FALLEN }
|
|
const SCENE_PATH := "res://assets/luneraria/g2/ancient.glb"
|
|
const UNSTABLE_BELOW: float = .605
|
|
const CRITICAL_BELOW: float = .305
|
|
const COLLAPSE_SECONDS: float = 2.8
|
|
const CRYSTAL_Y: float = 4.7
|
|
const BLADE_ANGLES: Array[float] = [0.0, PI * .5, PI, PI * 1.5]
|
|
## Dais steps (outer radius, top height above the plaza) - figures are lifted onto them.
|
|
const DAIS_STEPS: Array = [[4.0, .05], [3.4, .1], [2.8, .15]]
|
|
## Occlusion fade (G2): the blades, rings and heart above this height may thin out.
|
|
const FADE_FLOOR: float = 2.4
|
|
const OCCLUDER_RADIUS: float = 3.2
|
|
const TOP: float = 10.4
|
|
static var _scene: PackedScene
|
|
|
|
var building_id: String = ""
|
|
var team: int = 0
|
|
var state: int = State.STABLE
|
|
var fraction: float = 1.0
|
|
var fade_target: float = 0.0
|
|
var seed_value: float = 0.0
|
|
var light: OmniLight3D
|
|
var crystal: Node3D
|
|
var satellites: Array[Node3D] = []
|
|
var fragments: Array[Node3D] = []
|
|
var rings: Array[Node3D] = []
|
|
var blades: Array[Node3D] = []
|
|
var crown: Node3D
|
|
var crest: Node3D
|
|
var _parts: Dictionary = {}
|
|
var _rest: Dictionary = {}
|
|
var _instances: Array = []
|
|
var _structure: Array = []
|
|
var _crystals: Array = []
|
|
var _cloth: Array = []
|
|
var _ruin: Array = []
|
|
var _sent: Dictionary = {}
|
|
var _motes: GPUParticles3D
|
|
var _sparks: GPUParticles3D
|
|
var _smoke: GPUParticles3D
|
|
var _dust: GPUParticles3D
|
|
var _burst: GPUParticles3D
|
|
var _flash: MeshInstance3D
|
|
var _bar: Node3D
|
|
var _bar_fill: MeshInstance3D
|
|
var _time: float = 0.0
|
|
var _collapse_time: float = -1.0
|
|
var _smoke_left: float = 0.0
|
|
var _hit: float = 0.0
|
|
var _fade: float = 0.0
|
|
var _burst_fired: bool = false
|
|
var _fallen_fragment: int = 0
|
|
## Energy leaking from the cracked heart (critical state): fast, irregular sparks upwards.
|
|
var _leak: GPUParticles3D
|
|
## Critical state: the inner ring has slipped and hangs tilted; one satellite sags (unstable).
|
|
var _ring_slipped: bool = false
|
|
var _sag: float = 0.0
|
|
|
|
func setup(definition: Dictionary, facing: Vector2) -> void:
|
|
building_id = str(definition.id)
|
|
team = int(definition.team)
|
|
name = building_id
|
|
seed_value = 3.0 + team * 5.0
|
|
if _scene == null:
|
|
_scene = load(SCENE_PATH)
|
|
var model: Node3D = _scene.instantiate()
|
|
model.name = "Model"
|
|
add_child(model)
|
|
model.rotation.y = atan2(facing.x, facing.y)
|
|
for child: Node in model.get_children():
|
|
_parts[str(child.name)] = child
|
|
_rest[str(child.name)] = (child as Node3D).transform
|
|
_instances = Materials.dress(model)
|
|
Materials.apply_team(_instances, team, seed_value)
|
|
crystal = _parts.get("Crystal")
|
|
crown = _parts.get("Crown")
|
|
crest = _parts.get("Crest_A" if team == 0 else "Crest_B")
|
|
(_parts["Crest_B" if team == 0 else "Crest_A"] as Node3D).visible = false
|
|
for index: int in range(6):
|
|
if _parts.has("Satellite_%d" % index):
|
|
satellites.append(_parts["Satellite_%d" % index])
|
|
if _parts.has("Fragment_%d" % index):
|
|
fragments.append(_parts["Fragment_%d" % index])
|
|
for key: String in ["Ring_Outer", "Ring_Inner"]:
|
|
rings.append(_parts[key])
|
|
for index: int in range(4):
|
|
blades.append(_parts["Blade_%d" % index])
|
|
for key: String in _parts:
|
|
var list: Array = _collect(_parts[key])
|
|
if key == "Crystal" or key.begins_with("Satellite") or key.begins_with("Fragment"):
|
|
_crystals.append_array(list)
|
|
elif key.begins_with("Banner"):
|
|
_cloth.append_array(list)
|
|
elif key.begins_with("Ruin"):
|
|
_ruin.append_array(list)
|
|
else:
|
|
_structure.append_array(list)
|
|
Materials.apply_rune_tint(_structure, team)
|
|
# The glowing heart, satellites, fragments and the banners cast no sun shadow.
|
|
for instance: GeometryInstance3D in _crystals + _cloth:
|
|
instance.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
|
|
Materials.set_param(_structure, "crack_glow", 0.0)
|
|
(_parts["Ruin_Crystal"] as Node3D).visible = false
|
|
(_parts["Ruin_Rubble"] as Node3D).visible = false
|
|
light = OmniLight3D.new()
|
|
light.name = "HeartLight"
|
|
light.position = Vector3(0, CRYSTAL_Y, 0)
|
|
light.light_color = Materials.team_color(team, "light")
|
|
light.omni_range = 13.0
|
|
light.omni_attenuation = 1.2
|
|
light.light_energy = 2.2
|
|
add_child(light)
|
|
var color: Color = Materials.team_color(team, "crystal")
|
|
_motes = Fx.motes(color, 22, 2.4, 4.0)
|
|
_motes.position = Vector3(0, 2.4, 0)
|
|
_motes.emitting = true
|
|
add_child(_motes)
|
|
_sparks = Fx.sparks(Materials.team_color(team, "light"), 26, 2.1, 3.0)
|
|
_sparks.position = Vector3(0, 3.2, 0)
|
|
add_child(_sparks)
|
|
_smoke = Fx.smoke(22, 1.4, .15, 2.4, 4.5)
|
|
_smoke.position = Vector3(0, 2.2, 0)
|
|
add_child(_smoke)
|
|
_leak = Fx.leak(color, 34, .9)
|
|
_leak.position = Vector3(0, CRYSTAL_Y - .6, 0)
|
|
add_child(_leak)
|
|
_dust = Fx.dust_burst(70, 2.6, 2.6)
|
|
add_child(_dust)
|
|
_burst = Fx.shard_burst(color, 90, 8.0)
|
|
add_child(_burst)
|
|
_flash = Fx.flash_mesh(Color(color.r * 2.0, color.g * 2.0, color.b * 2.0, 1.0), 9.0)
|
|
_flash.position = Vector3(0, CRYSTAL_Y, 0)
|
|
_flash.visible = false
|
|
add_child(_flash)
|
|
_build_bar(11.2)
|
|
Materials.set_param(_instances, "fade_floor", global_position.y + FADE_FLOOR)
|
|
|
|
func _collect(node: Node) -> Array:
|
|
var result: Array = []
|
|
if node is GeometryInstance3D:
|
|
result.append(node)
|
|
for child: Node in node.get_children():
|
|
result.append_array(_collect(child))
|
|
return result
|
|
|
|
func _param(group: Array, group_key: String, parameter: String, value: float, epsilon: float = .01) -> void:
|
|
var key: String = group_key + ":" + parameter
|
|
if _sent.has(key) and absf(float(_sent[key]) - value) < epsilon:
|
|
return
|
|
_sent[key] = value
|
|
Materials.set_param(group, parameter, value)
|
|
|
|
func _build_bar(height: float) -> void:
|
|
_bar = Node3D.new()
|
|
_bar.name = "HealthBar"
|
|
_bar.position.y = height
|
|
add_child(_bar)
|
|
var width: float = 3.4
|
|
var back := MeshInstance3D.new()
|
|
var quad := QuadMesh.new()
|
|
quad.size = Vector2(width + .1, .26)
|
|
back.mesh = quad
|
|
var back_material := StandardMaterial3D.new()
|
|
back_material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
|
back_material.albedo_color = Color(.03, .03, .05, .78)
|
|
back_material.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
|
back_material.billboard_mode = BaseMaterial3D.BILLBOARD_ENABLED
|
|
back_material.no_depth_test = true
|
|
back.material_override = back_material
|
|
back.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
|
|
_bar.add_child(back)
|
|
_bar_fill = MeshInstance3D.new()
|
|
var fill := QuadMesh.new()
|
|
fill.size = Vector2(width, .17)
|
|
fill.center_offset = Vector3(width * .5, 0, 0)
|
|
_bar_fill.mesh = fill
|
|
var fill_material := StandardMaterial3D.new()
|
|
fill_material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
|
fill_material.albedo_color = Materials.team_color(team, "light").lightened(.15)
|
|
fill_material.billboard_mode = BaseMaterial3D.BILLBOARD_ENABLED
|
|
fill_material.billboard_keep_scale = true
|
|
fill_material.no_depth_test = true
|
|
fill_material.render_priority = 1
|
|
_bar_fill.material_override = fill_material
|
|
_bar_fill.position.x = -width * .5
|
|
_bar_fill.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
|
|
_bar.add_child(_bar_fill)
|
|
_bar.visible = false
|
|
|
|
## Height of the dais or plaza surface above the plateau at a local ground distance.
|
|
static func surface_lift(distance: float) -> float:
|
|
var lift: float = 0.0
|
|
for step: Array in DAIS_STEPS:
|
|
if distance < float(step[0]):
|
|
lift = float(step[1])
|
|
return lift
|
|
|
|
func is_standing() -> bool:
|
|
return state != State.COLLAPSING and state != State.FALLEN
|
|
|
|
## World box of the part that can hide figures (above the fade floor); empty once fallen.
|
|
func occluder_box() -> AABB:
|
|
if not is_standing():
|
|
return AABB()
|
|
return AABB(global_position + Vector3(-OCCLUDER_RADIUS, FADE_FLOOR, -OCCLUDER_RADIUS),
|
|
Vector3(OCCLUDER_RADIUS * 2.0, TOP - FADE_FLOOR, OCCLUDER_RADIUS * 2.0))
|
|
|
|
func current_fade() -> float:
|
|
return _fade
|
|
|
|
## 0..1 energy of the heart for the floor veins (dims with damage, gone when fallen).
|
|
func vein_energy() -> float:
|
|
return {State.STABLE: 1.0, State.UNSTABLE: .75, State.CRITICAL: .5}.get(state, 0.0) * (1.0 + _hit * .8)
|
|
|
|
func vein_flicker() -> float:
|
|
return {State.STABLE: 0.0, State.UNSTABLE: .25, State.CRITICAL: .7}.get(state, 1.0)
|
|
|
|
func set_health(value: float, max_value: float, animate: bool = true) -> void:
|
|
var previous: float = fraction
|
|
fraction = clampf(value / maxf(1.0, max_value), 0.0, 1.0)
|
|
if fraction < previous - .0003 and value > 0.0:
|
|
_hit = 1.0
|
|
var next: int = State.STABLE
|
|
if value <= 0.0:
|
|
if state == State.COLLAPSING or state == State.FALLEN:
|
|
next = state
|
|
else:
|
|
next = State.COLLAPSING if animate else State.FALLEN
|
|
elif fraction < CRITICAL_BELOW:
|
|
next = State.CRITICAL
|
|
elif fraction < UNSTABLE_BELOW:
|
|
next = State.UNSTABLE
|
|
if next != state:
|
|
_enter(next)
|
|
_bar.visible = value > 0.0 and fraction < .999
|
|
_bar_fill.scale.x = maxf(.001, fraction)
|
|
|
|
func _enter(next: int) -> void:
|
|
var from: int = state
|
|
state = next
|
|
var critical: bool = next == State.CRITICAL
|
|
_sparks.emitting = next == State.UNSTABLE or critical
|
|
_sparks.amount_ratio = .55 if next == State.UNSTABLE else 1.0
|
|
_smoke.emitting = critical
|
|
_leak.emitting = critical
|
|
_ring_slipped = critical
|
|
_sag = {State.UNSTABLE: .6, State.CRITICAL: 1.0}.get(next, 0.0)
|
|
_motes.emitting = next in [State.STABLE, State.UNSTABLE, State.CRITICAL]
|
|
_motes.amount_ratio = {State.STABLE: .6, State.UNSTABLE: .8, State.CRITICAL: 1.0}.get(next, 0.0)
|
|
var damage: float = {State.STABLE: 0.0, State.UNSTABLE: .45, State.CRITICAL: .85}.get(next, .9)
|
|
_param(_structure, "structure", "damage", damage)
|
|
_param(_structure, "structure", "crack_glow", {State.UNSTABLE: .8, State.CRITICAL: 1.5}.get(next, 0.0))
|
|
_param(_structure, "structure", "scorch", {State.STABLE: 0.0, State.UNSTABLE: .2, State.CRITICAL: .45}.get(next, .75))
|
|
_param(_cloth, "cloth", "tatter", {State.STABLE: 0.0, State.UNSTABLE: .3, State.CRITICAL: .6}.get(next, .85))
|
|
match next:
|
|
State.CRITICAL:
|
|
# Two blades have come loose (one leans far out), two fragments lie on the dais.
|
|
for spec: Array in [[0, -.3, -.1], [2, .14, -.04]]:
|
|
var blade: Node3D = blades[int(spec[0])]
|
|
var axis: Vector3 = _tangent(int(spec[0]))
|
|
var rest_blade: Transform3D = _rest[str(blade.name)]
|
|
blade.transform = Transform3D(Basis(axis, float(spec[1])) * rest_blade.basis, rest_blade.origin + Vector3(0, float(spec[2]), 0))
|
|
_fallen_fragment = 2
|
|
State.COLLAPSING:
|
|
_collapse_time = 0.0
|
|
_burst_fired = false
|
|
State.FALLEN:
|
|
_collapse_time = -1.0
|
|
_pose_fallen(1.0)
|
|
_leak.emitting = false
|
|
_smoke.emitting = from == State.COLLAPSING
|
|
_smoke.amount_ratio = 1.0
|
|
_smoke_left = 25.0 if from == State.COLLAPSING else 0.0
|
|
light.visible = false
|
|
_bar.visible = false
|
|
Materials.set_param(_ruin, "energy", .16)
|
|
Materials.set_param(_ruin, "flicker", .7)
|
|
Materials.set_param(_ruin, "fracture", .8)
|
|
Materials.set_param(_ruin, "damage", .7)
|
|
_param(_structure, "structure", "energy", .04)
|
|
_param(_structure, "structure", "flicker", 0.0)
|
|
|
|
func _tangent(index: int) -> Vector3:
|
|
var a: float = BLADE_ANGLES[index]
|
|
return Vector3(-sin(a), 0, cos(a))
|
|
|
|
func process_view(delta: float, _sim_delta: float) -> void:
|
|
_time += delta
|
|
_hit = maxf(0.0, _hit - delta * 2.5)
|
|
var calm: float = {State.STABLE: 1.0, State.UNSTABLE: .85, State.CRITICAL: .7}.get(state, 0.0)
|
|
var unrest: float = {State.STABLE: 0.0, State.UNSTABLE: .35, State.CRITICAL: 1.0}.get(state, 0.0)
|
|
if is_standing():
|
|
var jitter: float = sin(_time * 21.0) * sin(_time * 7.7) * .02 * unrest
|
|
crystal.position = (_rest["Crystal"] as Transform3D).origin + Vector3(jitter, sin(_time * 1.1) * .07 * calm, 0)
|
|
crystal.rotation.y += delta * (.18 + unrest * .25)
|
|
for index: int in range(satellites.size()):
|
|
var a: float = _time * (.42 + unrest * .2) + TAU * index / satellites.size()
|
|
var r: float = 1.25 + .1 * sin(_time * .9 + index)
|
|
var y: float = CRYSTAL_Y - 1.1 + .35 * sin(_time * .7 + index * 1.3)
|
|
if index == 0 and _sag > 0.0:
|
|
# One satellite has lost its hold: it sags and trembles.
|
|
y -= 1.1 * _sag
|
|
r += .35 * _sag + sin(_time * 13.0) * .03 * _sag
|
|
satellites[index].position = Vector3(cos(a) * r, y, sin(a) * r)
|
|
satellites[index].rotation.y = -a
|
|
for index: int in range(fragments.size()):
|
|
if index < _fallen_fragment:
|
|
fragments[index].position = _fragment_rest(index)
|
|
continue
|
|
var rest: Vector3 = (_rest[str(fragments[index].name)] as Transform3D).origin
|
|
var angle: float = atan2(rest.z, rest.x) + _time * .12
|
|
var radius: float = Vector2(rest.x, rest.z).length()
|
|
fragments[index].position = Vector3(cos(angle) * radius, rest.y + sin(_time * .8 + index) * .18, sin(angle) * radius)
|
|
fragments[index].rotation.y += delta * .5
|
|
var wobble: float = unrest * .08
|
|
rings[0].rotation = Vector3(sin(_time * .6) * wobble, _time * .32, sin(_time * .9) * wobble)
|
|
if _ring_slipped:
|
|
# The inner ring has slipped from its bearing: tilted, grinding round in jerks.
|
|
var jerk: float = floorf(_time * 1.5) * .35 + smoothstep(0.0, .25, fmod(_time * 1.5, 1.0)) * .35
|
|
rings[1].rotation = Vector3(.5 + sin(_time * 2.3) * .03, -jerk, .22)
|
|
else:
|
|
rings[1].rotation = Vector3(sin(_time * .8 + 1.0) * wobble * 1.6, -_time * .46, cos(_time * .7) * wobble * 1.6)
|
|
var flicker: float = {State.STABLE: 0.0, State.UNSTABLE: .25, State.CRITICAL: .85}.get(state, .9)
|
|
_param(_crystals, "crystal", "energy", calm * (1.0 + _hit * 1.4), .02)
|
|
_param(_crystals, "crystal", "flicker", flicker)
|
|
_param(_crystals, "crystal", "fracture", unrest * .8)
|
|
_param(_structure, "structure", "energy", .45 + calm * .55 + _hit * .5, .02)
|
|
_param(_structure, "structure", "flicker", flicker * .8)
|
|
var pulse: float = 1.0
|
|
if flicker > 0.0:
|
|
pulse = lerpf(1.0, .5 + .5 * absf(sin(_time * 17.0 + seed_value) * sin(_time * 5.3)), flicker)
|
|
light.light_energy = (1.0 + calm * 1.3) * pulse * (1.0 + _hit * .8)
|
|
elif state == State.COLLAPSING:
|
|
_collapse_step(delta)
|
|
elif state == State.FALLEN and _smoke_left > 0.0:
|
|
_smoke_left -= delta
|
|
if _smoke_left <= 0.0:
|
|
_smoke.emitting = false
|
|
_fade = move_toward(_fade, fade_target, delta * 3.0)
|
|
_param(_instances, "all", "fade", _fade, .02)
|
|
|
|
func _fragment_rest(index: int) -> Vector3:
|
|
var rest: Vector3 = (_rest[str(fragments[index].name)] as Transform3D).origin
|
|
var flat := Vector2(rest.x, rest.z).normalized() * 2.95
|
|
return Vector3(flat.x, .32, flat.y)
|
|
|
|
## Collapse (2.8 s): the heart overloads, implodes and bursts; the blades crumble into the
|
|
## dust, rings and crown fall onto the pedestal, fragments drop; the ruin remains.
|
|
func _collapse_step(delta: float) -> void:
|
|
_collapse_time += delta
|
|
var t: float = _collapse_time
|
|
var overload: float = smoothstep(0.0, .5, t) * (1.0 - smoothstep(.55, .75, t))
|
|
if crystal.visible:
|
|
crystal.scale = Vector3.ONE * maxf(.05, 1.0 + overload * .14 - smoothstep(.5, .72, t) * .95)
|
|
crystal.rotation.y += delta * (1.0 + overload * 6.0)
|
|
Materials.set_param(_crystals, "energy", 1.0 + overload * 3.5)
|
|
Materials.set_param(_crystals, "flicker", 1.0)
|
|
light.light_energy = 2.2 + overload * 7.0 if t < .8 else maxf(0.0, 9.0 * (1.0 - (t - .8) / .9))
|
|
for index: int in range(rings.size()):
|
|
rings[index].rotation.y += delta * (2.0 + overload * 8.0) * (1.0 if index == 0 else -1.0)
|
|
if t >= .7 and not _burst_fired:
|
|
_burst_fired = true
|
|
Fx.fire(_burst, crystal.global_position)
|
|
Fx.fire(_dust, global_position + Vector3(0, .4, 0))
|
|
crystal.visible = false
|
|
for satellite: Node3D in satellites:
|
|
satellite.visible = false
|
|
(_parts["Ruin_Crystal"] as Node3D).visible = true
|
|
(_parts["Ruin_Rubble"] as Node3D).visible = true
|
|
_flash.visible = true
|
|
_smoke.emitting = true
|
|
if _flash.visible:
|
|
var k: float = clampf((t - .7) / .55, 0.0, 1.0)
|
|
_flash.scale = Vector3.ONE * lerpf(.4, 1.6, k)
|
|
_flash.transparency = k
|
|
if k >= 1.0:
|
|
_flash.visible = false
|
|
if t >= .7:
|
|
_pose_fallen(smoothstep(.7, 2.2, t))
|
|
if t >= COLLAPSE_SECONDS:
|
|
_enter(State.FALLEN)
|
|
|
|
## Interpolates the falling parts from where they stand to their resting place (k = 0..1).
|
|
func _pose_fallen(k: float) -> void:
|
|
var fall: float = k * k
|
|
for index: int in range(blades.size()):
|
|
var blade: Node3D = blades[index]
|
|
var rest: Transform3D = _rest[str(blade.name)]
|
|
var axis: Vector3 = _tangent(index)
|
|
var out := Vector3(cos(BLADE_ANGLES[index]), 0, sin(BLADE_ANGLES[index]))
|
|
blade.transform = Transform3D(Basis(axis, -.45 * fall) * rest.basis, rest.origin + out * .8 * fall + Vector3(0, -2.6 * fall, 0))
|
|
blade.scale = Vector3.ONE * lerpf(1.0, .02, smoothstep(.45, .9, k))
|
|
blade.visible = k < .9
|
|
var landings: Array = [[Vector3(.25, 1.42, -.1), Vector3(.34, 0, .12)], [Vector3(-.3, 1.5, .15), Vector3(-.28, 0, -.42)]]
|
|
for index: int in range(rings.size()):
|
|
var rest: Transform3D = _rest[str(rings[index].name)]
|
|
var land: Array = landings[index]
|
|
rings[index].position = rest.origin.lerp(land[0], fall)
|
|
rings[index].rotation = (rings[index].rotation as Vector3).lerp(land[1], k)
|
|
for node: Node3D in [crown, crest]:
|
|
var rest: Transform3D = _rest[str(node.name)]
|
|
node.position = rest.origin.lerp(Vector3(.2, 1.62, .1), fall)
|
|
node.rotation = Vector3(.45, .3, .22) * k
|
|
for index: int in range(fragments.size()):
|
|
var start: Vector3 = (_rest[str(fragments[index].name)] as Transform3D).origin
|
|
fragments[index].position = start.lerp(_fragment_rest(index), fall)
|
|
fragments[index].rotation = Vector3(1.2, float(index), .4) * k
|
|
if k >= 1.0:
|
|
crystal.visible = false
|
|
for satellite: Node3D in satellites:
|
|
satellite.visible = false
|
|
(_parts["Ruin_Crystal"] as Node3D).visible = true
|
|
(_parts["Ruin_Rubble"] as Node3D).visible = true
|
|
Materials.set_param(_crystals, "energy", .12)
|
|
Materials.set_param(_crystals, "flicker", .8)
|