tail wag works

This commit is contained in:
2026-05-25 18:37:42 +03:00
parent 22ed4f7722
commit 98d579f733
8 changed files with 208 additions and 55 deletions
+86 -23
View File
@@ -5,18 +5,31 @@ enum State { WANDER, RAGDOLL }
var _state: State = State.WANDER
var _skeleton: Skeleton3D = null
var _sim: PhysicalBoneSimulator3D = null
var _anim_player: AnimationPlayer = null
var _wander_target: Vector3 = Vector3.ZERO
var _idle_timer: float = 0.0
@onready var _mesh: Node3D = $matador_v02
@onready var _hit_area: Area3D = $HitArea
@onready var _mesh: Node3D = $matador_v02
@onready var _hit_area: Area3D = $HitArea
@onready var _body_col: CollisionShape3D = $CollisionShape3D
const _WALK_ANIM: StringName = &"Armature|walk"
const _IDLE_ANIM: StringName = &"Armature|idle"
func _ready() -> void:
add_to_group(&"matador")
_skeleton = _find_skeleton(_mesh)
_anim_player = _find_anim_player(_mesh)
if _skeleton:
_sim = _setup_physical_bones()
if _anim_player:
_ensure_loop(_WALK_ANIM)
if _anim_player.has_animation(_IDLE_ANIM):
_anim_player.play(_IDLE_ANIM)
else:
push_warning("Matador: idle animation not found. Available: %s" %
str(_anim_player.get_animation_list()))
_hit_area.body_entered.connect(_on_body_entered)
_pick_wander_target()
@@ -34,6 +47,7 @@ func _tick_wander(delta: float) -> void:
_idle_timer -= delta
velocity.x = move_toward(velocity.x, 0.0, 10.0 * delta)
velocity.z = move_toward(velocity.z, 0.0, 10.0 * delta)
_play_anim(_IDLE_ANIM)
move_and_slide()
return
@@ -49,12 +63,13 @@ func _tick_wander(delta: float) -> void:
velocity.z = dir.z * spd
_mesh.rotation.y = lerp_angle(
_mesh.rotation.y, atan2(velocity.x, velocity.z), delta * 8.0)
_play_anim(_WALK_ANIM)
move_and_slide()
func _tick_ragdoll(delta: float) -> void:
velocity.x = move_toward(velocity.x, 0.0, 5.0 * delta)
velocity.z = move_toward(velocity.z, 0.0, 5.0 * delta)
velocity.x = move_toward(velocity.x, 0.0, 15.0 * delta)
velocity.z = move_toward(velocity.z, 0.0, 15.0 * delta)
move_and_slide()
@@ -73,8 +88,17 @@ func _on_body_entered(body: Node3D) -> void:
func _enter_ragdoll(hit_dir: Vector3, bull_speed: float) -> void:
_state = State.RAGDOLL
hit_dir.y = 0.0
hit_dir = hit_dir.normalized()
velocity = Vector3(hit_dir.x, 0.5, hit_dir.z) * bull_speed * 0.4
hit_dir = hit_dir.normalized()
# Cap lateral slide so matadors don't cross the arena; fixed upward pop
var lateral: float = minf(bull_speed * 0.2, 4.0)
velocity = Vector3(hit_dir.x * lateral, 2.0, hit_dir.z * lateral)
# Disable body capsule so a fast bull doesn't bounce off a ragdolling matador
_body_col.disabled = true
if _anim_player:
_anim_player.stop()
if not _sim:
return
@@ -83,13 +107,35 @@ func _enter_ragdoll(hit_dir: Vector3, bull_speed: float) -> void:
for child: Node in _sim.get_children():
if child is PhysicalBone3D:
var scatter := Vector3(
randf_range(-0.3, 0.3),
randf_range(0.2, 0.8),
randf_range(-0.3, 0.3))
randf_range(-0.4, 0.4),
randf_range(0.1, 0.5),
randf_range(-0.4, 0.4))
(child as PhysicalBone3D).apply_central_impulse(
(hit_dir + scatter).normalized() * strength)
# ── Animation helpers ─────────────────────────────────────────────────────────
func _ensure_loop(anim_name: StringName) -> void:
if _anim_player.has_animation(anim_name):
var anim := _anim_player.get_animation(anim_name)
anim.loop_mode = Animation.LOOP_LINEAR
func _play_anim(anim_name: StringName) -> void:
if not _anim_player:
return
if not _anim_player.has_animation(anim_name):
return
if _anim_player.current_animation != anim_name:
_anim_player.play(anim_name)
func _reset_bone_poses() -> void:
for i: int in _skeleton.get_bone_count():
_skeleton.set_bone_pose_rotation(i, Quaternion.IDENTITY)
func _pick_wander_target() -> void:
var radius := DP.f("mat_wander_radius")
var angle := randf() * TAU
@@ -109,19 +155,25 @@ func _setup_physical_bones() -> PhysicalBoneSimulator3D:
sim.active = false
_skeleton.add_child(sim)
# [bone_name, sphere_radius] or [bone_name, capsule_radius, capsule_height]
const BONES: Array = [
["matador", 0.18],
["chest", 0.13, 0.30],
["head", 0.14],
["arm_L", 0.07, 0.22],
["arm_R", 0.07, 0.22],
["forearm_L", 0.06, 0.22],
["forearm_R", 0.06, 0.22],
["leg_L", 0.10, 0.32],
["leg_R", 0.10, 0.32],
["shin_L", 0.08, 0.28],
["shin_R", 0.08, 0.28],
["matador", 0.18],
["COG", 0.12],
["chest", 0.13, 0.30],
["head", 0.14],
["collarbone_L", 0.06, 0.18],
["collarbone_R", 0.06, 0.18],
["arm_L", 0.07, 0.22],
["arm_R", 0.07, 0.22],
["forearm_L", 0.06, 0.22],
["forearm_R", 0.06, 0.22],
["hand_L", 0.05, 0.12],
["hand_R", 0.05, 0.12],
["leg_L", 0.10, 0.32],
["leg_R", 0.10, 0.32],
["shin_L", 0.08, 0.28],
["shin_R", 0.08, 0.28],
["foot_L", 0.07, 0.18],
["foot_R", 0.07, 0.18],
]
for entry: Array in BONES:
@@ -130,8 +182,9 @@ func _setup_physical_bones() -> PhysicalBoneSimulator3D:
continue
var pb := PhysicalBone3D.new()
pb.bone_name = bname
pb.linear_damp = 0.5
pb.angular_damp = 0.5
pb.joint_type = PhysicalBone3D.JOINT_TYPE_PIN
pb.linear_damp = 1.0
pb.angular_damp = 1.0
var cs := CollisionShape3D.new()
var use_capsule: bool = entry.size() >= 3
if use_capsule:
@@ -159,3 +212,13 @@ func _find_skeleton(node: Node) -> Skeleton3D:
if r:
return r
return null
func _find_anim_player(node: Node) -> AnimationPlayer:
if node is AnimationPlayer:
return node as AnimationPlayer
for child: Node in node.get_children():
var r := _find_anim_player(child)
if r:
return r
return null