1. Ankle cuffs. The previous pass re-weighted the offending vertices to the nearer leg, which was the wrong call: the strip between the ankles is REAL geometry spanning the gap (~10 cm of ClothCAndW crossing x = -0.05 .. +0.05 at ankle height), so re-weighting only tore it in half — a visible seam that still stretched. A triangle with one corner weighted to each leg has no correct pose; it must stretch the moment the legs separate. SkinMeshRepair now deletes those triangles instead (48 on Taila: the cloth band plus its outline shell). The cut is limited to BELOW THE KNEE, taken from the skeleton's own rest pose rather than a hardcoded height, because above the knee cross-leg geometry is legitimate — the skirt and shorts genuinely span left-thigh to right-thigh weights at the crotch. 2. The M4 floated because _measure_weapon derived the grip from mesh AABBs, and the FBX guns report bind-pose bounds tens of metres across — it measured the M4 as 24 m long and pushed the gun 7.5 m in front of the character. Bounding boxes are simply not trustworthy for these meshes. The grip was already authored elsewhere: WeaponManager places the first-person viewmodel's hands at fixed points in weapon space, and every weapon marks its barrel tip with muzzle_flash.position. Those move to WeaponGrips (dependency-free, so both the weapon system and the character models can use it without dragging each other's load order along) and third person now reaches for exactly the points the viewmodel uses. Every weapon's grip now lands 0.073 m from the hand — the M4 included, down from 2.32 m — with barrel lengths that match the models (0.30 m for the MP7, 0.68 m for the DMR). 3. Muzzle flash and tracers were spawned off the viewmodel's muzzle. The viewmodel is parented to the camera, so its muzzle sits inside the player's head — in third person the flash appeared by the character's shoulder. world_muzzle() returns the muzzle of the gun actually in the character's hands whenever the character is what the viewer sees, and the networked fire-effect RPC now sends that position too, so remote players stop seeing tracers leave the shooter's head. Measured: the third-person origin sits at the held gun, 0.54 m below the head, instead of on the camera. FSM tests 11/11, spawn smoke test 0 failures. Co-Authored-By: Claude Opus 4.8 <[email protected]>
532 lines
17 KiB
GDScript
532 lines
17 KiB
GDScript
extends Node3D
|
|
class_name HumanoidModel
|
|
|
|
@export var color: Color = Color.WHITE
|
|
@export var shadows_only: bool = false
|
|
|
|
# Internal Pivots
|
|
var root_pivot: Node3D
|
|
var torso: MeshInstance3D
|
|
var head_pivot: Node3D
|
|
var head: MeshInstance3D
|
|
|
|
var upper_arm_l_pivot: Node3D
|
|
var upper_arm_l: MeshInstance3D
|
|
var lower_arm_l_pivot: Node3D
|
|
var lower_arm_l: MeshInstance3D
|
|
|
|
var upper_arm_r_pivot: Node3D
|
|
var upper_arm_r: MeshInstance3D
|
|
var lower_arm_r_pivot: Node3D
|
|
var lower_arm_r: MeshInstance3D
|
|
var hand_r_pivot: Node3D
|
|
|
|
var thigh_l_pivot: Node3D
|
|
var thigh_l: MeshInstance3D
|
|
var calf_l_pivot: Node3D
|
|
var calf_l: MeshInstance3D
|
|
|
|
var thigh_r_pivot: Node3D
|
|
var thigh_r: MeshInstance3D
|
|
var calf_r_pivot: Node3D
|
|
var calf_r: MeshInstance3D
|
|
|
|
# Animation State Variables
|
|
var current_state: String = "idle"
|
|
var movement_speed: float = 0.0
|
|
var _anim_time: float = 0.0
|
|
|
|
func _ready() -> void:
|
|
rotation_degrees.y = 180
|
|
|
|
var mat := StandardMaterial3D.new()
|
|
mat.albedo_color = color
|
|
mat.roughness = 0.8
|
|
|
|
var shadow_setting = GeometryInstance3D.SHADOW_CASTING_SETTING_SHADOWS_ONLY if shadows_only else GeometryInstance3D.SHADOW_CASTING_SETTING_ON
|
|
|
|
root_pivot = Node3D.new()
|
|
add_child(root_pivot)
|
|
|
|
# Torso
|
|
torso = MeshInstance3D.new()
|
|
var t_mesh = BoxMesh.new()
|
|
t_mesh.size = Vector3(0.4, 0.7, 0.25)
|
|
t_mesh.material = mat
|
|
torso.mesh = t_mesh
|
|
torso.position = Vector3(0, 1.15, 0)
|
|
torso.cast_shadow = shadow_setting
|
|
root_pivot.add_child(torso)
|
|
|
|
# Head
|
|
head_pivot = Node3D.new()
|
|
head_pivot.position = Vector3(0, 1.5, 0)
|
|
root_pivot.add_child(head_pivot)
|
|
|
|
head = MeshInstance3D.new()
|
|
var h_mesh = BoxMesh.new()
|
|
h_mesh.size = Vector3(0.25, 0.25, 0.25)
|
|
h_mesh.material = mat
|
|
head.mesh = h_mesh
|
|
head.position = Vector3(0, 0.15, 0) # Offset from neck
|
|
head.cast_shadow = shadow_setting
|
|
head_pivot.add_child(head)
|
|
|
|
# Arms (Upper: 0.35 length, Lower: 0.35 length)
|
|
var ua_mesh = BoxMesh.new()
|
|
ua_mesh.size = Vector3(0.12, 0.35, 0.12)
|
|
ua_mesh.material = mat
|
|
var la_mesh = BoxMesh.new()
|
|
la_mesh.size = Vector3(0.1, 0.35, 0.1)
|
|
la_mesh.material = mat
|
|
|
|
# Left Arm
|
|
upper_arm_l_pivot = Node3D.new()
|
|
upper_arm_l_pivot.position = Vector3(-0.28, 1.45, 0) # Shoulder
|
|
root_pivot.add_child(upper_arm_l_pivot)
|
|
upper_arm_l = MeshInstance3D.new()
|
|
upper_arm_l.mesh = ua_mesh
|
|
upper_arm_l.position = Vector3(0, -0.175, 0)
|
|
upper_arm_l.cast_shadow = shadow_setting
|
|
upper_arm_l_pivot.add_child(upper_arm_l)
|
|
|
|
lower_arm_l_pivot = Node3D.new()
|
|
lower_arm_l_pivot.position = Vector3(0, -0.35, 0) # Elbow
|
|
upper_arm_l_pivot.add_child(lower_arm_l_pivot)
|
|
lower_arm_l = MeshInstance3D.new()
|
|
lower_arm_l.mesh = la_mesh
|
|
lower_arm_l.position = Vector3(0, -0.175, 0)
|
|
lower_arm_l.cast_shadow = shadow_setting
|
|
lower_arm_l_pivot.add_child(lower_arm_l)
|
|
|
|
# Right Arm
|
|
upper_arm_r_pivot = Node3D.new()
|
|
upper_arm_r_pivot.position = Vector3(0.28, 1.45, 0) # Shoulder
|
|
root_pivot.add_child(upper_arm_r_pivot)
|
|
upper_arm_r = MeshInstance3D.new()
|
|
upper_arm_r.mesh = ua_mesh
|
|
upper_arm_r.position = Vector3(0, -0.175, 0)
|
|
upper_arm_r.cast_shadow = shadow_setting
|
|
upper_arm_r_pivot.add_child(upper_arm_r)
|
|
|
|
lower_arm_r_pivot = Node3D.new()
|
|
lower_arm_r_pivot.position = Vector3(0, -0.35, 0) # Elbow
|
|
upper_arm_r_pivot.add_child(lower_arm_r_pivot)
|
|
lower_arm_r = MeshInstance3D.new()
|
|
lower_arm_r.mesh = la_mesh
|
|
lower_arm_r.position = Vector3(0, -0.175, 0)
|
|
lower_arm_r.cast_shadow = shadow_setting
|
|
lower_arm_r_pivot.add_child(lower_arm_r)
|
|
|
|
# Right hand: the wrist joint at the far end of the forearm. The
|
|
# third-person weapon hangs here so it travels with the arm instead of
|
|
# floating in front of the chest.
|
|
hand_r_pivot = Node3D.new()
|
|
hand_r_pivot.position = Vector3(0, -0.35, 0)
|
|
lower_arm_r_pivot.add_child(hand_r_pivot)
|
|
|
|
# Legs (Thigh: 0.45 length, Calf: 0.45 length)
|
|
var t_leg_mesh = BoxMesh.new()
|
|
t_leg_mesh.size = Vector3(0.15, 0.45, 0.15)
|
|
t_leg_mesh.material = mat
|
|
var c_leg_mesh = BoxMesh.new()
|
|
c_leg_mesh.size = Vector3(0.13, 0.45, 0.13)
|
|
c_leg_mesh.material = mat
|
|
|
|
# Left Leg
|
|
thigh_l_pivot = Node3D.new()
|
|
thigh_l_pivot.position = Vector3(-0.12, 0.85, 0) # Hip (raised slightly for 0.45+0.45=0.9 total)
|
|
root_pivot.add_child(thigh_l_pivot)
|
|
thigh_l = MeshInstance3D.new()
|
|
thigh_l.mesh = t_leg_mesh
|
|
thigh_l.position = Vector3(0, -0.225, 0)
|
|
thigh_l.cast_shadow = shadow_setting
|
|
thigh_l_pivot.add_child(thigh_l)
|
|
|
|
calf_l_pivot = Node3D.new()
|
|
calf_l_pivot.position = Vector3(0, -0.45, 0) # Knee
|
|
thigh_l_pivot.add_child(calf_l_pivot)
|
|
calf_l = MeshInstance3D.new()
|
|
calf_l.mesh = c_leg_mesh
|
|
calf_l.position = Vector3(0, -0.225, 0)
|
|
calf_l.cast_shadow = shadow_setting
|
|
calf_l_pivot.add_child(calf_l)
|
|
|
|
# Right Leg
|
|
thigh_r_pivot = Node3D.new()
|
|
thigh_r_pivot.position = Vector3(0.12, 0.85, 0) # Hip
|
|
root_pivot.add_child(thigh_r_pivot)
|
|
thigh_r = MeshInstance3D.new()
|
|
thigh_r.mesh = t_leg_mesh
|
|
thigh_r.position = Vector3(0, -0.225, 0)
|
|
thigh_r.cast_shadow = shadow_setting
|
|
thigh_r_pivot.add_child(thigh_r)
|
|
|
|
calf_r_pivot = Node3D.new()
|
|
calf_r_pivot.position = Vector3(0, -0.45, 0) # Knee
|
|
thigh_r_pivot.add_child(calf_r_pivot)
|
|
calf_r = MeshInstance3D.new()
|
|
calf_r.mesh = c_leg_mesh
|
|
calf_r.position = Vector3(0, -0.225, 0)
|
|
calf_r.cast_shadow = shadow_setting
|
|
calf_r_pivot.add_child(calf_r)
|
|
|
|
# Cel-shade + ink outline the blocky fallback body so it matches skinned
|
|
# player models (shadows-only bodies skip it: outlines would cast shadows)
|
|
if not shadows_only:
|
|
LevelMaterials.apply_toon_recursive(root_pivot, 0.004)
|
|
|
|
## Skin System
|
|
var current_skin: PlayerSkin
|
|
var skin_model: Node3D
|
|
|
|
func apply_skin(skin: PlayerSkin) -> void:
|
|
current_skin = skin
|
|
if skin.model_path != "" and skin.model_path != null:
|
|
var loaded = load(skin.model_path)
|
|
if loaded:
|
|
_set_procedural_visible(false)
|
|
if skin_model:
|
|
skin_model.queue_free()
|
|
skin_model = null
|
|
skin_model = loaded.instantiate()
|
|
if skin_model:
|
|
skin_model.name = "SkinModel"
|
|
# Fix orientation: Blender Z-up to Godot Y-up
|
|
skin_model.rotation_degrees = Vector3(0, 0, 0)
|
|
# Scale down if needed (Blender models can be large)
|
|
skin_model.scale = Vector3(1, 1, 1)
|
|
add_child(skin_model)
|
|
print("HumanoidModel: applied skin '%s'" % skin.skin_name)
|
|
return
|
|
_set_procedural_visible(true)
|
|
if skin_model:
|
|
skin_model.queue_free()
|
|
skin_model = null
|
|
_apply_color(skin.color_tint)
|
|
|
|
func _set_procedural_visible(on: bool) -> void:
|
|
for part in [torso, head, upper_arm_l, lower_arm_l, upper_arm_r, lower_arm_r, thigh_l, calf_l, thigh_r, calf_r]:
|
|
if part:
|
|
part.visible = on
|
|
|
|
## Show/hide the model to its OWNER (third-person toggle). off = shadows-only so
|
|
## the local first-person camera doesn't see the body; on = fully visible.
|
|
func set_owner_visible(on: bool) -> void:
|
|
var mode := GeometryInstance3D.SHADOW_CASTING_SETTING_ON if on \
|
|
else GeometryInstance3D.SHADOW_CASTING_SETTING_SHADOWS_ONLY
|
|
_set_owner_shadow_recursive(self, mode)
|
|
|
|
func _set_owner_shadow_recursive(node: Node, mode: int) -> void:
|
|
if node is GeometryInstance3D:
|
|
node.cast_shadow = mode
|
|
for child in node.get_children():
|
|
_set_owner_shadow_recursive(child, mode)
|
|
|
|
func _apply_color(col: Color) -> void:
|
|
var mat = StandardMaterial3D.new()
|
|
mat.albedo_color = col
|
|
mat.roughness = 0.8
|
|
for part in [torso, upper_arm_l, lower_arm_l, upper_arm_r, lower_arm_r, thigh_l, calf_l, thigh_r, calf_r]:
|
|
if part and part.mesh:
|
|
part.mesh.material = mat
|
|
if head and head.mesh:
|
|
var hmat = StandardMaterial3D.new()
|
|
hmat.albedo_color = Color(0.95, 0.85, 0.78)
|
|
hmat.roughness = 0.8
|
|
head.mesh.material = hmat
|
|
# Re-toon after the material swap (fresh StandardMaterials above)
|
|
if not shadows_only:
|
|
LevelMaterials.apply_toon_recursive(root_pivot, 0.004)
|
|
|
|
func update_state(state: String, speed: float, is_crouching: bool = false) -> void:
|
|
current_state = state
|
|
movement_speed = speed
|
|
if is_crouching and current_state == "ground":
|
|
current_state = "crouch"
|
|
|
|
var is_holding_weapon: bool = false
|
|
|
|
func set_weapon(script_path: String) -> void:
|
|
# Clear existing weapons (older builds parented them to root_pivot)
|
|
for parent in [root_pivot, hand_r_pivot]:
|
|
if not parent:
|
|
continue
|
|
for child in parent.get_children():
|
|
if child.has_meta("is_third_person_weapon"):
|
|
child.queue_free()
|
|
|
|
if script_path == "":
|
|
is_holding_weapon = false
|
|
return
|
|
|
|
is_holding_weapon = true
|
|
|
|
var script = load(script_path)
|
|
if not script:
|
|
return
|
|
|
|
var w = script.new()
|
|
w.name = "ThirdPersonWeapon"
|
|
w.set_meta("is_third_person_weapon", true)
|
|
|
|
# Connect to ready so we can override the weapon's own _ready() calls
|
|
w.ready.connect(func():
|
|
w.set_process(false)
|
|
w.set_process_input(false)
|
|
if shadows_only:
|
|
_set_shadows_recursive(w)
|
|
else:
|
|
# Toon shading only — FBX weapon normals tear inverted-hull outlines
|
|
LevelMaterials.apply_toon_recursive(w, 0.0)
|
|
|
|
# Undo the first-person viewmodel placement from _build_model()
|
|
_seat_weapon_in_hand(w)
|
|
)
|
|
|
|
# Riding the hand keeps the gun with the arm; _process re-aims it forward.
|
|
(hand_r_pivot if hand_r_pivot else root_pivot).add_child(w)
|
|
|
|
|
|
## The muzzle of the gun actually in this character's hand — see
|
|
## SkinnedPlayerModel.get_muzzle_node() for why world effects must use it.
|
|
func get_muzzle_node() -> Node3D:
|
|
var holder := hand_r_pivot if hand_r_pivot else root_pivot
|
|
if not holder:
|
|
return null
|
|
for child in holder.get_children():
|
|
if child.has_meta("is_third_person_weapon"):
|
|
if "muzzle_flash" in child and child.muzzle_flash:
|
|
return child.muzzle_flash
|
|
return child as Node3D
|
|
return null
|
|
|
|
|
|
## Slide the weapon along its own barrel axis so a plausible grip point — not
|
|
## whatever origin the artist left the model at — ends up in the fist, and turn
|
|
## it to face the way the body faces (the model is yawed 180° in _ready, so the
|
|
## gun's -Z muzzle axis has to point along the model's local +Z).
|
|
func _seat_weapon_in_hand(w: Node3D) -> void:
|
|
w.rotation_degrees = Vector3(0, 180, 0)
|
|
w.position = Vector3.ZERO
|
|
var min_t := INF # most negative along the barrel = stock end
|
|
var max_t := -INF # most positive = muzzle end
|
|
for mi in w.find_children("*", "MeshInstance3D", true, false):
|
|
if not mi.mesh:
|
|
continue
|
|
# mi.get_aabb(), NOT mi.mesh.get_aabb(): the FBX gun parts are skinned and
|
|
# the Mesh resource still carries huge bind-pose bounds.
|
|
var xf: Transform3D = w.global_transform.affine_inverse() * mi.global_transform
|
|
var aabb: AABB = mi.get_aabb()
|
|
for i in 8:
|
|
var t: float = (xf * aabb.get_endpoint(i)).dot(Vector3(0, 0, -1))
|
|
min_t = minf(min_t, t)
|
|
max_t = maxf(max_t, t)
|
|
if min_t > max_t:
|
|
return
|
|
# ~a third back from the muzzle is where a pistol grip sits on every gun in
|
|
# the set, which leaves real length of weapon behind the hand.
|
|
var grip_at := min_t + (max_t - min_t) * 0.32
|
|
w.position = Vector3(0, 0, -grip_at)
|
|
|
|
func _set_shadows_recursive(node: Node) -> void:
|
|
if node is GeometryInstance3D:
|
|
node.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_SHADOWS_ONLY
|
|
for child in node.get_children():
|
|
_set_shadows_recursive(child)
|
|
|
|
func _process(delta: float) -> void:
|
|
var anim_speed = 1.0
|
|
if current_state == "ground" and movement_speed > 1.0:
|
|
anim_speed = movement_speed * 0.4
|
|
|
|
_anim_time += delta * anim_speed
|
|
|
|
# Target values
|
|
var t_root_pos := Vector3.ZERO
|
|
var t_root_rot := Vector3.ZERO
|
|
var t_upper_arm_l_rot := Vector3.ZERO
|
|
var t_upper_arm_r_rot := Vector3.ZERO
|
|
var t_lower_arm_l_rot := Vector3.ZERO
|
|
var t_lower_arm_r_rot := Vector3.ZERO
|
|
var t_thigh_l_rot := Vector3.ZERO
|
|
var t_thigh_r_rot := Vector3.ZERO
|
|
var t_calf_l_rot := Vector3.ZERO
|
|
var t_calf_r_rot := Vector3.ZERO
|
|
|
|
match current_state:
|
|
"ground", "idle":
|
|
if movement_speed > 1.0:
|
|
# Run cycle
|
|
var swing = sin(_anim_time * 5.0)
|
|
# Hips
|
|
t_thigh_l_rot.x = -swing * 1.6
|
|
t_thigh_r_rot.x = swing * 1.6
|
|
# Knees bend backwards (positive X rotation) when leg swings back
|
|
# We add a bit of bend even when moving forward so it's not stiff
|
|
t_calf_l_rot.x = max(0.1, swing * 1.6 + 0.3)
|
|
t_calf_r_rot.x = max(0.1, -swing * 1.6 + 0.3)
|
|
|
|
# Arms
|
|
t_upper_arm_l_rot.x = swing * 1.0
|
|
t_upper_arm_r_rot.x = -swing * 1.0
|
|
# Elbows bend forwards (negative X rotation)
|
|
t_lower_arm_l_rot.x = -1.2 + max(0.0, -swing * 0.5)
|
|
t_lower_arm_r_rot.x = -1.2 + max(0.0, swing * 0.5)
|
|
|
|
# Bounce slightly
|
|
t_root_pos.y = abs(cos(_anim_time * 5.0)) * 0.1
|
|
else:
|
|
# Idle
|
|
var breath = sin(_anim_time * 2.0)
|
|
t_root_pos.y = breath * 0.02
|
|
t_upper_arm_l_rot.z = 0.05 + breath * 0.01
|
|
t_upper_arm_r_rot.z = -0.05 - breath * 0.01
|
|
t_lower_arm_l_rot.x = -0.1
|
|
t_lower_arm_r_rot.x = -0.1
|
|
t_calf_l_rot.x = 0.05
|
|
t_calf_r_rot.x = 0.05
|
|
|
|
"crouch":
|
|
t_root_pos.y = -0.5
|
|
# Thighs forward, calves back
|
|
t_thigh_l_rot.x = -1.0
|
|
t_thigh_r_rot.x = -1.0
|
|
t_calf_l_rot.x = 1.0
|
|
t_calf_r_rot.x = 1.0
|
|
# Arms bent defensively
|
|
t_upper_arm_l_rot.x = -0.5
|
|
t_upper_arm_r_rot.x = -0.5
|
|
t_lower_arm_l_rot.x = -1.2
|
|
t_lower_arm_r_rot.x = -1.2
|
|
|
|
"slide":
|
|
t_root_pos.y = -0.6
|
|
t_root_rot.x = deg_to_rad(-45) # Lean back
|
|
# Legs forward
|
|
t_thigh_l_rot.x = deg_to_rad(-60)
|
|
t_thigh_r_rot.x = deg_to_rad(-30)
|
|
t_calf_l_rot.x = 0.1
|
|
t_calf_r_rot.x = 0.1
|
|
# Arms back
|
|
t_upper_arm_l_rot.x = deg_to_rad(60)
|
|
t_upper_arm_r_rot.x = deg_to_rad(60)
|
|
t_lower_arm_l_rot.x = -0.2
|
|
t_lower_arm_r_rot.x = -0.2
|
|
|
|
"air":
|
|
# Falling / Jumping
|
|
t_thigh_l_rot.x = -0.4
|
|
t_thigh_r_rot.x = 0.2
|
|
t_calf_l_rot.x = 0.5
|
|
t_calf_r_rot.x = 0.1
|
|
|
|
t_upper_arm_l_rot.x = -0.8
|
|
t_upper_arm_r_rot.x = -0.8
|
|
t_upper_arm_l_rot.z = 0.5
|
|
t_upper_arm_r_rot.z = -0.5
|
|
t_lower_arm_l_rot.x = -0.5
|
|
t_lower_arm_r_rot.x = -0.5
|
|
|
|
"wall_run":
|
|
t_root_rot.z = deg_to_rad(-15)
|
|
var cycle = sin(_anim_time * 25.0)
|
|
t_thigh_l_rot.x = cycle * 0.8
|
|
t_thigh_r_rot.x = -cycle * 0.8
|
|
t_calf_l_rot.x = max(0.1, -cycle * 1.0 + 0.4)
|
|
t_calf_r_rot.x = max(0.1, cycle * 1.0 + 0.4)
|
|
|
|
t_upper_arm_l_rot.x = -cycle * 0.8
|
|
t_upper_arm_r_rot.x = cycle * 0.8
|
|
t_lower_arm_l_rot.x = -1.0
|
|
t_lower_arm_r_rot.x = -1.0
|
|
|
|
"wall_cling":
|
|
# Spiderman hanging
|
|
t_thigh_l_rot.x = deg_to_rad(-80)
|
|
t_thigh_r_rot.x = deg_to_rad(-40)
|
|
t_calf_l_rot.x = deg_to_rad(80)
|
|
t_calf_r_rot.x = deg_to_rad(40)
|
|
|
|
t_upper_arm_l_rot.x = deg_to_rad(-140)
|
|
t_upper_arm_r_rot.x = deg_to_rad(-140)
|
|
t_lower_arm_l_rot.x = deg_to_rad(-40)
|
|
t_lower_arm_r_rot.x = deg_to_rad(-40)
|
|
|
|
"grapple":
|
|
t_upper_arm_l_rot.x = deg_to_rad(-160)
|
|
t_lower_arm_l_rot.x = 0.0
|
|
t_upper_arm_r_rot.x = deg_to_rad(20)
|
|
t_lower_arm_r_rot.x = deg_to_rad(-90)
|
|
|
|
t_thigh_l_rot.x = deg_to_rad(-30)
|
|
t_calf_l_rot.x = deg_to_rad(30)
|
|
t_thigh_r_rot.x = deg_to_rad(20)
|
|
t_calf_r_rot.x = deg_to_rad(10)
|
|
|
|
"dash":
|
|
t_root_rot.x = deg_to_rad(-70)
|
|
t_upper_arm_l_rot.x = deg_to_rad(-170)
|
|
t_lower_arm_l_rot.x = 0.0
|
|
t_upper_arm_r_rot.x = deg_to_rad(-170)
|
|
t_lower_arm_r_rot.x = 0.0
|
|
|
|
t_thigh_l_rot.x = deg_to_rad(20)
|
|
t_calf_l_rot.x = 0.0
|
|
t_thigh_r_rot.x = deg_to_rad(20)
|
|
t_calf_r_rot.x = 0.0
|
|
|
|
"death":
|
|
t_root_rot.x = deg_to_rad(-90)
|
|
t_root_pos.y = -0.5
|
|
t_upper_arm_l_rot.x = deg_to_rad(170)
|
|
t_lower_arm_l_rot.x = 0.0
|
|
t_upper_arm_r_rot.x = deg_to_rad(170)
|
|
t_lower_arm_r_rot.x = 0.0
|
|
t_thigh_l_rot.x = deg_to_rad(-10)
|
|
t_calf_l_rot.x = 0.0
|
|
t_thigh_r_rot.x = deg_to_rad(10)
|
|
t_calf_r_rot.x = 0.0
|
|
|
|
# Override right arm (and left) if holding a weapon
|
|
if is_holding_weapon and current_state != "death":
|
|
# Right arm holds the grip: elbow tucked at the ribs, forearm level so
|
|
# the fist (and the weapon parented to it) sits chest-high just in
|
|
# front of the torso rather than out at arm's length.
|
|
t_upper_arm_r_rot.x = -0.15
|
|
t_upper_arm_r_rot.z = -0.10
|
|
t_lower_arm_r_rot.x = -1.45
|
|
|
|
# Left arm crosses over to the handguard: up, in, and a little further
|
|
# forward than the grip hand.
|
|
t_upper_arm_l_rot.x = -0.55
|
|
t_upper_arm_l_rot.z = 0.55
|
|
t_lower_arm_l_rot.x = -1.0
|
|
|
|
var lerp_speed = 40.0 * delta
|
|
root_pivot.position = root_pivot.position.lerp(t_root_pos, lerp_speed)
|
|
root_pivot.rotation = _lerp_vec3(root_pivot.rotation, t_root_rot, lerp_speed)
|
|
|
|
upper_arm_l_pivot.rotation = _lerp_vec3(upper_arm_l_pivot.rotation, t_upper_arm_l_rot, lerp_speed)
|
|
upper_arm_r_pivot.rotation = _lerp_vec3(upper_arm_r_pivot.rotation, t_upper_arm_r_rot, lerp_speed)
|
|
lower_arm_l_pivot.rotation = _lerp_vec3(lower_arm_l_pivot.rotation, t_lower_arm_l_rot, lerp_speed)
|
|
lower_arm_r_pivot.rotation = _lerp_vec3(lower_arm_r_pivot.rotation, t_lower_arm_r_rot, lerp_speed)
|
|
|
|
thigh_l_pivot.rotation = _lerp_vec3(thigh_l_pivot.rotation, t_thigh_l_rot, lerp_speed)
|
|
thigh_r_pivot.rotation = _lerp_vec3(thigh_r_pivot.rotation, t_thigh_r_rot, lerp_speed)
|
|
calf_l_pivot.rotation = _lerp_vec3(calf_l_pivot.rotation, t_calf_l_rot, lerp_speed)
|
|
calf_r_pivot.rotation = _lerp_vec3(calf_r_pivot.rotation, t_calf_r_rot, lerp_speed)
|
|
|
|
# The weapon rides the hand for position, but its AIM belongs to the body:
|
|
# cancel the arm chain's rotation so the barrel stays pointed where the
|
|
# player faces instead of swinging around with the arm animation.
|
|
if hand_r_pivot:
|
|
hand_r_pivot.global_basis = root_pivot.global_basis
|
|
|
|
func _lerp_vec3(a: Vector3, b: Vector3, t: float) -> Vector3:
|
|
return Vector3(
|
|
lerp_angle(a.x, b.x, t),
|
|
lerp_angle(a.y, b.y, t),
|
|
lerp_angle(a.z, b.z, t)
|
|
)
|