saumwacht-godot/scripts/presentation/animated_figure.gd
Martin Stickelbroeck c2b447610c Baseline: Saumwacht 0.15.0 (Phase 20A) vor dem Umbau zu Champions of Luneraria
Unveränderter Ausgangsstand inklusive Masterprompt und Konzeptbildern
unter assets/. .godot/ und docs/screenshots/ bleiben laut .gitignore
unversioniert; letztere sind unter ../backups gesichert.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 20:00:36 +02:00

89 lines
3.3 KiB
GDScript

extends Node3D
## Rigid stone joints: meshes shared across units, transforms owned per instance.
var asset_id: String = ""
var parts: Dictionary = {}
var rest_bounds: AABB
var _walk: float = 0.0
var _hit_material: StandardMaterial3D
var _core_health: float = 1.0
var _core_damage: float = 0.0
var _core_activate: float = 0.0
func setup(id: String, groups: Dictionary, bounds: AABB) -> void:
asset_id = id
rest_bounds = bounds
name = "Animated_" + id
_hit_material = StandardMaterial3D.new()
_hit_material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
_hit_material.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
_hit_material.albedo_color = Color(1.0, .83, .49, 0.0)
for key: String in groups:
var instance := MeshInstance3D.new()
instance.name = key
instance.mesh = groups[key].mesh
instance.position = groups[key].pivot
instance.set_meta("rest", instance.position)
instance.material_overlay = _hit_material
add_child(instance)
parts[key] = instance
func set_core_health(ratio: float) -> void:
var next: float = clampf(ratio, 0.0, 1.0)
if next < _core_health - .002:
_core_damage = 1.0
_core_health = next
func trigger_activate() -> void:
_core_activate = 1.0
func pose(time: float, moving: bool, attack: float, ability: float, hit: float, delta: float = 0.0) -> void:
_walk = move_toward(_walk, 1.0 if moving else 0.0, .12)
var stride: float = sin(time * 8.0) * _walk
var strike: float = sin(clampf(attack / .28, 0.0, 1.0) * PI)
_core_damage = maxf(0.0, _core_damage - delta * 1.8)
_core_activate = maxf(0.0, _core_activate - delta * .55)
_hit_material.albedo_color.a = clampf(maxf(hit * 2.4, _core_damage * .45), 0.0, .5)
for key: String in parts:
var part: MeshInstance3D = parts[key]
part.position = part.get_meta("rest")
part.rotation = Vector3.ZERO
part.scale = Vector3.ONE
if key.begins_with("Leg"):
var side: float = 1.0 if key.begins_with("LegL") else -1.0
if key.right(1).is_valid_int() and int(key.right(1)) % 2 == 1:
side *= -1.0
part.rotation.x = stride * side * .30
part.position.y += maxf(0, stride * side) * .05
continue
match key:
"ArmL", "ArmR":
var side: float = 1.0 if key == "ArmL" else -1.0
part.rotation.x = -stride * side * .16 - strike * (.52 if asset_id in ["g01","g02","g03","g04","e05","e08"] else .12)
part.rotation.z = strike * side * .08
"Weapon":
part.position.z -= strike * (.22 if asset_id == "g06" else .10)
if asset_id == "g05":
part.rotation.y = strike * .22
"Mortar":
part.rotation.x = -strike * .16
part.position.y -= strike * .07
"Flame":
part.scale.y = 1.0 + sin(time * 11.0) * .09 + ability * .45
"Crystal":
# Activation flare, calm float, then a readable shudder when hurt.
var distress: float = 1.0 - _core_health
var pulse: float = sin(time * (1.5 + distress * 2.2))
part.position.y += pulse * (.045 + distress * .018) + _core_activate * .14
part.rotation.y = sin(time * .35) * .08 + _core_damage * .16 * sin(time * 36.0)
part.scale = Vector3.ONE * (1.0 + _core_activate * .18 + _core_damage * .07)
"Body":
part.rotation.z = stride * .025
if asset_id in ["e03","e06","e09"]:
part.position.y += sin(time*2.4)*.06
if asset_id == "e10":
part.rotation.x = -strike*.15
part.position.z -= strike*.10
func get_aabb() -> AABB:
return rest_bounds