feat: Implement facing profiles and bend direction toggles in test harness

- Added functionality for skeleton IK bone switches, allowing users to dynamically change the bend direction of joints via a context menu.
- Introduced facing profiles (LEFT, RIGHT, FORWARD) that modify the bend direction of limbs based on the selected profile.
- Implemented body part z-ordering based on the facing profile, ensuring correct rendering order of limbs relative to the torso.
- Added a coordinates display panel that shows the position and rotation of the Skeleton2D and its bones, as well as IK target positions.
- Created a new script to generate a walk animation for the stickman rig, including keyframe tracks for various IK targets.
- Updated documentation to reflect the new features and their specifications.
This commit is contained in:
2026-08-23 22:21:23 -04:00
parent ab5c79ab6a
commit 273090993e
9 changed files with 1013 additions and 26 deletions
+126 -25
View File
@@ -28,6 +28,7 @@ joint_two_bone2d_node = NodePath("Torso/RightUpperArm/RightLowerArm")
[sub_resource type="SkeletonModification2DTwoBoneIK" id="SkeletonModification2DTwoBoneIK_f0s26"]
target_nodepath = NodePath("../IK_Targets/Left_Hand")
flip_bend_direction = true
joint_one_bone_idx = 2
joint_one_bone2d_node = NodePath("Torso/LeftUpperArm")
joint_two_bone_idx = 3
@@ -42,7 +43,6 @@ joint_two_bone2d_node = NodePath("Torso/RightUpperLeg/RightLowerLeg")
[sub_resource type="SkeletonModification2DTwoBoneIK" id="SkeletonModification2DTwoBoneIK_2leu7"]
target_nodepath = NodePath("../IK_Targets/Left_Leg")
flip_bend_direction = true
joint_one_bone_idx = 6
joint_one_bone2d_node = NodePath("Torso/LeftUpperLeg")
joint_two_bone_idx = 7
@@ -66,38 +66,127 @@ modifications/2 = SubResource("SkeletonModification2DTwoBoneIK_ylko5")
modifications/3 = SubResource("SkeletonModification2DTwoBoneIK_2leu7")
modifications/4 = SubResource("SkeletonModification2DLookAt_j4hao")
[sub_resource type="Animation" id="Animation_2leu7"]
length = 0.001
[sub_resource type="Animation" id="Animation_ylko5"]
length = 0.8
loop_mode = 1
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("IK_Targets/Torso:position")
tracks/0/interp = 2
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.2, 0.4, 0.6, 0.8),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1),
"update": 0,
"values": [Vector2(0, 10), Vector2(0, -15), Vector2(0, 10), Vector2(0, -15), Vector2(0, 10)]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("IK_Targets/Head:position")
tracks/1/interp = 2
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0, 0.2, 0.4, 0.6, 0.8),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1),
"update": 0,
"values": [Vector2(100, -614), Vector2(100, -639), Vector2(100, -614), Vector2(100, -639), Vector2(100, -614)]
}
tracks/2/type = "value"
tracks/2/imported = false
tracks/2/enabled = true
tracks/2/path = NodePath("IK_Targets/Right_Leg:position")
tracks/2/interp = 2
tracks/2/loop_wrap = true
tracks/2/keys = {
"times": PackedFloat32Array(0, 0.2, 0.4, 0.6, 0.8),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1),
"update": 0,
"values": [Vector2(110, 390), Vector2(0, 397), Vector2(-110, 380), Vector2(-20, 320), Vector2(110, 390)]
}
tracks/3/type = "value"
tracks/3/imported = false
tracks/3/enabled = true
tracks/3/path = NodePath("IK_Targets/Left_Leg:position")
tracks/3/interp = 2
tracks/3/loop_wrap = true
tracks/3/keys = {
"times": PackedFloat32Array(0, 0.2, 0.4, 0.6, 0.8),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1),
"update": 0,
"values": [Vector2(-110, 380), Vector2(-20, 320), Vector2(110, 390), Vector2(0, 397), Vector2(-110, 380)]
}
tracks/4/type = "value"
tracks/4/imported = false
tracks/4/enabled = true
tracks/4/path = NodePath("IK_Targets/Right_Hand:position")
tracks/4/interp = 2
tracks/4/loop_wrap = true
tracks/4/keys = {
"times": PackedFloat32Array(0, 0.2, 0.4, 0.6, 0.8),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1),
"update": 0,
"values": [Vector2(-90, 110), Vector2(0, 115), Vector2(90, 110), Vector2(0, 115), Vector2(-90, 110)]
}
tracks/5/type = "value"
tracks/5/imported = false
tracks/5/enabled = true
tracks/5/path = NodePath("IK_Targets/Left_Hand:position")
tracks/5/interp = 2
tracks/5/loop_wrap = true
tracks/5/keys = {
"times": PackedFloat32Array(0, 0.2, 0.4, 0.6, 0.8),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1),
"update": 0,
"values": [Vector2(90, 110), Vector2(0, 115), Vector2(-90, 110), Vector2(0, 115), Vector2(90, 110)]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_t75yq"]
_data = {
&"RESET": SubResource("Animation_2leu7"),
&"walk_right": SubResource("Animation_ylko5")
}
[sub_resource type="AnimationNodeStateMachine" id="AnimationNodeStateMachine_6rw38"]
[node name="Master" type="Node2D" unique_id=319103476]
[node name="Body" type="Node2D" parent="." unique_id=327811712]
[node name="Head" type="Node2D" parent="Body" unique_id=864822355]
position = Vector2(-0.038591623, -463.50787)
rotation = 0.000554086
scale = Vector2(0.9999999, 0.9999999)
position = Vector2(-0.03859164, -453.50787)
rotation = 0.00055408623
scale = Vector2(0.9999996, 0.9999996)
script = SubResource("GDScript_f0s26")
[node name="Body" type="Line2D" parent="Body" unique_id=1290443463]
position = Vector2(9.313226e-10, 10.000001)
rotation = -3.1415925
points = PackedVector2Array(0, 0, 0, 400)
width = 16.0
default_color = Color(0.445488, 0.445488, 0.445488, 1)
[node name="LeftUpperLeg" type="Line2D" parent="Body" unique_id=199373156]
position = Vector2(1.6097914e-05, 5.306938e-07)
position = Vector2(1.6098846e-05, 10.000002)
rotation = 0.4947604
points = PackedVector2Array(0, 0, 0, 200)
width = 16.0
default_color = Color(0.445488, 0.445488, 0.445488, 1)
[node name="RightUpperLeg" type="Line2D" parent="Body" unique_id=1396556308]
position = Vector2(-2.3235021e-05, 2.1205284e-05)
rotation = -0.4947779
position = Vector2(-2.3252098e-05, 10.000022)
rotation = -0.49392816
scale = Vector2(0.99999994, 0.99999994)
points = PackedVector2Array(0, 0, 0, 200)
width = 16.0
default_color = Color(0.445488, 0.445488, 0.445488, 1)
[node name="LeftLowerLeg" type="Line2D" parent="Body" unique_id=1590485904]
position = Vector2(-94.96416, 176.0165)
position = Vector2(-94.96416, 186.0165)
rotation = 0.0051915743
scale = Vector2(0.99999994, 0.99999994)
points = PackedVector2Array(0, 0, 0, 200)
@@ -105,36 +194,36 @@ width = 16.0
default_color = Color(0.445488, 0.445488, 0.445488, 1)
[node name="RightLowerLeg" type="Line2D" parent="Body" unique_id=29203948]
position = Vector2(94.96724, 176.01485)
rotation = -0.0051744953
scale = Vector2(0.99999994, 0.99999994)
position = Vector2(94.817635, 186.09546)
rotation = -0.0034907677
scale = Vector2(0.9999998, 0.9999998)
points = PackedVector2Array(0, 0, 0, 200)
width = 16.0
default_color = Color(0.445488, 0.445488, 0.445488, 1)
[node name="LeftUpperArm" type="Line2D" parent="Body" unique_id=744524157]
position = Vector2(0, -248)
position = Vector2(9.313226e-10, -238)
rotation = 1.6184965
points = PackedVector2Array(0, 0, 0, 175)
width = 16.0
default_color = Color(0.445488, 0.445488, 0.445488, 1)
[node name="RightUpperArm" type="Line2D" parent="Body" unique_id=1593646765]
position = Vector2(0, -248)
position = Vector2(9.313226e-10, -238)
rotation = -1.6184964
points = PackedVector2Array(0, 0, 0, 175)
width = 16.0
default_color = Color(0.445488, 0.445488, 0.445488, 1)
[node name="LeftLowerArm" type="Line2D" parent="Body" unique_id=2142117097]
position = Vector2(-167.80888, -256.0106)
position = Vector2(-167.80888, -246.01059)
rotation = 3.1406152
points = PackedVector2Array(0, 0, 0, 200)
width = 16.0
default_color = Color(0.445488, 0.445488, 0.445488, 1)
[node name="RightLowerArm" type="Line2D" parent="Body" unique_id=1200980480]
position = Vector2(167.80891, -256.01056)
position = Vector2(167.80891, -246.01057)
rotation = -3.1406155
points = PackedVector2Array(0, 0, 0, 200)
width = 16.0
@@ -145,13 +234,14 @@ rotation = -0.0006991282
modification_stack = SubResource("SkeletonModificationStack2D_j4hao")
[node name="Torso" type="Bone2D" parent="Skeleton2D" unique_id=744339938]
position = Vector2(-0.006991281, 9.999998)
rotation = 0.0006991282
rest = Transform2D(0.99999976, 0.00069912814, -0.00069912814, 0.99999976, 0, 0)
[node name="Head" type="Bone2D" parent="Skeleton2D/Torso" unique_id=1487357366]
position = Vector2(-0.12882307, -391.5079)
rotation = 0.000554086
scale = Vector2(0.9999999, 0.9999999)
rotation = 0.00055408623
scale = Vector2(0.9999996, 0.9999996)
rest = Transform2D(0.9998273, 0.0005539903, -0.0005539903, 0.9998273, -0.12882307, -391.5079)
auto_calculate_length_and_angle = false
length = 90.0
@@ -239,15 +329,17 @@ position = Vector2(1.5258789e-05, -7.6293945e-06)
remote_path = NodePath("../../../../Body/LeftUpperLeg")
[node name="RightUpperLeg" type="Bone2D" parent="Skeleton2D/Torso" unique_id=1125756606]
rotation = -0.4947779
rotation = -0.49392816
scale = Vector2(0.99999994, 0.99999994)
rest = Transform2D(0.8800552, -0.47482592, 0.47482592, 0.8800552, 0, 0)
auto_calculate_length_and_angle = false
length = 90.0
length = 200.0
bone_angle = 90.0
[node name="RightLowerLeg" type="Bone2D" parent="Skeleton2D/Torso/RightUpperLeg" unique_id=1819999778]
position = Vector2(0, 200)
rotation = 2.0603998
rotation = 2.0612338
scale = Vector2(0.9999999, 0.9999999)
rest = Transform2D(-0.47026652, 0.8825017, -0.8825017, -0.47026652, 0, 200)
auto_calculate_length_and_angle = false
length = 200.0
@@ -271,16 +363,16 @@ position = Vector2(0, 350)
[node name="IK_Targets" type="Node2D" parent="." unique_id=1089334064]
[node name="Right_Hand" type="Marker2D" parent="IK_Targets" unique_id=1785254130]
position = Vector2(168, -456)
position = Vector2(-90, 110)
[node name="Left_Hand" type="Marker2D" parent="IK_Targets" unique_id=1085197042]
position = Vector2(-168, -456)
position = Vector2(90, 110)
[node name="Right_Leg" type="Marker2D" parent="IK_Targets" unique_id=720679106]
position = Vector2(96, 376)
position = Vector2(110, 390)
[node name="Left_Leg" type="Marker2D" parent="IK_Targets" unique_id=63598929]
position = Vector2(-96, 376)
position = Vector2(-110, 380)
[node name="RayCast_LeftLeg" type="RayCast2D" parent="IK_Targets" unique_id=2000000001]
position = Vector2(-96, 200)
@@ -291,9 +383,18 @@ position = Vector2(96, 200)
target_position = Vector2(0, 200)
[node name="Head" type="Marker2D" parent="IK_Targets" unique_id=1347666160]
position = Vector2(0, -624)
position = Vector2(100, -614)
[node name="Torso" type="Marker2D" parent="IK_Targets" unique_id=809821395]
position = Vector2(0, 10)
[node name="RemoteTransform2D" type="RemoteTransform2D" parent="IK_Targets/Torso" unique_id=1659291279]
remote_path = NodePath("../../../Skeleton2D/Torso")
[node name="AnimationPlayer" type="AnimationPlayer" parent="." unique_id=679147993]
libraries/ = SubResource("AnimationLibrary_t75yq")
[node name="AnimationTree" type="AnimationTree" parent="." unique_id=1526237266]
active = false
tree_root = SubResource("AnimationNodeStateMachine_6rw38")
anim_player = NodePath("../AnimationPlayer")