feat: adding better animations

This commit is contained in:
DottsGit
2026-06-06 00:37:54 -04:00
parent d0716f72e8
commit 0c00f4c385
2 changed files with 297 additions and 128 deletions
+226 -114
View File
@@ -9,14 +9,26 @@ var root_pivot: Node3D
var torso: MeshInstance3D
var head_pivot: Node3D
var head: MeshInstance3D
var arm_l_pivot: Node3D
var arm_l: MeshInstance3D
var arm_r_pivot: Node3D
var arm_r: MeshInstance3D
var leg_l_pivot: Node3D
var leg_l: MeshInstance3D
var leg_r_pivot: Node3D
var leg_r: 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 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"
@@ -57,51 +69,97 @@ func _ready() -> void:
head.cast_shadow = shadow_setting
head_pivot.add_child(head)
# Arms
var a_mesh = BoxMesh.new()
a_mesh.size = Vector3(0.12, 0.65, 0.12)
a_mesh.material = mat
# 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
arm_l_pivot = Node3D.new()
arm_l_pivot.position = Vector3(-0.28, 1.45, 0) # Shoulder
root_pivot.add_child(arm_l_pivot)
arm_l = MeshInstance3D.new()
arm_l.mesh = a_mesh
arm_l.position = Vector3(0, -0.3, 0) # Offset down from shoulder
arm_l.cast_shadow = shadow_setting
arm_l_pivot.add_child(arm_l)
# 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)
arm_r_pivot = Node3D.new()
arm_r_pivot.position = Vector3(0.28, 1.45, 0) # Shoulder
root_pivot.add_child(arm_r_pivot)
arm_r = MeshInstance3D.new()
arm_r.mesh = a_mesh
arm_r.position = Vector3(0, -0.3, 0) # Offset down from shoulder
arm_r.cast_shadow = shadow_setting
arm_r_pivot.add_child(arm_r)
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)
# Legs
var l_mesh = BoxMesh.new()
l_mesh.size = Vector3(0.15, 0.8, 0.15)
l_mesh.material = mat
# 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)
leg_l_pivot = Node3D.new()
leg_l_pivot.position = Vector3(-0.12, 0.8, 0) # Hip
root_pivot.add_child(leg_l_pivot)
leg_l = MeshInstance3D.new()
leg_l.mesh = l_mesh
leg_l.position = Vector3(0, -0.4, 0) # Offset down from hip
leg_l.cast_shadow = shadow_setting
leg_l_pivot.add_child(leg_l)
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)
leg_r_pivot = Node3D.new()
leg_r_pivot.position = Vector3(0.12, 0.8, 0) # Hip
root_pivot.add_child(leg_r_pivot)
leg_r = MeshInstance3D.new()
leg_r.mesh = l_mesh
leg_r.position = Vector3(0, -0.4, 0) # Offset down from hip
leg_r.cast_shadow = shadow_setting
leg_r_pivot.add_child(leg_r)
# 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)
func update_state(state: String, speed: float, is_crouching: bool = false) -> void:
current_state = state
@@ -110,7 +168,6 @@ func update_state(state: String, speed: float, is_crouching: bool = false) -> vo
current_state = "crouch"
func _process(delta: float) -> void:
# Advance time. For walk, speed it up based on movement speed.
var anim_speed = 1.0
if current_state == "ground" and movement_speed > 1.0:
anim_speed = movement_speed * 0.4
@@ -120,107 +177,162 @@ func _process(delta: float) -> void:
# Target values
var t_root_pos := Vector3.ZERO
var t_root_rot := Vector3.ZERO
var t_arm_l_rot := Vector3.ZERO
var t_arm_r_rot := Vector3.ZERO
var t_leg_l_rot := Vector3.ZERO
var t_leg_r_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:
# Walk / Run
var swing = sin(_anim_time * 15.0)
t_arm_l_rot.x = swing * 0.8
t_arm_r_rot.x = -swing * 0.8
t_leg_l_rot.x = -swing * 0.8
t_leg_r_rot.x = swing * 0.8
# 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 * 15.0)) * 0.1
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_arm_l_rot.z = 0.05 + breath * 0.01
t_arm_r_rot.z = -0.05 - breath * 0.01
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
# Bend legs to simulate squat
t_leg_l_rot.x = -1.0
t_leg_r_rot.x = -1.0
# Arms tucked
t_arm_l_rot.x = 0.5
t_arm_r_rot.x = 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(-60) # Lean back
# Legs extended
t_leg_l_rot.x = deg_to_rad(-45)
t_leg_r_rot.x = deg_to_rad(60)
# Arms swept back for balance
t_arm_l_rot.x = deg_to_rad(60)
t_arm_r_rot.x = deg_to_rad(60)
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_leg_l_rot.x = -0.2
t_leg_r_rot.x = 0.2
t_arm_l_rot.x = -0.5
t_arm_r_rot.x = -0.5
t_arm_l_rot.z = 0.3
t_arm_r_rot.z = -0.3
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":
# Tilt away from wall slightly
t_root_rot.z = deg_to_rad(-15)
# Fast cycle legs
var cycle = sin(_anim_time * 25.0)
t_leg_l_rot.x = cycle * 0.6
t_leg_r_rot.x = -cycle * 0.6
t_arm_l_rot.x = -cycle * 0.6
t_arm_r_rot.x = cycle * 0.6
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":
# Hanging onto the wall
t_leg_l_rot.x = deg_to_rad(-45)
t_leg_r_rot.x = deg_to_rad(30)
t_arm_l_rot.x = deg_to_rad(-160)
t_arm_r_rot.x = deg_to_rad(-160)
# 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":
# One arm forward toward target, legs dangling
t_arm_l_rot.x = deg_to_rad(-120)
t_arm_r_rot.x = deg_to_rad(10)
t_leg_l_rot.x = deg_to_rad(15)
t_leg_r_rot.x = deg_to_rad(-10)
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":
# Superman pose
t_root_rot.x = deg_to_rad(-70)
t_arm_l_rot.x = deg_to_rad(-160)
t_arm_r_rot.x = deg_to_rad(-160)
t_leg_l_rot.x = deg_to_rad(20)
t_leg_r_rot.x = deg_to_rad(20)
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":
# Fall backwards like a plank
t_root_rot.x = deg_to_rad(-90)
t_root_pos.y = -0.5 # Shift down slightly so it rests on floor
t_arm_l_rot.x = deg_to_rad(170)
t_arm_r_rot.x = deg_to_rad(170)
t_leg_l_rot.x = deg_to_rad(-10)
t_leg_r_rot.x = deg_to_rad(10)
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
# Smoothly interpolate towards target values
var lerp_speed = 15.0 * delta
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)
arm_l_pivot.rotation = _lerp_vec3(arm_l_pivot.rotation, t_arm_l_rot, lerp_speed)
arm_r_pivot.rotation = _lerp_vec3(arm_r_pivot.rotation, t_arm_r_rot, lerp_speed)
leg_l_pivot.rotation = _lerp_vec3(leg_l_pivot.rotation, t_leg_l_rot, lerp_speed)
leg_r_pivot.rotation = _lerp_vec3(leg_r_pivot.rotation, t_leg_r_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)
func _lerp_vec3(a: Vector3, b: Vector3, t: float) -> Vector3:
return Vector3(
+71 -14
View File
@@ -11,27 +11,59 @@ func build_ragdoll(color: Color) -> void:
# Create bodies
torso_body = _create_body(Vector3(0.4, 0.7, 0.25), mat, Vector3(0, 1.15, 0), 20.0)
var head_body = _create_body(Vector3(0.25, 0.25, 0.25), mat, Vector3(0, 1.65, 0), 5.0)
var arm_l_body = _create_body(Vector3(0.12, 0.65, 0.12), mat, Vector3(-0.28, 1.15, 0), 5.0)
var arm_r_body = _create_body(Vector3(0.12, 0.65, 0.12), mat, Vector3(0.28, 1.15, 0), 5.0)
var leg_l_body = _create_body(Vector3(0.15, 0.8, 0.15), mat, Vector3(-0.12, 0.4, 0), 8.0)
var leg_r_body = _create_body(Vector3(0.15, 0.8, 0.15), mat, Vector3(0.12, 0.4, 0), 8.0)
var upper_arm_l_body = _create_body(Vector3(0.12, 0.35, 0.12), mat, Vector3(-0.28, 1.275, 0), 3.0)
var lower_arm_l_body = _create_body(Vector3(0.1, 0.35, 0.1), mat, Vector3(-0.28, 0.925, 0), 2.0)
var upper_arm_r_body = _create_body(Vector3(0.12, 0.35, 0.12), mat, Vector3(0.28, 1.275, 0), 3.0)
var lower_arm_r_body = _create_body(Vector3(0.1, 0.35, 0.1), mat, Vector3(0.28, 0.925, 0), 2.0)
var thigh_l_body = _create_body(Vector3(0.15, 0.45, 0.15), mat, Vector3(-0.12, 0.625, 0), 5.0)
var calf_l_body = _create_body(Vector3(0.13, 0.45, 0.13), mat, Vector3(-0.12, 0.175, 0), 3.0)
var thigh_r_body = _create_body(Vector3(0.15, 0.45, 0.15), mat, Vector3(0.12, 0.625, 0), 5.0)
var calf_r_body = _create_body(Vector3(0.13, 0.45, 0.13), mat, Vector3(0.12, 0.175, 0), 3.0)
add_child(torso_body)
add_child(head_body)
add_child(arm_l_body)
add_child(arm_r_body)
add_child(leg_l_body)
add_child(leg_r_body)
add_child(upper_arm_l_body)
add_child(lower_arm_l_body)
add_child(upper_arm_r_body)
add_child(lower_arm_r_body)
add_child(thigh_l_body)
add_child(calf_l_body)
add_child(thigh_r_body)
add_child(calf_r_body)
# Need to wait a frame for paths to be valid before creating joints
call_deferred("_setup_joints", head_body, arm_l_body, arm_r_body, leg_l_body, leg_r_body)
call_deferred("_setup_joints", head_body,
upper_arm_l_body, lower_arm_l_body,
upper_arm_r_body, lower_arm_r_body,
thigh_l_body, calf_l_body,
thigh_r_body, calf_r_body)
func _setup_joints(head_body, arm_l_body, arm_r_body, leg_l_body, leg_r_body) -> void:
func _setup_joints(head_body,
u_arm_l, l_arm_l,
u_arm_r, l_arm_r,
thigh_l, calf_l,
thigh_r, calf_r) -> void:
# Neck
_create_joint(torso_body, head_body, Vector3(0, 1.5, 0))
_create_joint(torso_body, arm_l_body, Vector3(-0.28, 1.45, 0))
_create_joint(torso_body, arm_r_body, Vector3(0.28, 1.45, 0))
_create_joint(torso_body, leg_l_body, Vector3(-0.12, 0.8, 0))
_create_joint(torso_body, leg_r_body, Vector3(0.12, 0.8, 0))
# Shoulders
_create_joint(torso_body, u_arm_l, Vector3(-0.28, 1.45, 0))
_create_joint(torso_body, u_arm_r, Vector3(0.28, 1.45, 0))
# Hips
_create_joint(torso_body, thigh_l, Vector3(-0.12, 0.85, 0))
_create_joint(torso_body, thigh_r, Vector3(0.12, 0.85, 0))
# Elbows (Hinge, bends forwards only)
_create_hinge(u_arm_l, l_arm_l, Vector3(-0.28, 1.10, 0), -PI * 0.8, 0.0)
_create_hinge(u_arm_r, l_arm_r, Vector3(0.28, 1.10, 0), -PI * 0.8, 0.0)
# Knees (Hinge, bends backwards only)
_create_hinge(thigh_l, calf_l, Vector3(-0.12, 0.40, 0), 0.0, PI * 0.8)
_create_hinge(thigh_r, calf_r, Vector3(0.12, 0.40, 0), 0.0, PI * 0.8)
func apply_initial_velocities(linear_vel: Vector3, impulse: Vector3) -> void:
for child in get_children():
@@ -93,3 +125,28 @@ func _create_joint(node_a: RigidBody3D, node_b: RigidBody3D, pos: Vector3) -> vo
joint.set_flag_z(Generic6DOFJoint3D.FLAG_ENABLE_ANGULAR_LIMIT, true)
joint.set_param_z(Generic6DOFJoint3D.PARAM_ANGULAR_LOWER_LIMIT, -PI * 0.2)
joint.set_param_z(Generic6DOFJoint3D.PARAM_ANGULAR_UPPER_LIMIT, PI * 0.2)
func _create_hinge(node_a: RigidBody3D, node_b: RigidBody3D, pos: Vector3, lower_lim: float, upper_lim: float) -> void:
var joint = Generic6DOFJoint3D.new()
add_child(joint)
joint.position = pos
joint.node_a = node_a.get_path()
joint.node_b = node_b.get_path()
joint.set_flag_x(Generic6DOFJoint3D.FLAG_ENABLE_LINEAR_LIMIT, true)
joint.set_flag_y(Generic6DOFJoint3D.FLAG_ENABLE_LINEAR_LIMIT, true)
joint.set_flag_z(Generic6DOFJoint3D.FLAG_ENABLE_LINEAR_LIMIT, true)
# Allow X rotation for the hinge (knee/elbow)
joint.set_flag_x(Generic6DOFJoint3D.FLAG_ENABLE_ANGULAR_LIMIT, true)
joint.set_param_x(Generic6DOFJoint3D.PARAM_ANGULAR_LOWER_LIMIT, lower_lim)
joint.set_param_x(Generic6DOFJoint3D.PARAM_ANGULAR_UPPER_LIMIT, upper_lim)
# Lock Y and Z
joint.set_flag_y(Generic6DOFJoint3D.FLAG_ENABLE_ANGULAR_LIMIT, true)
joint.set_param_y(Generic6DOFJoint3D.PARAM_ANGULAR_LOWER_LIMIT, 0.0)
joint.set_param_y(Generic6DOFJoint3D.PARAM_ANGULAR_UPPER_LIMIT, 0.0)
joint.set_flag_z(Generic6DOFJoint3D.FLAG_ENABLE_ANGULAR_LIMIT, true)
joint.set_param_z(Generic6DOFJoint3D.PARAM_ANGULAR_LOWER_LIMIT, 0.0)
joint.set_param_z(Generic6DOFJoint3D.PARAM_ANGULAR_UPPER_LIMIT, 0.0)