bouncy bull

This commit is contained in:
2026-05-15 22:10:58 +03:00
parent fe5d143047
commit 6549240337
9 changed files with 697 additions and 382 deletions
+17 -335
View File
@@ -5,72 +5,32 @@
[ext_resource type="Script" uid="uid://b720l6m0ehrj3" path="res://camera_follow.gd" id="3_kne1u"]
[ext_resource type="PackedScene" uid="uid://qy6qrxdfgx2g" path="res://Assets/bull_v10.fbx" id="4_e80uo"]
[sub_resource type="SphereShape3D" id="SphereShape3D_cxr5p"]
[sub_resource type="SphereShape3D" id="SphereShape3D_kne1u"]
radius = 0.3
[sub_resource type="SphereShape3D" id="SphereShape3D_e80uo"]
radius = 0.75698936
[sub_resource type="SphereShape3D" id="SphereShape3D_chest"]
radius = 0.35
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_cxr5p"]
radius = 0.00026695352
height = 0.002669535
[sub_resource type="SphereShape3D" id="SphereShape3D_belly"]
radius = 0.40
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_tdg3f"]
radius = 0.0002866844
height = 0.002866844
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_dtqjt"]
radius = 0.00028599252
height = 0.0028599252
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_5ncpa"]
radius = 0.0003274492
height = 0.003274492
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_wi0c6"]
radius = 0.00028593137
height = 0.0028593137
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_hp6x6"]
radius = 0.03
height = 0.15
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_3tkgg"]
radius = 0.02
height = 0.1
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_l6ohk"]
radius = 0.0003321008
height = 0.003321008
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_oudfo"]
radius = 0.00026695352
height = 0.002669535
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_d6rpx"]
radius = 0.0002866844
height = 0.002866844
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_r3kul"]
radius = 0.00028599252
height = 0.0028599252
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_jatw7"]
radius = 0.0003274492
height = 0.003274492
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_rv870"]
radius = 0.00028593137
height = 0.0028593137
[sub_resource type="SphereShape3D" id="SphereShape3D_hips"]
radius = 0.30
[node name="Player" type="CharacterBody3D" unique_id=165510044]
script = ExtResource("1_xhfnw")
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=314502219]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.9729152, 0)
shape = SubResource("SphereShape3D_cxr5p")
[node name="CollisionChest" type="CollisionShape3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -0.2)
shape = SubResource("SphereShape3D_chest")
[node name="CollisionBelly" type="CollisionShape3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0.1)
shape = SubResource("SphereShape3D_belly")
[node name="CollisionHips" type="CollisionShape3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0.4)
shape = SubResource("SphereShape3D_hips")
[node name="SpringArmPivot" type="Node3D" parent="." unique_id=1671817680]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.22, 0)
@@ -92,282 +52,4 @@ lerp_power = 6.0
[node name="bull_v10" parent="." unique_id=1386495166 instance=ExtResource("4_e80uo")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.655, 0.23)
[node name="PhysicalBoneSimulator3D" type="PhysicalBoneSimulator3D" parent="bull_v10/Armature/Skeleton3D" parent_id_path=PackedInt32Array(1386495166, 1366639819) index="2" unique_id=1777458180]
[node name="Physical Bone COG" type="PhysicalBone3D" parent="bull_v10/Armature/Skeleton3D/PhysicalBoneSimulator3D" unique_id=1541330657]
transform = Transform3D(-0.007564789, 0.00409347, -0.0051007345, -0.0065401834, -0.004734768, 0.0058998317, -7.7972817e-10, 0.007799071, 0.006258956, 0.0017633128, -0.0020413985, 0.0078065563)
joint_offset = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.000120311975, -0.0005881116, 0.34540433)
body_offset = Transform3D(-0.0075647887, 0.00409347, -0.005100734, -7.752023e-14, 0.007799073, 0.0062589566, 0.006540184, 0.004734768, -0.005899834, 0.0017633127, -0.002157284, 0.0020413995)
bone_name = "COG"
[node name="CollisionShape3D" type="CollisionShape3D" parent="bull_v10/Armature/Skeleton3D/PhysicalBoneSimulator3D/Physical Bone COG" unique_id=1877016782]
transform = Transform3D(0.99999994, 9.834766e-07, 7.1525574e-07, -8.046627e-07, 0, 1.0000002, 1.013279e-06, -1, -1.1920929e-07, 0.0016898364, 0.10711694, 0.35998654)
shape = SubResource("SphereShape3D_e80uo")
[node name="Physical Bone front_leg_1_L" type="PhysicalBone3D" parent="bull_v10/Armature/Skeleton3D/PhysicalBoneSimulator3D" unique_id=1675398557]
transform = Transform3D(-0.009573112, -0.0002095241, -0.0028829917, -0.00019426788, -0.009904512, 0.0013648955, -0.0028840597, 0.001362637, 0.009477629, 0.00391145, -0.004264981, 0.004374394)
joint_type = 1
joint_offset = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.00015264872, -7.158299e-05, 0.13297947)
body_offset = Transform3D(0.009999981, 1.0186341e-10, -3.5106495e-10, 3.965397e-10, 8.1490725e-10, -0.010000003, -2.1827873e-10, 0.009999998, 8.1490725e-10, -1.5264377e-06, 0.001329795, 7.157214e-07)
bone_name = "front_leg_1_L"
joint_constraints/bias = 0.3
joint_constraints/damping = 1.0
joint_constraints/impulse_clamp = 0.0
[node name="CollisionShape3D" type="CollisionShape3D" parent="bull_v10/Armature/Skeleton3D/PhysicalBoneSimulator3D/Physical Bone front_leg_1_L" unique_id=1745285030]
transform = Transform3D(1, 0, 0, 0, 0, 1, 0, -1, 0, 0, 0, 0)
shape = SubResource("CapsuleShape3D_cxr5p")
[node name="Physical Bone front_leg_2_L" type="PhysicalBone3D" parent="bull_v10/Armature/Skeleton3D/PhysicalBoneSimulator3D" unique_id=71283091]
transform = Transform3D(-0.009977277, 3.905031e-05, -0.00067263393, -3.9138667e-05, -0.009999925, -4.19114e-09, -0.00067262875, 2.6284176e-06, 0.009977355, 0.0043927236, -0.0044475948, 0.0016755704)
joint_type = 1
joint_offset = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.6548756e-05, -4.3348646e-05, 0.14317834)
body_offset = Transform3D(0.009999981, -2.4007022e-08, 8.510059e-09, 1.0807027e-10, 8.691763e-10, -0.009999999, 2.3192115e-08, 0.009999998, -3.1224912e-10, -1.6670674e-07, 0.0014317832, 4.3353066e-07)
bone_name = "front_leg_2_L"
joint_constraints/bias = 0.3
joint_constraints/damping = 1.0
joint_constraints/impulse_clamp = 0.0
[node name="CollisionShape3D" type="CollisionShape3D" parent="bull_v10/Armature/Skeleton3D/PhysicalBoneSimulator3D/Physical Bone front_leg_2_L" unique_id=102232034]
transform = Transform3D(1, 0, 0, 0, 0, 1, 0, -1, 0, 0, 0, 0)
shape = SubResource("CapsuleShape3D_tdg3f")
[node name="Physical Bone rear_leg_1_L" type="PhysicalBone3D" parent="bull_v10/Armature/Skeleton3D/PhysicalBoneSimulator3D" unique_id=641436769]
transform = Transform3D(-0.008503459, -0.005138319, -0.0011352889, 0.0042592627, -0.007987581, 0.0042493828, -0.0030902892, 0.0031298958, 0.008980752, 0.0047028894, 0.0036388426, 0.0065502548)
joint_type = 1
joint_offset = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.000323987, 0.0006103788, 0.14196822)
body_offset = Transform3D(0.010000004, -4.656613e-10, -2.3283064e-10, -5.820766e-10, -1.1641532e-09, -0.010000005, 4.656613e-10, 0.009999999, -9.313226e-10, -3.2398384e-06, 0.0014196831, -6.1036553e-06)
bone_name = "rear_leg_1_L"
joint_constraints/bias = 0.3
joint_constraints/damping = 1.0
joint_constraints/impulse_clamp = 0.0
[node name="CollisionShape3D" type="CollisionShape3D" parent="bull_v10/Armature/Skeleton3D/PhysicalBoneSimulator3D/Physical Bone rear_leg_1_L" unique_id=892834462]
transform = Transform3D(1, 0, 0, 0, 0, 1, 0, -1, 0, 0, 0, 0)
shape = SubResource("CapsuleShape3D_dtqjt")
[node name="Physical Bone rear_leg_2_L" type="PhysicalBone3D" parent="bull_v10/Armature/Skeleton3D/PhysicalBoneSimulator3D" unique_id=1185899204]
transform = Transform3D(-0.00915834, 0.0029074468, 0.002769761, -0.0039623342, -0.0054236636, -0.0074083614, -0.00065171614, -0.007882301, 0.006119203, 0.004407039, 0.0042441166, 0.0042632855)
joint_type = 1
joint_offset = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.00036044084, 0.0010113111, 0.16385125)
body_offset = Transform3D(0.0100000035, 1.1571683e-07, -5.5879354e-08, -5.3318217e-08, 2.561137e-08, -0.010000003, -1.1525117e-07, 0.010000005, 2.6542693e-08, -3.5953708e-06, 0.0016385128, -1.0117423e-05)
bone_name = "rear_leg_2_L"
joint_constraints/bias = 0.3
joint_constraints/damping = 1.0
joint_constraints/impulse_clamp = 0.0
[node name="CollisionShape3D" type="CollisionShape3D" parent="bull_v10/Armature/Skeleton3D/PhysicalBoneSimulator3D/Physical Bone rear_leg_2_L" unique_id=1284740745]
transform = Transform3D(1, 0, 0, 0, 0, 1, 0, -1, 0, 0, 0, 0)
shape = SubResource("CapsuleShape3D_5ncpa")
[node name="Physical Bone rear_leg_3_L" type="PhysicalBone3D" parent="bull_v10/Armature/Skeleton3D/PhysicalBoneSimulator3D" unique_id=35949848]
transform = Transform3D(-0.009840026, -0.00014057018, -0.0017759949, -0.00018075595, -0.009838606, 0.0017802179, -0.001772356, 0.0017838409, 0.009678672, 0.0042075394, 0.00519469, 0.0018702713)
joint_type = 1
joint_offset = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 6.2520294e-06, 2.4616318e-07, 0.14296634)
body_offset = Transform3D(0.010000004, -1.1350494e-09, -8.5128704e-10, -1.4551915e-10, 2.3283064e-10, -0.010000004, 1.4551915e-11, 0.010000002, -2.561137e-09, -6.239861e-08, 0.001429664, -2.0954758e-09)
bone_name = "rear_leg_3_L"
joint_constraints/bias = 0.3
joint_constraints/damping = 1.0
joint_constraints/impulse_clamp = 0.0
[node name="CollisionShape3D" type="CollisionShape3D" parent="bull_v10/Armature/Skeleton3D/PhysicalBoneSimulator3D/Physical Bone rear_leg_3_L" unique_id=1811789092]
transform = Transform3D(1, 0, 0, 0, 0, 1, 0, -1, 0, 0, 0, 0)
shape = SubResource("CapsuleShape3D_wi0c6")
[node name="Physical Bone tail_1" type="PhysicalBone3D" parent="bull_v10/Armature/Skeleton3D/PhysicalBoneSimulator3D" unique_id=409059252]
transform = Transform3D(0.010000001, 0, -2.7184845e-34, -2.7184847e-34, -5.551115e-16, -0.010000002, 0, 0.010000002, -5.551115e-16, 2.2598148e-19, 0.009381076, 0.0109815765)
joint_type = 1
joint_offset = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.8774088e-23, -6.9849165e-07, 0.08855743)
body_offset = Transform3D(0.010000001, 0, -2.7184843e-34, -2.7184854e-34, -6.661338e-16, -0.0100000035, 0, 0.0100000035, -6.661338e-16, 3.877409e-25, 0.0008855746, 6.9849193e-09)
bone_name = "tail_1"
joint_constraints/bias = 0.3
joint_constraints/damping = 1.0
joint_constraints/impulse_clamp = 0.0
[node name="CollisionShape3D" type="CollisionShape3D" parent="bull_v10/Armature/Skeleton3D/PhysicalBoneSimulator3D/Physical Bone tail_1" unique_id=1083045052]
transform = Transform3D(1, 0, 0, 0, 0, 1, 0, -1, 0, 0, 0, 0)
shape = SubResource("CapsuleShape3D_hp6x6")
[node name="Physical Bone tail_2" type="PhysicalBone3D" parent="bull_v10/Armature/Skeleton3D/PhysicalBoneSimulator3D" unique_id=1184926284]
transform = Transform3D(0.010000001, 0, -2.662741e-34, -2.6627415e-34, -5.551115e-16, -0.010000002, 0, 0.010000002, -5.551115e-16, 2.2598148e-19, 0.011170757, 0.0109815765)
joint_type = 1
joint_offset = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.8774088e-23, -6.9849165e-07, 0.09041118)
body_offset = Transform3D(0.010000001, 0, -2.6627408e-34, -2.662742e-34, -6.661338e-16, -0.0100000035, 0, 0.0100000035, -6.661338e-16, 3.877409e-25, 0.0009041121, 6.9849193e-09)
bone_name = "tail_2"
joint_constraints/bias = 0.3
joint_constraints/damping = 1.0
joint_constraints/impulse_clamp = 0.0
[node name="CollisionShape3D" type="CollisionShape3D" parent="bull_v10/Armature/Skeleton3D/PhysicalBoneSimulator3D/Physical Bone tail_2" unique_id=790298871]
transform = Transform3D(1, 0, 0, 0, 0, 1, 0, -1, 0, 0, 0, 0)
shape = SubResource("CapsuleShape3D_hp6x6")
[node name="Physical Bone tail_3" type="PhysicalBone3D" parent="bull_v10/Armature/Skeleton3D/PhysicalBoneSimulator3D" unique_id=1650092450]
transform = Transform3D(0.010000001, 0, 0, 0, -5.551115e-16, -0.010000002, 0, 0.010000002, -5.551115e-16, 2.2598148e-19, 0.013070463, 0.010998246)
joint_type = 1
joint_offset = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.877409e-23, -0.0016675792, 0.09955974)
body_offset = Transform3D(0.010000001, 0, 0, 0, -6.661338e-16, -0.010000004, 0, 0.0100000035, -6.661338e-16, 3.877409e-25, 0.0009955978, 1.6675796e-05)
bone_name = "tail_3"
joint_constraints/bias = 0.3
joint_constraints/damping = 1.0
joint_constraints/impulse_clamp = 0.0
[node name="CollisionShape3D" type="CollisionShape3D" parent="bull_v10/Armature/Skeleton3D/PhysicalBoneSimulator3D/Physical Bone tail_3" unique_id=2053684626]
transform = Transform3D(1, 0, 0, 0, 0, 1, 0, -1, 0, 0, 0, 0)
shape = SubResource("CapsuleShape3D_hp6x6")
[node name="Physical Bone tail_4" type="PhysicalBone3D" parent="bull_v10/Armature/Skeleton3D/PhysicalBoneSimulator3D" unique_id=1695718220]
transform = Transform3D(0.010000001, 0, 0, 0, -5.551115e-16, -0.010000002, 0, 0.010000002, -5.551115e-16, 2.2598148e-19, 0.015077802, 0.010981572)
joint_type = 1
joint_offset = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.877409e-23, -9.313222e-08, 0.10117438)
body_offset = Transform3D(0.010000001, 0, 0, 0, -6.661338e-16, -0.010000004, 0, 0.0100000035, -6.661338e-16, 3.877409e-25, 0.0010117441, 9.313226e-10)
bone_name = "tail_4"
joint_constraints/bias = 0.3
joint_constraints/damping = 1.0
joint_constraints/impulse_clamp = 0.0
[node name="CollisionShape3D" type="CollisionShape3D" parent="bull_v10/Armature/Skeleton3D/PhysicalBoneSimulator3D/Physical Bone tail_4" unique_id=1860507411]
transform = Transform3D(1, 0, 0, 0, 0, 1, 0, -1, 0, 0, 0, 0)
shape = SubResource("CapsuleShape3D_hp6x6")
[node name="Physical Bone tail_5" type="PhysicalBone3D" parent="bull_v10/Armature/Skeleton3D/PhysicalBoneSimulator3D" unique_id=839180583]
transform = Transform3D(0.010000001, 0, 0, 0, -5.551115e-16, -0.010000002, 0, 0.010000002, -5.551115e-16, 2.2598148e-19, 0.016923692, 0.0109815765)
joint_type = 1
joint_offset = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.877409e-23, -6.984917e-07, 0.083414994)
body_offset = Transform3D(0.010000001, 0, 0, 0, -6.661338e-16, -0.010000004, 0, 0.0100000035, -6.661338e-16, 3.877409e-25, 0.00083415024, 6.9849193e-09)
bone_name = "tail_5"
joint_constraints/bias = 0.3
joint_constraints/damping = 1.0
joint_constraints/impulse_clamp = 0.0
[node name="CollisionShape3D" type="CollisionShape3D" parent="bull_v10/Armature/Skeleton3D/PhysicalBoneSimulator3D/Physical Bone tail_5" unique_id=389279420]
transform = Transform3D(1, 0, 0, 0, 0, 1, 0, -1, 0, 0, 0, 0)
shape = SubResource("CapsuleShape3D_hp6x6")
[node name="Physical Bone tail_6" type="PhysicalBone3D" parent="bull_v10/Armature/Skeleton3D/PhysicalBoneSimulator3D" unique_id=1413495806]
transform = Transform3D(0.010000001, 0, 0, 0, -5.551115e-16, -0.010000002, 0, 0.010000002, -5.551115e-16, 2.2598148e-19, 0.018541092, 0.0109815765)
joint_type = 1
joint_offset = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.877409e-23, -6.984917e-07, 0.07832466)
body_offset = Transform3D(0.010000001, 0, 0, 0, -6.661338e-16, -0.010000004, 0, 0.0100000035, -6.661338e-16, 3.877409e-25, 0.00078324694, 6.9849193e-09)
bone_name = "tail_6"
joint_constraints/bias = 0.3
joint_constraints/damping = 1.0
joint_constraints/impulse_clamp = 0.0
[node name="CollisionShape3D" type="CollisionShape3D" parent="bull_v10/Armature/Skeleton3D/PhysicalBoneSimulator3D/Physical Bone tail_6" unique_id=1892370858]
transform = Transform3D(1, 0, 0, 0, 0, 1, 0, -1, 0, 0, 0, 0)
shape = SubResource("CapsuleShape3D_hp6x6")
[node name="Physical Bone tail_tip_1" type="PhysicalBone3D" parent="bull_v10/Armature/Skeleton3D/PhysicalBoneSimulator3D" unique_id=1460110003]
transform = Transform3D(0.010000001, 0, 0, 0, -5.551115e-16, -0.010000002, 0, 0.010000002, -5.551115e-16, 2.2598148e-19, 0.019823557, 0.0109815765)
joint_type = 1
joint_offset = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.877409e-23, -6.984917e-07, 0.05696246)
body_offset = Transform3D(0.010000001, 0, 0, 0, -6.661338e-16, -0.010000004, 0, 0.0100000035, -6.661338e-16, 3.877409e-25, 0.0005696248, 6.9849193e-09)
bone_name = "tail_tip_1"
joint_constraints/bias = 0.3
joint_constraints/damping = 1.0
joint_constraints/impulse_clamp = 0.0
[node name="CollisionShape3D" type="CollisionShape3D" parent="bull_v10/Armature/Skeleton3D/PhysicalBoneSimulator3D/Physical Bone tail_tip_1" unique_id=2108943626]
transform = Transform3D(1, 0, 0, 0, 0, 1, 0, -1, 0, 0, 0, 0)
shape = SubResource("CapsuleShape3D_3tkgg")
[node name="Physical Bone tail_tip_2" type="PhysicalBone3D" parent="bull_v10/Armature/Skeleton3D/PhysicalBoneSimulator3D" unique_id=2147217275]
transform = Transform3D(0.010000001, 0, 0, 0, -5.551115e-16, -0.010000002, 0, 0.010000002, -5.551115e-16, 2.2598148e-19, 0.020996556, 0.010997413)
joint_type = 1
joint_offset = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.8774088e-23, -0.001584179, 0.064297915)
body_offset = Transform3D(0.010000001, 0, 0, 0, -6.661338e-16, -0.010000006, 0, 0.010000004, -6.661338e-16, 3.877409e-25, 0.0006429795, 1.5841797e-05)
bone_name = "tail_tip_2"
joint_constraints/bias = 0.3
joint_constraints/damping = 1.0
joint_constraints/impulse_clamp = 0.0
[node name="CollisionShape3D" type="CollisionShape3D" parent="bull_v10/Armature/Skeleton3D/PhysicalBoneSimulator3D/Physical Bone tail_tip_2" unique_id=1720964713]
transform = Transform3D(1, 0, 0, 0, 0, 1, 0, -1, 0, 0, 0, 0)
shape = SubResource("CapsuleShape3D_3tkgg")
[node name="Physical Bone head" type="PhysicalBone3D" parent="bull_v10/Armature/Skeleton3D/PhysicalBoneSimulator3D" unique_id=599730911]
transform = Transform3D(-0.010000001, -1.5099572e-09, 3.9762948e-16, 3.7627612e-17, 2.3841862e-09, 0.010000002, -1.5099573e-09, 0.010000002, -2.3841862e-09, -3.7788734e-13, -0.009213124, 0.010918824)
joint_type = 1
joint_offset = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.2797929e-10, -0.0017600593, 0.16605066)
body_offset = Transform3D(0.010000001, -8.881785e-16, -4.2358053e-17, 2.6490955e-19, 2.220446e-16, -0.0100000035, 3.3306685e-16, 0.0100000035, 2.220446e-16, -2.2797875e-12, 0.0016605072, 1.76006e-05)
bone_name = "head"
joint_constraints/bias = 0.3
joint_constraints/damping = 1.0
joint_constraints/impulse_clamp = 0.0
[node name="CollisionShape3D" type="CollisionShape3D" parent="bull_v10/Armature/Skeleton3D/PhysicalBoneSimulator3D/Physical Bone head" unique_id=505205665]
transform = Transform3D(1, 0, 0, 0, 0, 1, 0, -1, 0, 0, 0, 0)
shape = SubResource("CapsuleShape3D_l6ohk")
[node name="Physical Bone front_leg_1_R" type="PhysicalBone3D" parent="bull_v10/Armature/Skeleton3D/PhysicalBoneSimulator3D" unique_id=866459371]
transform = Transform3D(-0.009573112, 0.0002095241, 0.0028829917, 0.00019426788, -0.009904512, 0.0013648955, 0.0028840597, 0.001362637, 0.009477629, -0.00391145, -0.004264981, 0.004374394)
joint_type = 1
joint_offset = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.00015264872, -7.158299e-05, 0.13297947)
body_offset = Transform3D(0.009999981, -1.0186341e-10, 3.5106495e-10, -3.965397e-10, 8.1490725e-10, -0.010000003, 2.1827873e-10, 0.009999998, 8.1490725e-10, 1.5264377e-06, 0.001329795, 7.157214e-07)
bone_name = "front_leg_1_R"
joint_constraints/bias = 0.3
joint_constraints/damping = 1.0
joint_constraints/impulse_clamp = 0.0
[node name="CollisionShape3D" type="CollisionShape3D" parent="bull_v10/Armature/Skeleton3D/PhysicalBoneSimulator3D/Physical Bone front_leg_1_R" unique_id=937418203]
transform = Transform3D(1, 0, 0, 0, 0, 1, 0, -1, 0, 0, 0, 0)
shape = SubResource("CapsuleShape3D_oudfo")
[node name="Physical Bone front_leg_2_R" type="PhysicalBone3D" parent="bull_v10/Armature/Skeleton3D/PhysicalBoneSimulator3D" unique_id=1283418469]
transform = Transform3D(-0.009977277, -3.905031e-05, 0.00067263393, 3.9138667e-05, -0.009999925, -4.19114e-09, 0.00067262875, 2.6284176e-06, 0.009977355, -0.0043927236, -0.0044475948, 0.0016755704)
joint_type = 1
joint_offset = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.6548756e-05, -4.3348646e-05, 0.14317834)
body_offset = Transform3D(0.009999981, 2.4007022e-08, -8.510059e-09, -1.0807027e-10, 8.691763e-10, -0.009999999, -2.3192115e-08, 0.009999998, -3.1224912e-10, 1.6670674e-07, 0.0014317832, 4.3353066e-07)
bone_name = "front_leg_2_R"
joint_constraints/bias = 0.3
joint_constraints/damping = 1.0
joint_constraints/impulse_clamp = 0.0
[node name="CollisionShape3D" type="CollisionShape3D" parent="bull_v10/Armature/Skeleton3D/PhysicalBoneSimulator3D/Physical Bone front_leg_2_R" unique_id=258563666]
transform = Transform3D(1, 0, 0, 0, 0, 1, 0, -1, 0, 0, 0, 0)
shape = SubResource("CapsuleShape3D_d6rpx")
[node name="Physical Bone rear_leg_1_R" type="PhysicalBone3D" parent="bull_v10/Armature/Skeleton3D/PhysicalBoneSimulator3D" unique_id=216944416]
transform = Transform3D(-0.008503459, 0.005138319, 0.0011352889, -0.0042592627, -0.007987581, 0.0042493828, 0.0030902892, 0.0031298958, 0.008980752, -0.0047028894, 0.0036388426, 0.0065502548)
joint_type = 1
joint_offset = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.000323987, 0.0006103788, 0.14196822)
body_offset = Transform3D(0.010000004, 4.656613e-10, 2.3283064e-10, 5.820766e-10, -1.1641532e-09, -0.010000005, -4.656613e-10, 0.009999999, -9.313226e-10, 3.2398384e-06, 0.0014196831, -6.1036553e-06)
bone_name = "rear_leg_1_R"
joint_constraints/bias = 0.3
joint_constraints/damping = 1.0
joint_constraints/impulse_clamp = 0.0
[node name="CollisionShape3D" type="CollisionShape3D" parent="bull_v10/Armature/Skeleton3D/PhysicalBoneSimulator3D/Physical Bone rear_leg_1_R" unique_id=314882241]
transform = Transform3D(1, 0, 0, 0, 0, 1, 0, -1, 0, 0, 0, 0)
shape = SubResource("CapsuleShape3D_r3kul")
[node name="Physical Bone rear_leg_2_R" type="PhysicalBone3D" parent="bull_v10/Armature/Skeleton3D/PhysicalBoneSimulator3D" unique_id=1097552018]
transform = Transform3D(-0.00915834, -0.0029074468, -0.002769761, 0.0039623342, -0.0054236636, -0.0074083614, 0.00065171614, -0.007882301, 0.006119203, -0.004407039, 0.0042441166, 0.0042632855)
joint_type = 1
joint_offset = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.00036044084, 0.0010113111, 0.16385125)
body_offset = Transform3D(0.0100000035, -1.1571683e-07, 5.5879354e-08, 5.3318217e-08, 2.561137e-08, -0.010000003, 1.1525117e-07, 0.010000005, 2.6542693e-08, 3.5953708e-06, 0.0016385128, -1.0117423e-05)
bone_name = "rear_leg_2_R"
joint_constraints/bias = 0.3
joint_constraints/damping = 1.0
joint_constraints/impulse_clamp = 0.0
[node name="CollisionShape3D" type="CollisionShape3D" parent="bull_v10/Armature/Skeleton3D/PhysicalBoneSimulator3D/Physical Bone rear_leg_2_R" unique_id=473621577]
transform = Transform3D(1, 0, 0, 0, 0, 1, 0, -1, 0, 0, 0, 0)
shape = SubResource("CapsuleShape3D_jatw7")
[node name="Physical Bone rear_leg_3_R" type="PhysicalBone3D" parent="bull_v10/Armature/Skeleton3D/PhysicalBoneSimulator3D" unique_id=2066167688]
transform = Transform3D(-0.009840026, 0.00014057018, 0.0017759949, 0.00018075595, -0.009838606, 0.0017802179, 0.001772356, 0.0017838409, 0.009678672, -0.0042075394, 0.00519469, 0.0018702713)
joint_type = 1
joint_offset = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -6.2520294e-06, 2.4616318e-07, 0.14296634)
body_offset = Transform3D(0.010000004, 1.1350494e-09, 8.5128704e-10, 1.4551915e-10, 2.3283064e-10, -0.010000004, -1.4551915e-11, 0.010000002, -2.561137e-09, 6.239861e-08, 0.001429664, -2.0954758e-09)
bone_name = "rear_leg_3_R"
joint_constraints/bias = 0.3
joint_constraints/damping = 1.0
joint_constraints/impulse_clamp = 0.0
[node name="CollisionShape3D" type="CollisionShape3D" parent="bull_v10/Armature/Skeleton3D/PhysicalBoneSimulator3D/Physical Bone rear_leg_3_R" unique_id=2020682212]
transform = Transform3D(1, 0, 0, 0, 0, 1, 0, -1, 0, 0, 0, 0)
shape = SubResource("CapsuleShape3D_rv870")
[editable path="bull_v10"]
+437
View File
@@ -0,0 +1,437 @@
extends Node
## Procedural leg stepping, body wobble, and tail simulation.
## Created at runtime by player.gd; call setup() before the first _process tick.
# ── Leg State Machine ─────────────────────────────────────────────────────────
enum LegState { GALLOP, LAUNCH, RAGDOLL, TUCK, LAND }
# Leg IK config — rest offsets are in bull_v10 local space
const LEG_CFG: Dictionary = {
"FL": { "root": &"front_leg_1_L", "tip": &"front_leg_2_L_end",
"rest": Vector3(-0.30, -0.25, -0.50) },
"FR": { "root": &"front_leg_1_R", "tip": &"front_leg_2_R_end",
"rest": Vector3( 0.30, -0.25, -0.50) },
"BL": { "root": &"rear_leg_1_L", "tip": &"rear_leg_3_L_end",
"rest": Vector3(-0.30, -0.25, 0.60) },
"BR": { "root": &"rear_leg_1_R", "tip": &"rear_leg_3_R_end",
"rest": Vector3( 0.30, -0.25, 0.60) },
}
# Gallop gait phase offset per leg (0.01.0 cycle).
const LEG_PHASES: Dictionary = { "BL": 0.0, "FL": 0.2, "BR": 0.5, "FR": 0.7 }
# Per-leg ragdoll phase offsets for variety
const RAGDOLL_PHASE: Dictionary = { "FL": 0.0, "FR": 1.3, "BL": 2.6, "BR": 3.9 }
var _player: CharacterBody3D
var _bull: Node3D
var _skeleton: Skeleton3D
# Per-leg state
var _ik: Dictionary = {} # id -> SkeletonIK3D
var _foot: Dictionary = {} # id -> current foot (world)
var _step_from: Dictionary = {} # id -> Vector3
var _step_to: Dictionary = {} # id -> Vector3
var _step_t: Dictionary = {} # id -> float (1.0 = planted)
# Body wobble
var _prev_vel: Vector3 = Vector3.ZERO
var _tilt_x: float = 0.0
var _tilt_z: float = 0.0
var _aerial_pitch: float = 0.0
# Leg state machine
var _leg_state: LegState = LegState.GALLOP
var _state_time: float = 0.0 # time spent in current state
var _was_on_floor: bool = true
var _launch_feet: Dictionary = {} # frozen foot positions at launch
# Gait
var _gait_phase: float = 0.0
# Tail — Verlet chain simulation
const TAIL_BONES: Array = [
&"tail_1", &"tail_2", &"tail_3", &"tail_4",
&"tail_5", &"tail_6",
&"tail_tip_1", &"tail_tip_2", &"tail_tip_2_end",
]
var _tail_idx: Array[int] = []
var _tail_world: Array[Vector3] = []
var _tail_prev: Array[Vector3] = []
var _tail_lengths: Array[float] = []
var _tail_rest_dir: Array[Vector3] = []
func setup(player: CharacterBody3D, bull: Node3D) -> void:
_player = player
_bull = bull
_skeleton = _find_skeleton(bull)
if not _skeleton:
push_error("BullLegs: Skeleton3D not found under bull_v10")
return
# Disable any physical bone simulator — we don't need it
var phys_sim := _find_physical_bones(_skeleton)
if phys_sim:
phys_sim.active = false
_setup_tail()
for id: String in LEG_CFG:
var cfg: Dictionary = LEG_CFG[id]
var ik := SkeletonIK3D.new()
ik.root_bone = cfg["root"]
ik.tip_bone = cfg["tip"]
ik.interpolation = 1.0
ik.min_distance = 0.01
_skeleton.add_child(ik)
ik.start()
_ik[id] = ik
var world_rest: Vector3 = _bull.to_global(cfg["rest"])
_foot[id] = world_rest
_step_from[id] = world_rest
_step_to[id] = world_rest
_step_t[id] = 1.0
func _setup_tail() -> void:
for bone_name: StringName in TAIL_BONES:
var idx: int = _skeleton.find_bone(bone_name)
if idx == -1:
push_warning("BullLegs: tail bone '%s' not found" % bone_name)
continue
_tail_idx.append(idx)
if _tail_idx.size() < 2:
push_error("BullLegs: not enough tail bones found")
return
for i in range(_tail_idx.size() - 1):
var a: Transform3D = _skeleton.get_bone_global_rest(_tail_idx[i])
var b: Transform3D = _skeleton.get_bone_global_rest(_tail_idx[i + 1])
_tail_lengths.append((b.origin - a.origin).length())
_tail_rest_dir.append((b.origin - a.origin).normalized())
_tail_rest_dir.append(_tail_rest_dir[-1])
for idx: int in _tail_idx:
var world: Vector3 = _skeleton.to_global(_skeleton.get_bone_global_rest(idx).origin)
_tail_world.append(world)
_tail_prev.append(world)
func _process(delta: float) -> void:
if not _skeleton:
return
_wobble_body(delta)
_update_leg_state(delta)
_step_feet(delta)
_apply_ik()
_update_tail(delta)
_apply_tail()
# ── Body Wobble ───────────────────────────────────────────────────────────────
func _wobble_body(delta: float) -> void:
var vel: Vector3 = _player.velocity
var accel: Vector3 = (vel - _prev_vel) / maxf(delta, 0.001)
_prev_vel = vel
var la: Vector3 = _bull.global_transform.basis.inverse() * accel
var tx := clampf(-la.z * DP.f("body_tilt_fwd"), -0.35, 0.35)
var tz := clampf(-la.x * DP.f("body_tilt_side"), -0.25, 0.25)
_tilt_x = lerpf(_tilt_x, tx, delta * 10.0)
_tilt_z = lerpf(_tilt_z, tz, delta * 10.0)
var flat_spd: float = Vector2(vel.x, vel.z).length()
var target_pitch: float = 0.0
if not _player.is_on_floor() and flat_spd > 0.5:
target_pitch = clampf(-atan2(vel.y, flat_spd) * DP.f("aerial_pitch_scale"), -1.4, 1.4)
_aerial_pitch = lerpf(_aerial_pitch, target_pitch, delta * DP.f("aerial_pitch_speed"))
_bull.rotation.x = _tilt_x + _aerial_pitch
_bull.rotation.z = _tilt_z
# ── Leg State Machine ─────────────────────────────────────────────────────────
func _update_leg_state(delta: float) -> void:
var on_floor: bool = _player.is_on_floor()
_state_time += delta
match _leg_state:
LegState.GALLOP:
if not on_floor and _was_on_floor:
# Just left the ground — enter LAUNCH
_leg_state = LegState.LAUNCH
_state_time = 0.0
# Freeze current foot positions
for id: String in _foot:
_launch_feet[id] = _foot[id]
LegState.LAUNCH:
if on_floor:
_leg_state = LegState.GALLOP
_state_time = 0.0
elif _state_time > DP.f("launch_hold_time"):
_leg_state = LegState.RAGDOLL
_state_time = 0.0
LegState.RAGDOLL:
if on_floor:
_leg_state = LegState.GALLOP
_state_time = 0.0
elif _player.velocity.y < DP.f("tuck_apex_threshold"):
# Approaching or past apex — start tucking
_leg_state = LegState.TUCK
_state_time = 0.0
LegState.TUCK:
if on_floor:
_leg_state = LegState.GALLOP
_state_time = 0.0
else:
# Check for ground proximity — transition to LAND
var space := _player.get_world_3d().direct_space_state
var start := _player.global_position
var end_pt := start + Vector3.DOWN * DP.f("jump_land_threshold")
var params := PhysicsRayQueryParameters3D.create(start, end_pt)
params.exclude = [_player.get_rid()]
var hit := space.intersect_ray(params)
if hit:
_leg_state = LegState.LAND
_state_time = 0.0
LegState.LAND:
if on_floor:
_leg_state = LegState.GALLOP
_state_time = 0.0
_was_on_floor = on_floor
# ── Feet Stepping ─────────────────────────────────────────────────────────────
func _step_feet(delta: float) -> void:
match _leg_state:
LegState.GALLOP:
_step_gallop(delta)
LegState.LAUNCH:
_step_launch(delta)
LegState.RAGDOLL:
_step_ragdoll(delta)
LegState.TUCK:
_step_tuck(delta)
LegState.LAND:
_step_land(delta)
func _step_gallop(delta: float) -> void:
var threshold: float = DP.f("leg_step_threshold")
var duration: float = DP.f("leg_step_duration")
var height: float = DP.f("leg_step_height")
var overshoot: float = DP.f("leg_step_overshoot")
var flat_spd: float = Vector2(_player.velocity.x, _player.velocity.z).length()
var prev_phase: float = _gait_phase
_gait_phase = fmod(_gait_phase + flat_spd * DP.f("gait_freq") * delta, 1.0)
# Advance in-progress steps
for id: String in _step_t:
if _step_t[id] >= 1.0:
continue
_step_t[id] = minf(_step_t[id] + delta / duration, 1.0)
var t: float = _step_t[id]
var e: float = t * t * (3.0 - 2.0 * t)
var arc: float = height * sin(t * PI)
_foot[id] = _step_from[id].lerp(_step_to[id], e) + Vector3(0.0, arc, 0.0)
# Trigger steps when gait phase crosses each leg's offset
for id: String in LEG_PHASES:
if _step_t[id] < 1.0:
continue
if not _phase_crossed(prev_phase, _gait_phase, LEG_PHASES[id]):
continue
var ideal: Vector3 = _ideal_foot(id)
if _foot[id].distance_to(ideal) < threshold:
continue
_step_from[id] = _foot[id]
_step_to[id] = ideal + _player.velocity * duration * overshoot
_step_t[id] = 0.0
func _step_launch(_delta: float) -> void:
# Hold feet at their frozen launch positions
for id: String in _foot:
if _launch_feet.has(id):
_foot[id] = _launch_feet[id]
func _step_ragdoll(delta: float) -> void:
# Procedural floppy dangle — NOT real physics ragdoll
var time: float = _state_time
var amp: float = DP.f("ragdoll_wobble_amp")
# Amplitude grows over time (legs spread more the longer airborne)
var grow: float = clampf(time * 0.8, 0.0, 1.5)
for id: String in _foot:
var rest: Vector3 = LEG_CFG[id]["rest"]
var phase: float = RAGDOLL_PHASE[id]
var dangle_offset := Vector3(
sin(time * 3.0 + phase) * amp * grow, # side sway
-0.4 - sin(time * 2.0 + phase) * 0.1 * grow, # gravity pull down
cos(time * 2.5 + phase) * amp * grow # forward/back wobble
)
_foot[id] = _bull.to_global(rest + dangle_offset)
func _step_tuck(delta: float) -> void:
# Smoothly pull legs into tucked position under the body
var speed: float = DP.f("jump_tuck_speed")
for id: String in _foot:
var tucked: Vector3 = _get_tucked_foot(id)
_foot[id] = _foot[id].lerp(tucked, clampf(delta * speed, 0.0, 1.0))
func _step_land(delta: float) -> void:
# Extend legs from tucked pose toward ideal ground positions, resume gallop cycle
var speed: float = DP.f("jump_tuck_speed") * 1.5 # slightly faster extend
for id: String in _foot:
var ideal: Vector3 = _ideal_foot(id)
_foot[id] = _foot[id].lerp(ideal, clampf(delta * speed, 0.0, 1.0))
# ── Helpers ───────────────────────────────────────────────────────────────────
func _phase_crossed(prev: float, curr: float, target: float) -> bool:
if curr >= prev:
return target >= prev and target < curr
else:
return target >= prev or target < curr
func _ideal_foot(id: String) -> Vector3:
var world_rest: Vector3 = _bull.to_global(LEG_CFG[id]["rest"])
# Raycast for ground
var space := _player.get_world_3d().direct_space_state
var params := PhysicsRayQueryParameters3D.create(
world_rest + Vector3(0.0, 0.6, 0.0),
world_rest + Vector3(0.0, -1.4, 0.0),
)
params.exclude = [_player.get_rid()]
var hit := space.intersect_ray(params)
return hit["position"] if hit else world_rest
func _get_tucked_foot(id: String) -> Vector3:
var rest: Vector3 = LEG_CFG[id]["rest"]
var tucked_local := rest + Vector3(0.0, DP.f("jump_tuck_up"), DP.f("jump_tuck_fwd"))
return _bull.to_global(tucked_local)
func _apply_ik() -> void:
for id: String in _ik:
var ik: SkeletonIK3D = _ik[id]
var target_local: Vector3 = _skeleton.get_parent().to_local(_foot[id])
ik.target = Transform3D(Basis.IDENTITY, target_local)
# ── Tail ──────────────────────────────────────────────────────────────────────
func _update_tail(delta: float) -> void:
if _tail_world.is_empty():
return
var gravity := Vector3(0.0, -DP.f("tail_gravity"), 0.0)
var damping := 1.0 - clampf(DP.f("tail_damping"), 0.0, 0.99)
# Re-anchor root joint
var anchor: Vector3 = _skeleton.to_global(_skeleton.get_bone_global_rest(_tail_idx[0]).origin)
_tail_world[0] = anchor
_tail_prev[0] = anchor
# Idle wag force
var flat_speed: float = Vector2(_player.velocity.x, _player.velocity.z).length()
var wag_strength: float = lerpf(DP.f("tail_wag_idle"), 0.0, clampf(flat_speed / 3.0, 0.0, 1.0))
var time: float = Time.get_ticks_msec() / 1000.0
# Verlet integrate
for i in range(1, _tail_world.size()):
var vel: Vector3 = (_tail_world[i] - _tail_prev[i]) * damping
_tail_prev[i] = _tail_world[i]
# Add wag force (sinusoidal side-to-side)
var wag := _bull.global_transform.basis.x * sin(time * DP.f("tail_wag_freq") + i * 0.5) * wag_strength
_tail_world[i] = _tail_world[i] + vel + gravity * (delta * delta) + wag * delta
# Length constraint
for i in range(1, _tail_world.size()):
var dir: Vector3 = _tail_world[i] - _tail_world[i - 1]
var len: float = dir.length()
var target_len: float = _tail_lengths[i - 1]
if len > 0.0001:
_tail_world[i] = _tail_world[i - 1] + dir * (target_len / len)
else:
_tail_world[i] = _tail_world[i - 1] + Vector3(0.0, -target_len, 0.0)
# Body collision pass — prevent tail from clipping through torso
# Use bull's local space; body is approximated as a capsule along Z axis
var body_radius: float = DP.f("tail_body_radius")
for i in range(1, _tail_world.size()):
var p := _bull.to_local(_tail_world[i])
# Capsule centered at roughly (0, 0, 0.1) in bull local space, half-length 0.4 along Z
var closest := Vector3(0.0, 0.0, clampf(p.z, -0.3, 0.5))
var diff := p - closest
var dist := diff.length()
if dist < body_radius and dist > 0.001:
p = closest + diff.normalized() * body_radius
_tail_world[i] = _bull.to_global(p)
func _apply_tail() -> void:
if _tail_world.is_empty():
return
for i in range(_tail_world.size()):
var idx: int = _tail_idx[i]
var pos_skel: Vector3 = _skeleton.to_local(_tail_world[i])
var point_dir_skel: Vector3
if i < _tail_world.size() - 1:
point_dir_skel = (_skeleton.to_local(_tail_world[i + 1]) - pos_skel).normalized()
else:
var prev_skel: Vector3 = _skeleton.to_local(_tail_world[i - 1])
point_dir_skel = (pos_skel - prev_skel).normalized()
var rest_dir: Vector3 = _tail_rest_dir[i]
var rest_t: Transform3D = _skeleton.get_bone_global_rest(idx)
var new_basis: Basis
if rest_dir.length_squared() > 0.0001 and point_dir_skel.length_squared() > 0.0001:
new_basis = Basis(Quaternion(rest_dir, point_dir_skel)) * rest_t.basis
else:
new_basis = rest_t.basis
_skeleton.set_bone_global_pose_override(idx, Transform3D(new_basis, pos_skel), 1.0, false)
# ── Utility ───────────────────────────────────────────────────────────────────
func _find_skeleton(node: Node) -> Skeleton3D:
if node is Skeleton3D:
return node as Skeleton3D
for child in node.get_children():
var r := _find_skeleton(child)
if r:
return r
return null
func _find_physical_bones(node: Node) -> PhysicalBoneSimulator3D:
for child in node.get_children():
if child is PhysicalBoneSimulator3D:
return child as PhysicalBoneSimulator3D
return null
+1
View File
@@ -0,0 +1 @@
uid://bvtw8s41wrrx
+61 -3
View File
@@ -110,7 +110,11 @@ func _build_params(content: VBoxContainer) -> void:
content.add_child(header)
for key: String in sections[section]:
_add_float_row(content, key, DP.get_all()[key])
var meta: Dictionary = DP.get_all()[key]
if meta["type"] == TYPE_FLOAT:
_add_float_row(content, key, meta)
elif meta["type"] == TYPE_BOOL:
_add_bool_row(content, key, meta)
func _add_float_row(parent: VBoxContainer, key: String, meta: Dictionary) -> void:
@@ -161,6 +165,47 @@ func _add_float_row(parent: VBoxContainer, key: String, meta: Dictionary) -> voi
)
func _add_bool_row(parent: VBoxContainer, key: String, meta: Dictionary) -> void:
var row := HBoxContainer.new()
row.size_flags_horizontal = Control.SIZE_FILL
parent.add_child(row)
var lbl := Label.new()
lbl.text = key.replace("_", " ")
lbl.custom_minimum_size.x = 140
lbl.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
row.add_child(lbl)
var btn := Button.new()
btn.toggle_mode = true
btn.button_pressed = meta["value"]
btn.text = "HIDDEN" if not meta["value"] else "SHOWN"
_update_btn_style(btn, meta["value"])
row.add_child(btn)
btn.toggled.connect(func(v: bool) -> void:
btn.text = "HIDDEN" if not v else "SHOWN"
_update_btn_style(btn, v)
DP.set_value(key, v)
)
DP.any_changed.connect(func(changed_key: String, _val: Variant) -> void:
if changed_key != key and changed_key != "__all__":
return
var v_b: bool = DP.b(key)
btn.set_pressed_no_signal(v_b)
btn.text = "HIDDEN" if not v_b else "SHOWN"
_update_btn_style(btn, v_b)
)
func _update_btn_style(btn: Button, active: bool) -> void:
if active:
btn.add_theme_color_override("font_color", Color(0.4, 1.0, 0.5))
else:
btn.add_theme_color_override("font_color", Color(1.0, 0.4, 0.4))
func _on_save() -> void:
DP.save()
_status_label.text = "Saved!"
@@ -169,8 +214,21 @@ func _on_save() -> void:
)
func _on_dp_changed(_key: String, _value: Variant) -> void:
pass # individual rows handle their own updates via signal
func _on_dp_changed(key: String, _value: Variant) -> void:
if key == "show_collisions" or key == "__all__":
var active: bool = DP.b("show_collisions")
get_tree().debug_collisions_hint = active
# Force refresh of all collision shapes
_refresh_collisions(get_tree().root, active)
func _refresh_collisions(node: Node, active: bool) -> void:
if node is CollisionShape3D or node is CollisionPolygon3D:
node.visible = !node.visible # Trigger a notification
node.visible = !node.visible
for child in node.get_children():
_refresh_collisions(child, active)
func _input(event: InputEvent) -> void:
+1
View File
@@ -0,0 +1 @@
uid://via815ow6p5r
+55 -12
View File
@@ -17,18 +17,21 @@ func _ready() -> void:
func _register_all() -> void:
# ── Movement ──────────────────────────────────────────────────────────────
_reg_f("Movement", "walk_speed", 6.0, 1.0, 40.0)
_reg_f("Movement", "charge_speed", 18.0, 5.0, 60.0)
_reg_f("Movement", "walk_accel", 14.0, 1.0, 80.0)
_reg_f("Movement", "charge_accel", 40.0, 5.0, 120.0)
_reg_f("Movement", "deceleration", 12.0, 1.0, 50.0)
_reg_f("Movement", "charge_decel", 3.5, 0.5, 20.0)
_reg_f("Movement", "jump_velocity", 4.5, 1.0, 20.0)
# ── Turning ───────────────────────────────────────────────────────────────
_reg_f("Turning", "walk_turn_fast", 8.0, 0.5, 25.0)
_reg_f("Turning", "walk_turn_slow", 3.5, 0.5, 15.0)
_reg_f("Turning", "charge_turn", 0.55, 0.05, 3.0, 0.01)
_reg_f("Turning", "visual_turn_speed", 12.0, 1.0, 40.0)
_reg_f("Movement", "kick_impulse", 7.0, 0.5, 200.0)
_reg_f("Movement", "kick_interval", 0.22, 0.05, 2.0, 0.01)
_reg_f("Movement", "kick_vertical", 3.5, 0.0, 100.0)
_reg_f("Movement", "kick_spread", 12.0, 0.0, 90.0)
_reg_f("Movement", "kick_force_var", 0.20, 0.0, 2.0, 0.05)
_reg_f("Movement", "charge_kick_mult", 3.0, 1.0, 50.0)
_reg_f("Movement", "roll_damping", 0.30, 0.01, 0.99, 0.01)
_reg_f("Movement", "max_speed", 20.0, 2.0, 1000.0)
_reg_f("Movement", "jump_velocity", 4.5, 1.0, 200.0)
_reg_f("Movement", "visual_turn_speed", 12.0, 1.0, 200.0)
_reg_f("Movement", "bounce_restitution", 0.55, 0.0, 2.0, 0.05)
_reg_f("Movement", "bounce_min_impact", 1.0, 0.0, 20.0)
# ── Dust speed thresholds ─────────────────────────────────────────────────
_reg_f("Dust", "dust_walk_spd", 1.5, 0.1, 10.0)
_reg_f("Dust", "dust_charge_spd", 7.0, 1.0, 20.0)
# ── Dust ──────────────────────────────────────────────────────────────────
_reg_f("Dust", "dust_lifetime", 0.9, 0.1, 4.0)
_reg_f("Dust", "dust_explosiveness", 0.35, 0.0, 1.0)
@@ -42,6 +45,32 @@ func _register_all() -> void:
_reg_f("Dust", "charge_scale_max", 2.2, 0.05, 6.0)
_reg_f("Dust", "charge_vel_min", 2.8, 0.1, 20.0)
_reg_f("Dust", "charge_vel_max", 5.5, 0.1, 20.0)
# ── Body wobble ───────────────────────────────────────────────────────────
_reg_f("Body", "body_tilt_fwd", 0.035, 0.0, 0.15, 0.005)
_reg_f("Body", "body_tilt_side", 0.025, 0.0, 0.15, 0.005)
_reg_f("Body", "aerial_pitch_scale", 0.7, 0.0, 4.0, 0.05)
_reg_f("Body", "aerial_pitch_speed", 6.0, 0.5, 30.0)
# ── Legs ──────────────────────────────────────────────────────────────────
_reg_f("Legs", "gait_freq", 0.30, 0.0, 5.0, 0.01)
_reg_f("Legs", "leg_step_threshold", 0.12, 0.01, 1.5, 0.01)
_reg_f("Legs", "leg_step_height", 0.32, 0.0, 1.5, 0.01)
_reg_f("Legs", "leg_step_duration", 0.10, 0.02, 0.5, 0.01)
_reg_f("Legs", "leg_step_overshoot", 1.2, 0.0, 5.0, 0.1)
_reg_f("Legs", "jump_tuck_fwd", 0.2, -1.0, 1.0, 0.05)
_reg_f("Legs", "jump_tuck_up", 0.3, 0.0, 1.5, 0.05)
_reg_f("Legs", "jump_tuck_speed", 10.0, 1.0, 30.0)
_reg_f("Legs", "jump_land_threshold", 1.5, 0.0, 5.0, 0.1)
_reg_f("Legs", "launch_hold_time", 0.15, 0.0, 1.0, 0.01)
_reg_f("Legs", "ragdoll_wobble_amp", 0.15, 0.0, 1.0, 0.01)
_reg_f("Legs", "tuck_apex_threshold", 1.0, -5.0, 5.0, 0.1)
# ── Tail ──────────────────────────────────────────────────────────────────
_reg_f("Tail", "tail_gravity", 9.8, 0.0, 30.0)
_reg_f("Tail", "tail_damping", 0.08, 0.0, 0.5, 0.01)
_reg_f("Tail", "tail_body_radius", 0.35, 0.05, 2.0, 0.05)
_reg_f("Tail", "tail_wag_freq", 4.0, 0.5, 15.0)
_reg_f("Tail", "tail_wag_idle", 0.3, 0.0, 2.0, 0.05)
# ── Debug ─────────────────────────────────────────────────────────────────
_reg_b("Debug", "show_collisions", false)
func _reg_f(section: String, key: String, default: float,
@@ -57,11 +86,25 @@ func _reg_f(section: String, key: String, default: float,
}
func _reg_b(section: String, key: String, default: bool) -> void:
_params[key] = {
"value": default,
"default": default,
"section": section,
"type": TYPE_BOOL,
}
## Read a float param. Panics on unknown key — intentional: typos should be loud.
func f(key: String) -> float:
return _params[key]["value"] as float
## Read a bool param.
func b(key: String) -> bool:
return _params[key]["value"] as bool
func set_value(key: String, value: Variant) -> void:
if not _params.has(key):
return
+1
View File
@@ -0,0 +1 @@
uid://b3ix5yi7b3ynh
+119 -31
View File
@@ -1,6 +1,6 @@
extends CharacterBody3D
# Hoof positions in bull_v10 local space — adjust y/z if puffs don't land on the hooves
# Hoof positions in bull_v10 local space — dust emitter offsets
const HOOF_OFFSETS: Array = [
Vector3(-0.30, -0.25, -0.50),
Vector3( 0.30, -0.25, -0.50),
@@ -11,10 +11,13 @@ const HOOF_OFFSETS: Array = [
@onready var camera_pivot: Node3D = $SpringArmPivot
@onready var cube_guy: Node3D = $bull_v10
var current_speed: float = 0.0
var facing_angle: float = 0.0
var _hoof_emitters: Array[CPUParticles3D] = []
var _legs: Node
var _debug_shapes: Array[MeshInstance3D] = []
var _kick_timer: float = 0.0
var _was_on_floor: bool = false
var _pre_slide_vel_y: float = 0.0
enum _DustState { NONE, WALK, CHARGE }
var _dust_state: _DustState = _DustState.NONE
@@ -22,7 +25,16 @@ var _dust_state: _DustState = _DustState.NONE
func _ready() -> void:
_setup_hoof_dust()
_setup_legs()
DP.any_changed.connect(_on_dp_changed)
_sync_debug_vis()
func _setup_legs() -> void:
_legs = Node.new()
_legs.set_script(load("res://bull_legs.gd"))
add_child(_legs)
_legs.call(&"setup", self, cube_guy)
func _setup_hoof_dust() -> void:
@@ -97,8 +109,10 @@ func _set_dust_state(new_state: _DustState) -> void:
p.emitting = true
func _on_dp_changed(_key: String, _val: Variant) -> void:
# Force re-apply so slider changes take effect without needing a state transition
func _on_dp_changed(key: String, _val: Variant) -> void:
if key == "show_collisions" or key == "__all__":
_sync_debug_vis()
if _hoof_emitters.is_empty() or _dust_state == _DustState.NONE:
return
var prev := _dust_state
@@ -106,13 +120,79 @@ func _on_dp_changed(_key: String, _val: Variant) -> void:
_set_dust_state(prev)
func _sync_debug_vis() -> void:
_clear_debug_vis()
if not DP.b("show_collisions"):
return
# Material for wireframes
var mat := StandardMaterial3D.new()
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
mat.no_depth_test = true
mat.albedo_color = Color(0, 1, 0, 0.5)
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
# Find all collision shapes in self and children
_create_debug_meshes(self, mat)
func _create_debug_meshes(node: Node, mat: Material) -> void:
if node is CollisionShape3D and node.shape:
var mi := MeshInstance3D.new()
mi.mesh = node.shape.get_debug_mesh()
mi.material_override = mat
# We add it as child of the collision shape to follow its transform
node.add_child(mi)
_debug_shapes.append(mi)
for child in node.get_children():
_create_debug_meshes(child, mat)
func _clear_debug_vis() -> void:
for mi in _debug_shapes:
if is_instance_valid(mi):
mi.get_parent().remove_child(mi)
mi.queue_free()
_debug_shapes.clear()
func _apply_kick(direction: Vector3, charging: bool) -> void:
var base := DP.f("kick_impulse") * (DP.f("charge_kick_mult") if charging else 1.0)
# Random force variance — each kick punches a different amount
var force := base * randf_range(1.0 - DP.f("kick_force_var"), 1.0 + DP.f("kick_force_var"))
# Random angle spread — slight mis-kick like a real ball
var spread := deg_to_rad(randf_range(-DP.f("kick_spread"), DP.f("kick_spread")))
var kick_dir := direction.rotated(Vector3.UP, spread)
velocity.x += kick_dir.x * force
velocity.z += kick_dir.z * force
# Vertical arc — only when grounded so arcs don't chain infinitely in the air
if is_on_floor():
velocity.y += DP.f("kick_vertical") * randf_range(0.75, 1.25)
# Cap immediately after kick so charge doesn't overshoot by too much
var flat := Vector2(velocity.x, velocity.z)
var spd := flat.length()
var cap := DP.f("max_speed") * (DP.f("charge_kick_mult") if charging else 1.0)
if spd > cap:
var s := cap / spd
velocity.x *= s
velocity.z *= s
func _physics_process(delta: float) -> void:
# ── Gravity & jump ────────────────────────────────────────────────────────
if not is_on_floor():
velocity += get_gravity() * delta
if Input.is_action_just_pressed("jump") and is_on_floor():
if Input.is_action_just_pressed(&"jump") and is_on_floor():
velocity.y = DP.f("jump_velocity")
# ── Camera-relative input direction ───────────────────────────────────────
var input_dir := Input.get_vector("move_left", "move_right", "move_forward", "move_back")
var cam_basis := camera_pivot.global_transform.basis
var flat_forward := -Vector3(cam_basis.z.x, 0.0, cam_basis.z.z).normalized()
@@ -120,36 +200,44 @@ func _physics_process(delta: float) -> void:
var direction := (flat_forward * -input_dir.y + flat_right * input_dir.x).normalized()
var charging := Input.is_action_pressed(&"charge")
var on_floor := is_on_floor()
if direction:
var target_angle := atan2(direction.x, direction.z)
# Reset kick timer on landing so the first kick after a bounce is immediate
if on_floor and not _was_on_floor and direction:
_kick_timer = 0.0
_was_on_floor = on_floor
if current_speed < 0.01:
facing_angle = target_angle
# ── Periodic kick ─────────────────────────────────────────────────────────
_kick_timer = maxf(0.0, _kick_timer - delta)
if direction and _kick_timer <= 0.0:
_apply_kick(direction, charging)
_kick_timer = DP.f("kick_interval")
var speed_t: float = clampf(current_speed / DP.f("walk_speed"), 0.0, 1.0)
var turn_rate: float = DP.f("charge_turn") if charging \
else lerpf(DP.f("walk_turn_fast"), DP.f("walk_turn_slow"), speed_t)
# ── Rolling friction (exponential, frame-rate independent) ────────────────
var retain := pow(DP.f("roll_damping"), delta)
velocity.x *= retain
velocity.z *= retain
var diff := wrapf(target_angle - facing_angle, -PI, PI)
facing_angle += clampf(diff, -turn_rate * delta, turn_rate * delta)
var top_speed: float = DP.f("charge_speed") if charging else DP.f("walk_speed")
var accel: float = DP.f("charge_accel") if charging else DP.f("walk_accel")
current_speed = move_toward(current_speed, top_speed, accel * delta)
else:
var decel: float = DP.f("charge_decel") if charging else DP.f("deceleration")
current_speed = move_toward(current_speed, 0.0, decel * delta)
velocity.x = sin(facing_angle) * current_speed
velocity.z = cos(facing_angle) * current_speed
cube_guy.rotation.y = lerp_angle(cube_guy.rotation.y, facing_angle,
delta * DP.f("visual_turn_speed"))
# ── Visual rotation follows velocity direction ─────────────────────────────
var flat_speed := Vector2(velocity.x, velocity.z).length()
if flat_speed > 0.4:
var target_angle := atan2(velocity.x, velocity.z)
cube_guy.rotation.y = lerp_angle(
cube_guy.rotation.y, target_angle, delta * DP.f("visual_turn_speed"))
_pre_slide_vel_y = velocity.y
move_and_slide()
if current_speed > 0.5 and is_on_floor():
_set_dust_state(_DustState.CHARGE if charging else _DustState.WALK)
# ── Bounce on landing ─────────────────────────────────────────────────────
if is_on_floor() and not _was_on_floor:
var impact := -_pre_slide_vel_y
if impact > DP.f("bounce_min_impact"):
velocity.y = impact * DP.f("bounce_restitution")
# ── Dust ──────────────────────────────────────────────────────────────────
if flat_speed > DP.f("dust_charge_spd") and on_floor:
_set_dust_state(_DustState.CHARGE)
elif flat_speed > DP.f("dust_walk_spd") and on_floor:
_set_dust_state(_DustState.WALK)
else:
_set_dust_state(_DustState.NONE)
+5 -1
View File
@@ -11,10 +11,14 @@ config_version=5
[application]
config/name="Bullosseum"
config/features=PackedStringArray("4.6", "Forward Plus")
config/features=PackedStringArray("4.7", "Forward Plus")
config/icon="res://icon.svg"
config/run/main_scene="res://scene.tscn"
[editor_plugins]
enabled=PackedStringArray("res://addons/rider-plugin/plugin.cfg")
[input]
move_forward={