feat: emotive animations + anime speed lines — hit flinch, dance emote (B)
- SkinnedPlayerModel: generic play_oneshot() (generalizes the Land lock), Hit and Dance in the clip table (Dance loops), restartable clips - Hit flinch: victims visibly react on every peer's screen via the existing damage broadcast — no new RPC - Dance emote on B: toggles while grounded and idle, breaks on any movement; synced to remotes via synced_is_dancing (all three spawner configs) - Anime radial speed-lines overlay (canvas shader): fades in past ~1.2x walk speed, spikes on dash, widescreen-corrected clear center - New input action 'emote' (B) in project.godot 11/11 FSM tests, spawn smoke 0 failures. Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Fable 5
parent
37c15bed1a
commit
5bcbcfdc3f
@@ -75,6 +75,11 @@ var synced_position: Vector3 = Vector3.ZERO
|
||||
var synced_velocity: Vector3 = Vector3.ZERO
|
||||
var synced_is_ads: bool = false
|
||||
var synced_wall_side: float = 0.0 # -1 wall left, +1 wall right (wall-run lean)
|
||||
var synced_is_dancing: bool = false # dance emote (B), shown on the model
|
||||
|
||||
# Anime speed-lines overlay (local player only)
|
||||
var _speedlines: ColorRect = null
|
||||
var _speedline_burst: float = 0.0
|
||||
@export var synced_skin_id: String = ""
|
||||
@export var synced_weapon_path: String = ""
|
||||
|
||||
@@ -130,6 +135,7 @@ func _ready() -> void:
|
||||
add_child(_damage_layer)
|
||||
_setup_hit_marker()
|
||||
_setup_hud()
|
||||
_setup_speedlines()
|
||||
else:
|
||||
call_deferred("_hide_remote_weapons")
|
||||
|
||||
@@ -315,6 +321,18 @@ func _setup_audio() -> void:
|
||||
add_child(grapple_swing_player)
|
||||
grapple_swing_player.play()
|
||||
|
||||
func _setup_speedlines() -> void:
|
||||
_speedlines = ColorRect.new()
|
||||
_speedlines.name = "SpeedLines"
|
||||
_speedlines.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
_speedlines.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
var mat := ShaderMaterial.new()
|
||||
mat.shader = load("res://assets/shaders/speed_lines.gdshader")
|
||||
mat.set_shader_parameter("intensity", 0.0)
|
||||
_speedlines.material = mat
|
||||
_damage_layer.add_child(_speedlines)
|
||||
|
||||
|
||||
func _setup_hit_marker() -> void:
|
||||
hit_marker = Control.new()
|
||||
hit_marker.set_anchors_preset(Control.PRESET_CENTER)
|
||||
@@ -665,6 +683,12 @@ func rpc_take_damage(amount: float, hit_pos: Vector3, attacker_id: int, weapon_n
|
||||
if attacker_id != 0:
|
||||
recent_attackers[attacker_id] = Time.get_ticks_msec() / 1000.0
|
||||
|
||||
# Flinch: the victim's model plays a hit reaction on every screen.
|
||||
if amount > 0.0:
|
||||
var vm = get_visual_model()
|
||||
if vm and vm.has_method("play_oneshot"):
|
||||
vm.play_oneshot("Hit", 0.3)
|
||||
|
||||
# Knockback lands on the simulating peer (movement is client-authoritative)
|
||||
if is_multiplayer_authority() and impulse.length_squared() > 0.01:
|
||||
apply_impulse(impulse)
|
||||
@@ -817,6 +841,22 @@ func _physics_process(_delta: float) -> void:
|
||||
if Input.is_action_just_pressed("toggle_camera_view"):
|
||||
set_third_person(not third_person)
|
||||
|
||||
# Dance emote (B): toggles while grounded and idle-ish; any
|
||||
# movement/jump/crouch input breaks it.
|
||||
if Input.is_action_just_pressed("emote"):
|
||||
var m := _ensure_machine()
|
||||
var slow: bool = Vector2(velocity.x, velocity.z).length() < 1.0
|
||||
if not synced_is_dancing and m and m.current_state == "ground" and slow:
|
||||
synced_is_dancing = true
|
||||
else:
|
||||
synced_is_dancing = false
|
||||
if synced_is_dancing:
|
||||
var m2 := _ensure_machine()
|
||||
var moving := raw_input.length() > 0.1 or input_jump or input_crouch or input_dash
|
||||
var airborne: bool = m2 and m2.current_state != "ground"
|
||||
if moving or airborne:
|
||||
synced_is_dancing = false
|
||||
|
||||
var machine := _ensure_machine()
|
||||
if machine:
|
||||
machine.input_dir = raw_input
|
||||
@@ -834,6 +874,16 @@ func _physics_process(_delta: float) -> void:
|
||||
if not wind_player.playing and wind_factor > 0.0:
|
||||
wind_player.play()
|
||||
|
||||
# Anime speed lines: fade in past ~1.2x walk speed, spike on dash.
|
||||
if is_instance_valid(_speedlines):
|
||||
_speedline_burst = maxf(_speedline_burst - _delta * 2.5, 0.0)
|
||||
var hs := Vector2(velocity.x, velocity.z).length()
|
||||
var speed_intensity := clampf((hs - params.walk_speed * 1.2) / 12.0, 0.0, 0.85)
|
||||
var target := maxf(speed_intensity, _speedline_burst)
|
||||
var mat := _speedlines.material as ShaderMaterial
|
||||
var cur: float = mat.get_shader_parameter("intensity")
|
||||
mat.set_shader_parameter("intensity", lerpf(cur, target, 1.0 - exp(-10.0 * _delta)))
|
||||
|
||||
var sm := _ensure_machine()
|
||||
if not sm:
|
||||
return
|
||||
@@ -879,6 +929,8 @@ func _physics_process(_delta: float) -> void:
|
||||
visual.set_locomotion(d.x, d.y, 1.0 if synced_is_ads else 0.0)
|
||||
if visual.has_method("set_wall_side"):
|
||||
visual.set_wall_side(sm.wall_side)
|
||||
if visual.has_method("set_dancing"):
|
||||
visual.set_dancing(synced_is_dancing)
|
||||
|
||||
# Publish state for remote peers
|
||||
synced_movement_state = sm.current_state
|
||||
@@ -918,6 +970,8 @@ func _on_movement_event(ev: String, data: Dictionary) -> void:
|
||||
grapple_shoot_player.play()
|
||||
elif ev == "grapple_latch":
|
||||
grapple_latch_player.play()
|
||||
elif ev == "dash":
|
||||
_speedline_burst = 1.0
|
||||
elif ev == "land":
|
||||
# Landing thud: reuse the footstep sample, pitched down and louder
|
||||
# with impact. Ground state resets pitch/volume before each step.
|
||||
@@ -960,6 +1014,8 @@ func _process(delta: float) -> void:
|
||||
visual.set_locomotion(d.x, d.y, 1.0 if synced_is_ads else 0.0)
|
||||
if visual.has_method("set_wall_side"):
|
||||
visual.set_wall_side(synced_wall_side)
|
||||
if visual.has_method("set_dancing"):
|
||||
visual.set_dancing(synced_is_dancing)
|
||||
# Check for weapon changes
|
||||
if synced_weapon_path != "" and synced_weapon_path != visual.get_meta("current_weapon_path", ""):
|
||||
visual.set_weapon(synced_weapon_path)
|
||||
|
||||
Reference in New Issue
Block a user