Feat/14 movement overhaul #20
@@ -168,31 +168,31 @@ func _create_walk_anim() -> Animation:
|
||||
|
||||
# Add rotation tracks for each bone using type=1 (ROTATION)
|
||||
_add_rot_track(anim, "LeftUpperLeg", [
|
||||
{"t": 0.0, "euler": Vector3(-0.6, 0, 0)},
|
||||
{"t": cycle * 0.5, "euler": Vector3(0.6, 0, 0)},
|
||||
{"t": cycle, "euler": Vector3(-0.6, 0, 0)}
|
||||
{"t": 0.0, "rot": Vector3(-0.6, 0, 0)},
|
||||
{"t": cycle * 0.5, "rot": Vector3(0.6, 0, 0)},
|
||||
{"t": cycle, "rot": Vector3(-0.6, 0, 0)}
|
||||
])
|
||||
_add_rot_track(anim, "RightUpperLeg", [
|
||||
{"t": 0.0, "euler": Vector3(0.6, 0, 0)},
|
||||
{"t": cycle * 0.5, "euler": Vector3(-0.6, 0, 0)},
|
||||
{"t": cycle, "euler": Vector3(0.6, 0, 0)}
|
||||
{"t": 0.0, "rot": Vector3(0.6, 0, 0)},
|
||||
{"t": cycle * 0.5, "rot": Vector3(-0.6, 0, 0)},
|
||||
{"t": cycle, "rot": Vector3(0.6, 0, 0)}
|
||||
])
|
||||
_add_rot_track(anim, "LeftUpperArm", [
|
||||
{"t": 0.0, "euler": Vector3(-0.4, 0, 0)},
|
||||
{"t": cycle * 0.5, "euler": Vector3(0.4, 0, 0)},
|
||||
{"t": cycle, "euler": Vector3(-0.4, 0, 0)}
|
||||
{"t": 0.0, "rot": Vector3(-0.4, 0, 0)},
|
||||
{"t": cycle * 0.5, "rot": Vector3(0.4, 0, 0)},
|
||||
{"t": cycle, "rot": Vector3(-0.4, 0, 0)}
|
||||
])
|
||||
_add_rot_track(anim, "RightUpperArm", [
|
||||
{"t": 0.0, "euler": Vector3(0.4, 0, 0)},
|
||||
{"t": cycle * 0.5, "euler": Vector3(-0.4, 0, 0)},
|
||||
{"t": cycle, "euler": Vector3(0.4, 0, 0)}
|
||||
{"t": 0.0, "rot": Vector3(0.4, 0, 0)},
|
||||
{"t": cycle * 0.5, "rot": Vector3(-0.4, 0, 0)},
|
||||
{"t": cycle, "rot": Vector3(0.4, 0, 0)}
|
||||
])
|
||||
_add_rot_track(anim, "Spine", [
|
||||
{"t": 0.0, "euler": Vector3(0, 0, 0)},
|
||||
{"t": cycle * 0.25, "euler": Vector3(0, 0.1, 0)},
|
||||
{"t": cycle * 0.5, "euler": Vector3(0, 0, 0)},
|
||||
{"t": cycle * 0.75, "euler": Vector3(0, -0.1, 0)},
|
||||
{"t": cycle, "euler": Vector3(0, 0, 0)}
|
||||
{"t": 0.0, "rot": Vector3(0, 0, 0)},
|
||||
{"t": cycle * 0.25, "rot": Vector3(0, 0.1, 0)},
|
||||
{"t": cycle * 0.5, "rot": Vector3(0, 0, 0)},
|
||||
{"t": cycle * 0.75, "rot": Vector3(0, -0.1, 0)},
|
||||
{"t": cycle, "rot": Vector3(0, 0, 0)}
|
||||
])
|
||||
return anim
|
||||
|
||||
@@ -207,24 +207,24 @@ func _create_jump_anim() -> Animation:
|
||||
anim.loop_mode = Animation.LOOP_LINEAR
|
||||
|
||||
_add_rot_track(anim, "LeftUpperArm", [
|
||||
{"t": 0.0, "euler": Vector3(0, 0, 0)},
|
||||
{"t": 0.5, "euler": Vector3(-1.2, 0, 0)},
|
||||
{"t": 1.0, "euler": Vector3(0, 0, 0)}
|
||||
{"t": 0.0, "rot": Vector3(0, 0, 0)},
|
||||
{"t": 0.5, "rot": Vector3(-1.2, 0, 0)},
|
||||
{"t": 1.0, "rot": Vector3(0, 0, 0)}
|
||||
])
|
||||
_add_rot_track(anim, "RightUpperArm", [
|
||||
{"t": 0.0, "euler": Vector3(0, 0, 0)},
|
||||
{"t": 0.5, "euler": Vector3(-1.2, 0, 0)},
|
||||
{"t": 1.0, "euler": Vector3(0, 0, 0)}
|
||||
{"t": 0.0, "rot": Vector3(0, 0, 0)},
|
||||
{"t": 0.5, "rot": Vector3(-1.2, 0, 0)},
|
||||
{"t": 1.0, "rot": Vector3(0, 0, 0)}
|
||||
])
|
||||
_add_rot_track(anim, "LeftUpperLeg", [
|
||||
{"t": 0.0, "euler": Vector3(0, 0, 0)},
|
||||
{"t": 0.5, "euler": Vector3(-0.3, 0, 0)},
|
||||
{"t": 1.0, "euler": Vector3(0, 0, 0)}
|
||||
{"t": 0.0, "rot": Vector3(0, 0, 0)},
|
||||
{"t": 0.5, "rot": Vector3(-0.3, 0, 0)},
|
||||
{"t": 1.0, "rot": Vector3(0, 0, 0)}
|
||||
])
|
||||
_add_rot_track(anim, "RightUpperLeg", [
|
||||
{"t": 0.0, "euler": Vector3(0, 0, 0)},
|
||||
{"t": 0.5, "euler": Vector3(-0.3, 0, 0)},
|
||||
{"t": 1.0, "euler": Vector3(0, 0, 0)}
|
||||
{"t": 0.0, "rot": Vector3(0, 0, 0)},
|
||||
{"t": 0.5, "rot": Vector3(-0.3, 0, 0)},
|
||||
{"t": 1.0, "rot": Vector3(0, 0, 0)}
|
||||
])
|
||||
return anim
|
||||
|
||||
@@ -234,34 +234,35 @@ func _create_idle_anim() -> Animation:
|
||||
anim.loop_mode = Animation.LOOP_LINEAR
|
||||
|
||||
_add_rot_track(anim, "Spine", [
|
||||
{"t": 0.0, "euler": Vector3(0, 0, 0)},
|
||||
{"t": 1.0, "euler": Vector3(0.02, 0, 0)},
|
||||
{"t": 2.0, "euler": Vector3(0, 0, 0)}
|
||||
{"t": 0.0, "rot": Vector3(0, 0, 0)},
|
||||
{"t": 1.0, "rot": Vector3(0.02, 0, 0)},
|
||||
{"t": 2.0, "rot": Vector3(0, 0, 0)}
|
||||
])
|
||||
_add_rot_track(anim, "Head", [
|
||||
{"t": 0.0, "euler": Vector3(0, 0, 0)},
|
||||
{"t": 1.0, "euler": Vector3(0.03, 0.02, 0)},
|
||||
{"t": 2.0, "euler": Vector3(0, 0, 0)}
|
||||
{"t": 0.0, "rot": Vector3(0, 0, 0)},
|
||||
{"t": 1.0, "rot": Vector3(0.03, 0.02, 0)},
|
||||
{"t": 2.0, "rot": Vector3(0, 0, 0)}
|
||||
])
|
||||
_add_rot_track(anim, "LeftUpperArm", [
|
||||
{"t": 0.0, "euler": Vector3(0, 0, 0)},
|
||||
{"t": 1.0, "euler": Vector3(0.02, 0, 0.05)},
|
||||
{"t": 2.0, "euler": Vector3(0, 0, 0)}
|
||||
{"t": 0.0, "rot": Vector3(0, 0, 0)},
|
||||
{"t": 1.0, "rot": Vector3(0.02, 0, 0.05)},
|
||||
{"t": 2.0, "rot": Vector3(0, 0, 0)}
|
||||
])
|
||||
_add_rot_track(anim, "RightUpperArm", [
|
||||
{"t": 0.0, "euler": Vector3(0, 0, 0)},
|
||||
{"t": 1.0, "euler": Vector3(0.02, 0, -0.05)},
|
||||
{"t": 2.0, "euler": Vector3(0, 0, 0)}
|
||||
{"t": 0.0, "rot": Vector3(0, 0, 0)},
|
||||
{"t": 1.0, "rot": Vector3(0.02, 0, -0.05)},
|
||||
{"t": 2.0, "rot": Vector3(0, 0, 0)}
|
||||
])
|
||||
return anim
|
||||
|
||||
func _add_rot_track(anim: Animation, bone_name: String, keyframes: Array) -> void:
|
||||
var track = anim.add_track(0) # TYPE_VALUE = 0 (required in Godot 4.2.1)
|
||||
var track = anim.add_track(Animation.TYPE_ROTATION_3D) # TYPE_ROTATION_3D = 2
|
||||
anim.track_set_path(track, "MikuRig/Skeleton3D:" + bone_name)
|
||||
anim.track_set_interpolation_type(track, Animation.INTERPOLATION_LINEAR)
|
||||
for kf in keyframes:
|
||||
# Use Vector3 euler angles - Godot 4.2.1 accepts both Vector3 and Quaternion
|
||||
anim.track_insert_key(track, kf["t"], kf["euler"])
|
||||
# Rotation tracks require Quaternion values
|
||||
var quat = Quaternion.from_euler(kf["rot"])
|
||||
anim.track_insert_key(track, kf["t"], quat)
|
||||
|
||||
func _is_animation_broken(ap: AnimationPlayer, anim_name: String) -> bool:
|
||||
## Returns true if the animation has near-zero motion (Blender GLTF track stripping).
|
||||
@@ -277,21 +278,15 @@ func _is_animation_broken(ap: AnimationPlayer, anim_name: String) -> bool:
|
||||
var key_count = anim.track_get_key_count(t)
|
||||
if key_count < 2:
|
||||
continue
|
||||
var path = String(anim.track_get_path(t))
|
||||
var is_rotation_track = path.ends_with(":rotation")
|
||||
for i in range(1, key_count):
|
||||
var v = anim.track_get_key_value(t, i)
|
||||
if v is Vector3:
|
||||
var prev_v = anim.track_get_key_value(t, i - 1)
|
||||
var delta: float
|
||||
if is_rotation_track:
|
||||
# For rotation tracks, sum absolute euler angle differences
|
||||
delta = abs(v.x - prev_v.x) + abs(v.y - prev_v.y) + abs(v.z - prev_v.z)
|
||||
else:
|
||||
delta = v.distance_to(prev_v)
|
||||
max_delta = max(max_delta, delta)
|
||||
# Threshold: rotation < 0.02 rad total (~1.1 deg) or position < 0.005 units
|
||||
return max_delta < 0.02
|
||||
var prev = anim.track_get_key_value(t, i - 1)
|
||||
if v is Vector3 and prev is Vector3:
|
||||
max_delta = max(max_delta, v.distance_to(prev))
|
||||
elif v is Quaternion and prev is Quaternion:
|
||||
max_delta = max(max_delta, v.angle_to(prev))
|
||||
# Threshold: < 0.005 units of motion
|
||||
return max_delta < 0.005
|
||||
|
||||
func _get_bone_name(bone_name: String) -> String:
|
||||
## Returns the bone name as stored in the skeleton (uses cache).
|
||||
@@ -299,177 +294,6 @@ func _get_bone_name(bone_name: String) -> String:
|
||||
return bone_name
|
||||
return bone_name
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# CODE-DRIVEN ANIMATION SYSTEM
|
||||
# Godot 4.2.1 AnimationPlayer cannot rotate bones via ":rotation" track paths
|
||||
# on runtime-loaded GLB skeletons. We apply rotations directly each frame.
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
func _create_locomotion_data(loop: bool, speed_mult: float = 1.0) -> Dictionary:
|
||||
## Creates a walk/run animation data structure.
|
||||
## Returns: {length, loop, tracks: [{bone, keyframes: [{t, rot: Vector3}]}]}
|
||||
var cycle: float = 1.0 / locomotion_cycle_speed * speed_mult
|
||||
var t1: float = 0.0
|
||||
var t2: float = cycle * 0.25
|
||||
var t3: float = cycle * 0.5
|
||||
var t4: float = cycle * 0.75
|
||||
var t5: float = cycle
|
||||
|
||||
var tracks: Array = []
|
||||
|
||||
# Legs: opposite phase walk cycle
|
||||
_add_catmull_rot_track(tracks, "LeftUpperLeg", [t1, t2, t3, t4, t5],
|
||||
[-0.6, -0.1, 0.6, -0.1, -0.6], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0])
|
||||
_add_catmull_rot_track(tracks, "LeftLowerLeg", [t1, t2, t3, t4, t5],
|
||||
[0.1, 0.5, 0.8, 0.3, 0.1], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0])
|
||||
_add_catmull_rot_track(tracks, "RightUpperLeg", [t1, t2, t3, t4, t5],
|
||||
[0.6, -0.1, -0.6, -0.1, 0.6], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0])
|
||||
_add_catmull_rot_track(tracks, "RightLowerLeg", [t1, t2, t3, t4, t5],
|
||||
[0.3, 0.1, 0.1, 0.5, 0.3], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0])
|
||||
|
||||
# Arms: opposite to legs
|
||||
_add_catmull_rot_track(tracks, "LeftUpperArm", [t1, t2, t3, t4, t5],
|
||||
[0.5, 0.0, -0.5, 0.0, 0.5], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0])
|
||||
_add_catmull_rot_track(tracks, "LeftLowerArm", [t1, t2, t3, t4, t5],
|
||||
[-0.8, -0.3, -0.2, -0.5, -0.8], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0])
|
||||
_add_catmull_rot_track(tracks, "RightUpperArm", [t1, t2, t3, t4, t5],
|
||||
[-0.5, 0.0, 0.5, 0.0, -0.5], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0])
|
||||
_add_catmull_rot_track(tracks, "RightLowerArm", [t1, t2, t3, t4, t5],
|
||||
[-0.2, -0.5, -0.8, -0.3, -0.2], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0])
|
||||
|
||||
# Spine twist
|
||||
_add_catmull_rot_track(tracks, "Spine", [t1, t2, t3, t4, t5],
|
||||
[0.0, 0.05, 0.0, -0.05, 0.0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0])
|
||||
|
||||
# Hips bounce (position offset)
|
||||
_add_catmull_pos_track(tracks, "Hips", [t1, t2, t3, t4, t5],
|
||||
[0, 0, 0, 0, 0], [0, 0.02, 0, 0.02, 0], [0, 0, 0, 0, 0])
|
||||
|
||||
return {"length": cycle, "loop": loop, "tracks": tracks}
|
||||
|
||||
func _create_jump_data() -> Dictionary:
|
||||
## Creates jump animation data.
|
||||
var tracks: Array = []
|
||||
var t1: float = 0.0
|
||||
var t2: float = 0.5
|
||||
var t3: float = 1.0
|
||||
|
||||
_add_keyframe_rot_track(tracks, "LeftUpperArm", [
|
||||
{"t": t1, "rot": Vector3(-0.8, 0, 0.3)},
|
||||
{"t": t2, "rot": Vector3(-1.2, 0, 0.5)},
|
||||
{"t": t3, "rot": Vector3(-0.8, 0, 0.3)}
|
||||
])
|
||||
_add_keyframe_rot_track(tracks, "RightUpperArm", [
|
||||
{"t": t1, "rot": Vector3(-0.8, 0, -0.3)},
|
||||
{"t": t2, "rot": Vector3(-1.2, 0, -0.5)},
|
||||
{"t": t3, "rot": Vector3(-0.8, 0, -0.3)}
|
||||
])
|
||||
_add_keyframe_rot_track(tracks, "LeftLowerArm", [
|
||||
{"t": t1, "rot": Vector3(0, 0, 0)},
|
||||
{"t": t2, "rot": Vector3(-0.4, 0, 0)},
|
||||
{"t": t3, "rot": Vector3(0, 0, 0)}
|
||||
])
|
||||
_add_keyframe_rot_track(tracks, "RightLowerArm", [
|
||||
{"t": t1, "rot": Vector3(0, 0, 0)},
|
||||
{"t": t2, "rot": Vector3(-0.4, 0, 0)},
|
||||
{"t": t3, "rot": Vector3(0, 0, 0)}
|
||||
])
|
||||
_add_keyframe_rot_track(tracks, "LeftUpperLeg", [
|
||||
{"t": t1, "rot": Vector3(0, 0, 0)},
|
||||
{"t": t2, "rot": Vector3(-0.3, 0, 0)},
|
||||
{"t": t3, "rot": Vector3(0, 0, 0)}
|
||||
])
|
||||
_add_keyframe_rot_track(tracks, "RightUpperLeg", [
|
||||
{"t": t1, "rot": Vector3(0, 0, 0)},
|
||||
{"t": t2, "rot": Vector3(-0.3, 0, 0)},
|
||||
{"t": t3, "rot": Vector3(0, 0, 0)}
|
||||
])
|
||||
_add_keyframe_rot_track(tracks, "Spine", [
|
||||
{"t": t1, "rot": Vector3(0, 0, 0)},
|
||||
{"t": t2, "rot": Vector3(0.1, 0, 0)},
|
||||
{"t": t3, "rot": Vector3(0, 0, 0)}
|
||||
])
|
||||
|
||||
return {"length": 1.0, "loop": true, "tracks": tracks}
|
||||
|
||||
func _create_idle_data() -> Dictionary:
|
||||
## Creates idle animation data.
|
||||
var tracks: Array = []
|
||||
var t1: float = 0.0
|
||||
var t2: float = 1.0
|
||||
var t3: float = 2.0
|
||||
|
||||
_add_keyframe_rot_track(tracks, "Spine", [
|
||||
{"t": t1, "rot": Vector3(0, 0, 0)},
|
||||
{"t": t2, "rot": Vector3(0.02, 0, 0)},
|
||||
{"t": t3, "rot": Vector3(0, 0, 0)}
|
||||
])
|
||||
_add_keyframe_rot_track(tracks, "Head", [
|
||||
{"t": t1, "rot": Vector3(0, 0, 0)},
|
||||
{"t": t2, "rot": Vector3(0.03, 0.02, 0)},
|
||||
{"t": t3, "rot": Vector3(0, 0, 0)}
|
||||
])
|
||||
_add_keyframe_rot_track(tracks, "LeftUpperArm", [
|
||||
{"t": t1, "rot": Vector3(0, 0, 0.05)},
|
||||
{"t": t2, "rot": Vector3(0.02, 0, 0.08)},
|
||||
{"t": t3, "rot": Vector3(0, 0, 0.05)}
|
||||
])
|
||||
_add_keyframe_rot_track(tracks, "RightUpperArm", [
|
||||
{"t": t1, "rot": Vector3(0, 0, -0.05)},
|
||||
{"t": t2, "rot": Vector3(0.02, 0, -0.08)},
|
||||
{"t": t3, "rot": Vector3(0, 0, -0.05)}
|
||||
])
|
||||
_add_keyframe_pos_track(tracks, "Hips", [
|
||||
{"t": t1, "pos": Vector3(0, 0, 0)},
|
||||
{"t": t2, "pos": Vector3(0, 0.005, 0)},
|
||||
{"t": t3, "pos": Vector3(0, 0, 0)}
|
||||
])
|
||||
|
||||
return {"length": 2.0, "loop": true, "tracks": tracks}
|
||||
|
||||
func _add_catmull_rot_track(tracks: Array, bone_name: String, times: Array, x: Array, y: Array, z: Array) -> void:
|
||||
var kf: Array = []
|
||||
for i in range(times.size()):
|
||||
kf.append({"t": times[i], "rot": Vector3(x[i], y[i], z[i])})
|
||||
tracks.append({"bone": bone_name, "keyframes": kf})
|
||||
|
||||
func _add_catmull_pos_track(tracks: Array, bone_name: String, times: Array, x: Array, y: Array, z: Array) -> void:
|
||||
var kf: Array = []
|
||||
for i in range(times.size()):
|
||||
kf.append({"t": times[i], "pos": Vector3(x[i], y[i], z[i])})
|
||||
tracks.append({"bone": bone_name, "keyframes": kf})
|
||||
|
||||
func _add_keyframe_rot_track(tracks: Array, bone_name: String, keyframes: Array) -> void:
|
||||
var kf: Array = []
|
||||
for k in keyframes:
|
||||
kf.append({"t": k["t"], "rot": k["rot"]})
|
||||
tracks.append({"bone": bone_name, "keyframes": kf})
|
||||
|
||||
func _add_keyframe_pos_track(tracks: Array, bone_name: String, keyframes: Array) -> void:
|
||||
var kf: Array = []
|
||||
for k in keyframes:
|
||||
kf.append({"t": k["t"], "pos": k["pos"]})
|
||||
tracks.append({"bone": bone_name, "keyframes": kf})
|
||||
|
||||
|
||||
|
||||
func _add_animation_to_player(ap: AnimationPlayer, anim_name: String, anim: Animation) -> void:
|
||||
## Adds an animation to the AnimationPlayer via its default library.
|
||||
var lib_name: String = ""
|
||||
if not ap.has_animation_library(lib_name):
|
||||
ap.add_animation_library(lib_name, AnimationLibrary.new())
|
||||
var lib = ap.get_animation_library(lib_name)
|
||||
lib.add_animation(anim_name, anim)
|
||||
print("SkinnedPlayerModel: added '%s' (%d tracks)" % [anim_name, anim.get_track_count()])
|
||||
|
||||
func _update_skin() -> void:
|
||||
## Force skeleton skin update by recomputing all bone transforms.
|
||||
## GLTF runtime loaded models need this because AnimationPlayer's internal
|
||||
## bone pose updates don't always trigger the visual pipeline's dirty flags.
|
||||
## force_update_all_bone_transforms() recomputes the full bone hierarchy
|
||||
## and emits bone_pose_changed, which causes MeshInstance to update vertex buffers.
|
||||
skeleton.force_update_all_bone_transforms()
|
||||
|
||||
func _setup_first_person() -> void:
|
||||
# In first person, hide the head and upper body so only arms/hands/legs show
|
||||
# This creates the classic FPS arms-only view
|
||||
|
||||
Reference in New Issue
Block a user