Feat/14 movement overhaul #20
Binary file not shown.
@@ -92,9 +92,85 @@ func load_model(path: String) -> void:
|
|||||||
elif anim_list.size() > 0:
|
elif anim_list.size() > 0:
|
||||||
animation_player.play(anim_list[0])
|
animation_player.play(anim_list[0])
|
||||||
print("SkinnedPlayerModel: playing '%s'" % anim_list[0])
|
print("SkinnedPlayerModel: playing '%s'" % anim_list[0])
|
||||||
|
|
||||||
|
# If the loaded animations are too subtle (only 1 track), create better ones
|
||||||
|
if animation_player.has_animation("Idle"):
|
||||||
|
var idle_anim = animation_player.get_animation("Idle")
|
||||||
|
if idle_anim.get_track_count() <= 1:
|
||||||
|
print("SkinnedPlayerModel: Idle has too few tracks, creating a proper one")
|
||||||
|
_create_idle_animation(animation_player)
|
||||||
|
animation_player.play("Idle")
|
||||||
|
print("SkinnedPlayerModel: playing newly created Idle")
|
||||||
else:
|
else:
|
||||||
print("SkinnedPlayerModel: WARNING - no AnimationPlayer found")
|
print("SkinnedPlayerModel: WARNING - no AnimationPlayer found")
|
||||||
|
|
||||||
|
func _create_idle_animation(ap: AnimationPlayer) -> void:
|
||||||
|
# Create a proper idle animation with visible movement
|
||||||
|
var anim = Animation.new()
|
||||||
|
anim.length = 2.0
|
||||||
|
anim.loop_mode = Animation.LOOP_LINEAR
|
||||||
|
|
||||||
|
# Get the skeleton path from the scene
|
||||||
|
var skel_path = "MikuRig/Skeleton3D"
|
||||||
|
|
||||||
|
# Spine sway
|
||||||
|
_create_bone_track(anim, skel_path + ":Spine", "rotation",
|
||||||
|
[
|
||||||
|
{ "time": 0.0, "value": Vector3(0.1, 0, 0) },
|
||||||
|
{ "time": 0.5, "value": Vector3(-0.1, 0, 0) },
|
||||||
|
{ "time": 1.0, "value": Vector3(0.1, 0, 0) },
|
||||||
|
{ "time": 1.5, "value": Vector3(-0.1, 0, 0) },
|
||||||
|
{ "time": 2.0, "value": Vector3(0.1, 0, 0) },
|
||||||
|
])
|
||||||
|
|
||||||
|
# Left arm sway
|
||||||
|
_create_bone_track(anim, skel_path + ":LeftUpperArm", "rotation",
|
||||||
|
[
|
||||||
|
{ "time": 0.0, "value": Vector3(0, 0, 0.3) },
|
||||||
|
{ "time": 1.0, "value": Vector3(0, 0, 0.5) },
|
||||||
|
{ "time": 2.0, "value": Vector3(0, 0, 0.3) },
|
||||||
|
])
|
||||||
|
|
||||||
|
# Right arm sway
|
||||||
|
_create_bone_track(anim, skel_path + ":RightUpperArm", "rotation",
|
||||||
|
[
|
||||||
|
{ "time": 0.0, "value": Vector3(0, 0, -0.3) },
|
||||||
|
{ "time": 1.0, "value": Vector3(0, 0, -0.5) },
|
||||||
|
{ "time": 2.0, "value": Vector3(0, 0, -0.3) },
|
||||||
|
])
|
||||||
|
|
||||||
|
# Neck gentle movement
|
||||||
|
_create_bone_track(anim, skel_path + ":Neck", "rotation",
|
||||||
|
[
|
||||||
|
{ "time": 0.0, "value": Vector3(0.05, 0, 0) },
|
||||||
|
{ "time": 1.0, "value": Vector3(-0.05, 0, 0) },
|
||||||
|
{ "time": 2.0, "value": Vector3(0.05, 0, 0) },
|
||||||
|
])
|
||||||
|
|
||||||
|
# Hips subtle bounce
|
||||||
|
_create_bone_track(anim, skel_path + ":Hips", "position",
|
||||||
|
[
|
||||||
|
{ "time": 0.0, "value": Vector3(0, 0, 0) },
|
||||||
|
{ "time": 0.5, "value": Vector3(0, 0.02, 0) },
|
||||||
|
{ "time": 1.0, "value": Vector3(0, 0, 0) },
|
||||||
|
{ "time": 1.5, "value": Vector3(0, 0.02, 0) },
|
||||||
|
{ "time": 2.0, "value": Vector3(0, 0, 0) },
|
||||||
|
])
|
||||||
|
|
||||||
|
# Add animation to the player
|
||||||
|
ap.add_animation("Idle", anim)
|
||||||
|
print("SkinnedPlayerModel: created Idle animation with " + str(anim.get_track_count()) + " tracks")
|
||||||
|
|
||||||
|
func _create_bone_track(anim: Animation, bone_path: String, prop: String, keyframes: Array) -> void:
|
||||||
|
var track_idx = anim.add_track(Animation.TYPE_VALUE)
|
||||||
|
anim.track_set_path(track_idx, bone_path + ":" + prop)
|
||||||
|
anim.track_set_interpolation_type(track_idx, Animation.INTERPOLATION_LINEAR)
|
||||||
|
|
||||||
|
for kf in keyframes:
|
||||||
|
var time = kf["time"]
|
||||||
|
var value = kf["value"]
|
||||||
|
anim.track_insert_key(track_idx, time, value)
|
||||||
|
|
||||||
func _setup_first_person() -> void:
|
func _setup_first_person() -> void:
|
||||||
# In first person, hide the head and upper body so only arms/hands/legs show
|
# In first person, hide the head and upper body so only arms/hands/legs show
|
||||||
# This creates the classic FPS arms-only view
|
# This creates the classic FPS arms-only view
|
||||||
|
|||||||
@@ -432,7 +432,7 @@ func _spawn_player(pid: int) -> CharacterBody3D:
|
|||||||
# Miku: use the rigged GLB model with its own armature
|
# Miku: use the rigged GLB model with its own armature
|
||||||
var skinned = load("res://characters/skinned_player_model.gd").new()
|
var skinned = load("res://characters/skinned_player_model.gd").new()
|
||||||
skinned.name = "SkinnedModel"
|
skinned.name = "SkinnedModel"
|
||||||
skinned.model_path = "res://assets/characters/skins/miku_rigged_animated.glb"
|
skinned.model_path = "res://assets/characters/skins/miku_rigged_final.glb"
|
||||||
skinned.scale_factor = 1.0 # Already scaled to 1.8m in Blender
|
skinned.scale_factor = 1.0 # Already scaled to 1.8m in Blender
|
||||||
skinned.position = Vector3.ZERO # Model origin = player origin
|
skinned.position = Vector3.ZERO # Model origin = player origin
|
||||||
player.add_child(skinned)
|
player.add_child(skinned)
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ func _spawn_player(pid: int) -> CharacterBody3D:
|
|||||||
# Local player: use skinned Miku model with animations
|
# Local player: use skinned Miku model with animations
|
||||||
var skinned = load("res://characters/skinned_player_model.gd").new()
|
var skinned = load("res://characters/skinned_player_model.gd").new()
|
||||||
skinned.name = "SkinnedModel"
|
skinned.name = "SkinnedModel"
|
||||||
skinned.model_path = "res://assets/characters/skins/miku_rigged_animated.glb"
|
skinned.model_path = "res://assets/characters/skins/miku_rigged_final.glb"
|
||||||
skinned.position = Vector3(0, 0.0, 0)
|
skinned.position = Vector3(0, 0.0, 0)
|
||||||
skinned.first_person_mode = true
|
skinned.first_person_mode = true
|
||||||
player.add_child(skinned)
|
player.add_child(skinned)
|
||||||
|
|||||||
+197
-200
@@ -1,8 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
Add animations to an already-rigged Miku GLB.
|
Add animations to an already-rigged Miku GLB.
|
||||||
Uses the existing skeleton and skin weights from the original model.
|
Keyframes ALL bones for each animation to ensure proper track export.
|
||||||
Just adds new animation actions and re-exports.
|
|
||||||
"""
|
"""
|
||||||
import bpy
|
import bpy
|
||||||
import sys
|
import sys
|
||||||
@@ -17,249 +16,251 @@ input_path = argv[0] if len(argv) > 0 else ''
|
|||||||
output_path = argv[1] if len(argv) > 1 else ''
|
output_path = argv[1] if len(argv) > 1 else ''
|
||||||
|
|
||||||
if not input_path or not output_path:
|
if not input_path or not output_path:
|
||||||
print("Usage: blender --background --python add_animations.py -- <input.glb> <output.glb>")
|
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Clear scene
|
|
||||||
bpy.ops.object.select_all(action='SELECT')
|
bpy.ops.object.select_all(action='SELECT')
|
||||||
bpy.ops.object.delete()
|
bpy.ops.object.delete()
|
||||||
|
|
||||||
# Import the rigged GLB
|
|
||||||
print(f"Importing {input_path}...")
|
print(f"Importing {input_path}...")
|
||||||
bpy.ops.import_scene.gltf(filepath=input_path)
|
bpy.ops.import_scene.gltf(filepath=input_path)
|
||||||
|
|
||||||
# Find armature and mesh
|
|
||||||
armatures = [o for o in bpy.data.objects if o.type == 'ARMATURE']
|
armatures = [o for o in bpy.data.objects if o.type == 'ARMATURE']
|
||||||
meshes = [o for o in bpy.data.objects if o.type == 'MESH' and o.name != 'Icosphere']
|
meshes = [o for o in bpy.data.objects if o.type == 'MESH' and o.name != 'Icosphere']
|
||||||
|
|
||||||
if not armatures:
|
if not armatures:
|
||||||
print("ERROR: No armature found")
|
print("ERROR: No armature")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
arm = armatures[0]
|
arm = armatures[0]
|
||||||
print(f"Armature: {arm.name} ({len(arm.data.bones)} bones)")
|
print(f"Armature: {arm.name} ({len(arm.data.bones)} bones)")
|
||||||
|
|
||||||
# List existing bones
|
|
||||||
for bone in arm.data.bones:
|
|
||||||
parent = bone.parent.name if bone.parent else "ROOT"
|
|
||||||
print(f" {bone.name:25s} parent={parent:25s} head={bone.head_local} tail={bone.tail_local}")
|
|
||||||
|
|
||||||
# List meshes
|
|
||||||
for m in meshes:
|
|
||||||
print(f"Mesh: {m.name} ({len(m.data.vertices)} verts)")
|
|
||||||
if m.parent:
|
|
||||||
print(f" Parent: {m.parent.name} ({m.parent.type})")
|
|
||||||
for mod in m.modifiers:
|
|
||||||
if mod.type == 'ARMATURE':
|
|
||||||
print(f" Armature mod: {mod.object.name if mod.object else 'None'}")
|
|
||||||
|
|
||||||
# List existing animations
|
|
||||||
print(f"\nExisting animations: {len(bpy.data.actions)}")
|
|
||||||
for action in bpy.data.actions:
|
|
||||||
print(f" {action.name} ({action.frame_range[0]:.0f}-{action.frame_range[1]:.0f})")
|
|
||||||
|
|
||||||
# Check if armature has animation_data
|
|
||||||
if arm.animation_data:
|
|
||||||
print(f"\nArmature animation_data: action={arm.animation_data.action}")
|
|
||||||
print(f" NLA tracks: {len(arm.animation_data.nla_tracks)}")
|
|
||||||
for track in arm.animation_data.nla_tracks:
|
|
||||||
print(f" Track: {track.name}")
|
|
||||||
for strip in track.strips:
|
|
||||||
print(f" Strip: {strip.name} ({strip.frame_start}-{strip.frame_end})")
|
|
||||||
|
|
||||||
# Enter pose mode
|
|
||||||
bpy.context.view_layer.objects.active = arm
|
bpy.context.view_layer.objects.active = arm
|
||||||
bpy.ops.object.mode_set(mode='POSE')
|
bpy.ops.object.mode_set(mode='POSE')
|
||||||
|
|
||||||
# Get bone names for reference
|
bone_names = [b.name for b in arm.data.bones if b.name != "neutral_bone"]
|
||||||
bone_names = [b.name for b in arm.data.bones]
|
print(f"Bones: {bone_names}")
|
||||||
print(f"\nBone names: {bone_names}")
|
|
||||||
|
|
||||||
# Define animation keyframes using the EXISTING bone names
|
def create_animation(name, frame_end, keyframes_func):
|
||||||
# The bones are: Hips, Spine, Chest, Neck, Head, LeftUpperArm, LeftLowerArm, LeftHand,
|
"""Create an animation. keyframes_func(frame, bone_name) -> (loc, rot) or None."""
|
||||||
# RightUpperArm, RightLowerArm, RightHand, LeftUpperLeg, LeftLowerLeg, LeftFoot,
|
print(f" Creating {name}...")
|
||||||
# RightUpperLeg, RightLowerLeg, RightFoot, neutral_bone
|
action = bpy.data.actions.new(name=name)
|
||||||
|
arm.animation_data.action = action
|
||||||
|
action.frame_range = (1, frame_end)
|
||||||
|
|
||||||
|
# Keyframe ALL bones at every frame to ensure tracks are exported
|
||||||
|
for frame in range(1, frame_end + 1):
|
||||||
|
for bone_name in bone_names:
|
||||||
|
bone = arm.pose.bones[bone_name]
|
||||||
|
result = keyframes_func(frame, bone_name)
|
||||||
|
if result is None:
|
||||||
|
# Default: no movement
|
||||||
|
bone.location = (0, 0, 0)
|
||||||
|
bone.rotation_euler = (0, 0, 0)
|
||||||
|
else:
|
||||||
|
loc, rot = result
|
||||||
|
bone.location = loc if loc else (0, 0, 0)
|
||||||
|
bone.rotation_mode = 'XYZ'
|
||||||
|
bone.rotation_euler = rot if rot else (0, 0, 0)
|
||||||
|
|
||||||
|
bone.keyframe_insert(data_path="location", frame=frame)
|
||||||
|
bone.keyframe_insert(data_path="rotation_euler", frame=frame)
|
||||||
|
|
||||||
|
print(f" {name}: {frame_end} frames, {len(bone_names)} bones")
|
||||||
|
|
||||||
def set_rest():
|
# Idle: visible swaying (large enough to not be optimized away)
|
||||||
for bone in arm.pose.bones:
|
def idle_kf(frame, bone_name):
|
||||||
bone.location = (0, 0, 0)
|
t = (frame - 1) / 30.0
|
||||||
bone.rotation_euler = (0, 0, 0)
|
phase = t * 2 * math.pi
|
||||||
bone.scale = (1, 1, 1)
|
if bone_name == "Spine":
|
||||||
|
return None, (math.sin(phase) * 0.5, 0, 0)
|
||||||
|
elif bone_name == "LeftUpperArm":
|
||||||
|
return None, (0, 0, 0.5 + math.sin(phase) * 0.3)
|
||||||
|
elif bone_name == "RightUpperArm":
|
||||||
|
return None, (0, 0, -0.5 - math.sin(phase) * 0.3)
|
||||||
|
elif bone_name == "Neck":
|
||||||
|
return None, (math.sin(phase) * 0.3, 0, 0)
|
||||||
|
elif bone_name == "LeftLowerArm":
|
||||||
|
return None, (-0.5, 0, 0)
|
||||||
|
elif bone_name == "RightLowerArm":
|
||||||
|
return None, (-0.5, 0, 0)
|
||||||
|
return None, None
|
||||||
|
|
||||||
def key(bone_name, frame, loc=None, rot=None):
|
create_animation("Idle", 30, idle_kf)
|
||||||
bone = arm.pose.bones.get(bone_name)
|
|
||||||
if not bone:
|
|
||||||
return
|
|
||||||
if loc:
|
|
||||||
bone.location = loc
|
|
||||||
bone.keyframe_insert(data_path="location", frame=frame)
|
|
||||||
if rot:
|
|
||||||
bone.rotation_mode = 'XYZ'
|
|
||||||
bone.rotation_euler = rot
|
|
||||||
bone.keyframe_insert(data_path="rotation_euler", frame=frame)
|
|
||||||
|
|
||||||
# Idle
|
|
||||||
print("\nCreating Idle...")
|
|
||||||
action_idle = bpy.data.actions.new(name="Idle")
|
|
||||||
arm.animation_data.action = action_idle
|
|
||||||
action_idle.frame_range = (1, 30)
|
|
||||||
set_rest()
|
|
||||||
|
|
||||||
key("Spine", 1, rot=(0.02, 0, 0))
|
|
||||||
key("Spine", 15, rot=(-0.02, 0, 0))
|
|
||||||
key("Spine", 30, rot=(0.02, 0, 0))
|
|
||||||
key("LeftUpperArm", 1, rot=(0, 0, 0.05))
|
|
||||||
key("LeftUpperArm", 15, rot=(0, 0, -0.05))
|
|
||||||
key("LeftUpperArm", 30, rot=(0, 0, 0.05))
|
|
||||||
key("RightUpperArm", 1, rot=(0, 0, -0.05))
|
|
||||||
key("RightUpperArm", 15, rot=(0, 0, 0.05))
|
|
||||||
key("RightUpperArm", 30, rot=(0, 0, -0.05))
|
|
||||||
|
|
||||||
# Walk
|
# Walk
|
||||||
print("Creating Walk...")
|
def walk_kf(frame, bone_name):
|
||||||
action_walk = bpy.data.actions.new(name="Walk")
|
t = (frame - 1) / 24.0
|
||||||
arm.animation_data.action = action_walk
|
|
||||||
action_walk.frame_range = (1, 24)
|
|
||||||
set_rest()
|
|
||||||
|
|
||||||
for f in range(1, 25):
|
|
||||||
t = (f - 1) / 24
|
|
||||||
phase = t * 2 * math.pi
|
phase = t * 2 * math.pi
|
||||||
key("Hips", f, loc=(0, 0, abs(math.sin(phase * 2)) * 0.01))
|
if bone_name == "Hips":
|
||||||
key("LeftUpperLeg", f, rot=(math.sin(phase) * 0.4, 0, 0))
|
return (0, 0, abs(math.sin(phase * 2)) * 0.02), None
|
||||||
key("LeftLowerLeg", f, rot=(max(0, -math.sin(phase) * 0.3 + 0.2), 0, 0))
|
elif bone_name == "LeftUpperLeg":
|
||||||
key("RightUpperLeg", f, rot=(math.sin(phase + math.pi) * 0.4, 0, 0))
|
return None, (math.sin(phase) * 0.5, 0, 0)
|
||||||
key("RightLowerLeg", f, rot=(max(0, -math.sin(phase + math.pi) * 0.3 + 0.2), 0, 0))
|
elif bone_name == "LeftLowerLeg":
|
||||||
key("LeftUpperArm", f, rot=(-math.sin(phase) * 0.3, 0, 0.05))
|
return None, (max(0, -math.sin(phase) * 0.4 + 0.3), 0, 0)
|
||||||
key("LeftLowerArm", f, rot=(-0.3 + max(0, math.sin(phase) * 0.2), 0, 0))
|
elif bone_name == "RightUpperLeg":
|
||||||
key("RightUpperArm", f, rot=(-math.sin(phase + math.pi) * 0.3, 0, -0.05))
|
return None, (math.sin(phase + math.pi) * 0.5, 0, 0)
|
||||||
key("RightLowerArm", f, rot=(-0.3 + max(0, math.sin(phase + math.pi) * 0.2), 0, 0))
|
elif bone_name == "RightLowerLeg":
|
||||||
key("Spine", f, rot=(0, math.sin(phase) * 0.05, 0))
|
return None, (max(0, -math.sin(phase + math.pi) * 0.4 + 0.3), 0, 0)
|
||||||
|
elif bone_name == "LeftUpperArm":
|
||||||
|
return None, (-math.sin(phase) * 0.4, 0, 0.1)
|
||||||
|
elif bone_name == "LeftLowerArm":
|
||||||
|
return None, (-0.5 + max(0, math.sin(phase) * 0.3), 0, 0)
|
||||||
|
elif bone_name == "RightUpperArm":
|
||||||
|
return None, (-math.sin(phase + math.pi) * 0.4, 0, -0.1)
|
||||||
|
elif bone_name == "RightLowerArm":
|
||||||
|
return None, (-0.5 + max(0, math.sin(phase + math.pi) * 0.3), 0, 0)
|
||||||
|
elif bone_name == "Spine":
|
||||||
|
return None, (0, math.sin(phase) * 0.08, 0)
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
create_animation("Walk", 24, walk_kf)
|
||||||
|
|
||||||
# Run
|
# Run
|
||||||
print("Creating Run...")
|
def run_kf(frame, bone_name):
|
||||||
action_run = bpy.data.actions.new(name="Run")
|
t = (frame - 1) / 20.0
|
||||||
arm.animation_data.action = action_run
|
|
||||||
action_run.frame_range = (1, 20)
|
|
||||||
set_rest()
|
|
||||||
|
|
||||||
for f in range(1, 21):
|
|
||||||
t = (f - 1) / 20
|
|
||||||
phase = t * 2 * math.pi
|
phase = t * 2 * math.pi
|
||||||
key("Hips", f, loc=(0, 0, abs(math.sin(phase * 2)) * 0.03))
|
if bone_name == "Hips":
|
||||||
key("LeftUpperLeg", f, rot=(math.sin(phase) * 0.8, 0, 0))
|
return (0, 0, abs(math.sin(phase * 2)) * 0.04), None
|
||||||
key("LeftLowerLeg", f, rot=(max(0.1, -math.sin(phase) * 0.6 + 0.3), 0, 0))
|
elif bone_name == "LeftUpperLeg":
|
||||||
key("RightUpperLeg", f, rot=(math.sin(phase + math.pi) * 0.8, 0, 0))
|
return None, (math.sin(phase) * 0.9, 0, 0)
|
||||||
key("RightLowerLeg", f, rot=(max(0.1, -math.sin(phase + math.pi) * 0.6 + 0.3), 0, 0))
|
elif bone_name == "LeftLowerLeg":
|
||||||
key("LeftUpperArm", f, rot=(-math.sin(phase) * 0.7, 0, 0.1))
|
return None, (max(0.1, -math.sin(phase) * 0.7 + 0.4), 0, 0)
|
||||||
key("LeftLowerArm", f, rot=(-1.0 + max(0, math.sin(phase) * 0.3), 0, 0))
|
elif bone_name == "RightUpperLeg":
|
||||||
key("RightUpperArm", f, rot=(-math.sin(phase + math.pi) * 0.7, 0, -0.1))
|
return None, (math.sin(phase + math.pi) * 0.9, 0, 0)
|
||||||
key("RightLowerArm", f, rot=(-1.0 + max(0, math.sin(phase + math.pi) * 0.3), 0, 0))
|
elif bone_name == "RightLowerLeg":
|
||||||
key("Spine", f, rot=(0.1, math.sin(phase) * 0.08, 0))
|
return None, (max(0.1, -math.sin(phase + math.pi) * 0.7 + 0.4), 0, 0)
|
||||||
|
elif bone_name == "LeftUpperArm":
|
||||||
|
return None, (-math.sin(phase) * 0.8, 0, 0.15)
|
||||||
|
elif bone_name == "LeftLowerArm":
|
||||||
|
return None, (-1.2 + max(0, math.sin(phase) * 0.4), 0, 0)
|
||||||
|
elif bone_name == "RightUpperArm":
|
||||||
|
return None, (-math.sin(phase + math.pi) * 0.8, 0, -0.15)
|
||||||
|
elif bone_name == "RightLowerArm":
|
||||||
|
return None, (-1.2 + max(0, math.sin(phase + math.pi) * 0.4), 0, 0)
|
||||||
|
elif bone_name == "Spine":
|
||||||
|
return None, (0.15, math.sin(phase) * 0.1, 0)
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
create_animation("Run", 20, run_kf)
|
||||||
|
|
||||||
# Jump
|
# Jump
|
||||||
print("Creating Jump...")
|
def jump_kf(frame, bone_name):
|
||||||
action_jump = bpy.data.actions.new(name="Jump")
|
if frame <= 5:
|
||||||
arm.animation_data.action = action_jump
|
t = (frame - 1) / 4.0
|
||||||
action_jump.frame_range = (1, 20)
|
if bone_name == "Hips":
|
||||||
set_rest()
|
return (0, 0, -t * 0.3), None
|
||||||
|
elif bone_name in ("LeftUpperLeg", "RightUpperLeg"):
|
||||||
|
return None, (-t * 0.7, 0, 0)
|
||||||
|
elif bone_name in ("LeftLowerLeg", "RightLowerLeg"):
|
||||||
|
return None, (t * 1.2, 0, 0)
|
||||||
|
elif bone_name == "LeftUpperArm":
|
||||||
|
return None, (0, 0, -t * 0.8)
|
||||||
|
elif bone_name == "RightUpperArm":
|
||||||
|
return None, (0, 0, t * 0.8)
|
||||||
|
elif frame <= 8:
|
||||||
|
t = (frame - 5) / 2.0
|
||||||
|
if bone_name == "Hips":
|
||||||
|
return (0, 0, -0.3 + t * 0.3), None
|
||||||
|
elif bone_name in ("LeftUpperLeg", "RightUpperLeg"):
|
||||||
|
return None, (-0.7 + t * 0.7, 0, 0)
|
||||||
|
elif bone_name in ("LeftLowerLeg", "RightLowerLeg"):
|
||||||
|
return None, (1.2 - t * 0.4, 0, 0)
|
||||||
|
elif bone_name == "LeftUpperArm":
|
||||||
|
return None, (-t * 2.5, 0, -0.8 - t)
|
||||||
|
elif bone_name == "RightUpperArm":
|
||||||
|
return None, (-t * 2.5, 0, 0.8 + t)
|
||||||
|
elif frame <= 15:
|
||||||
|
if bone_name == "LeftUpperArm":
|
||||||
|
return None, (-2.5, 0, -1.8)
|
||||||
|
elif bone_name == "RightUpperArm":
|
||||||
|
return None, (-2.5, 0, 1.8)
|
||||||
|
elif bone_name in ("LeftLowerArm", "RightLowerArm"):
|
||||||
|
return None, (-0.3, 0, 0)
|
||||||
|
elif bone_name == "LeftUpperLeg":
|
||||||
|
return None, (0.3, 0, 0)
|
||||||
|
elif bone_name == "LeftLowerLeg":
|
||||||
|
return None, (0.6, 0, 0)
|
||||||
|
elif bone_name == "RightUpperLeg":
|
||||||
|
return None, (0.2, 0, 0)
|
||||||
|
elif bone_name == "RightLowerLeg":
|
||||||
|
return None, (0.4, 0, 0)
|
||||||
|
else:
|
||||||
|
t = (frame - 15) / 4.0
|
||||||
|
if bone_name == "Hips":
|
||||||
|
return (0, 0, -t * 0.3), None
|
||||||
|
elif bone_name in ("LeftUpperLeg", "RightUpperLeg"):
|
||||||
|
return None, (-t * 0.6, 0, 0)
|
||||||
|
elif bone_name in ("LeftLowerLeg", "RightLowerLeg"):
|
||||||
|
return None, (t * 1.0, 0, 0)
|
||||||
|
elif bone_name == "LeftUpperArm":
|
||||||
|
return None, (-2.5 + t * 2.5, 0, -1.8 + t * 1.8)
|
||||||
|
elif bone_name == "RightUpperArm":
|
||||||
|
return None, (-2.5 + t * 2.5, 0, 1.8 - t * 1.8)
|
||||||
|
return None, None
|
||||||
|
|
||||||
for f in range(1, 6):
|
create_animation("Jump", 20, jump_kf)
|
||||||
t = (f - 1) / 4
|
|
||||||
key("Hips", f, loc=(0, 0, -t * 0.2))
|
|
||||||
key("LeftUpperLeg", f, rot=(-t * 0.6, 0, 0))
|
|
||||||
key("LeftLowerLeg", f, rot=(t * 1.0, 0, 0))
|
|
||||||
key("RightUpperLeg", f, rot=(-t * 0.6, 0, 0))
|
|
||||||
key("RightLowerLeg", f, rot=(t * 1.0, 0, 0))
|
|
||||||
key("LeftUpperArm", f, rot=(0, 0, -t * 0.5))
|
|
||||||
key("RightUpperArm", f, rot=(0, 0, t * 0.5))
|
|
||||||
|
|
||||||
for f in range(6, 9):
|
|
||||||
t = (f - 6) / 2
|
|
||||||
key("Hips", f, loc=(0, 0, -0.2 + t * 0.2))
|
|
||||||
key("LeftUpperLeg", f, rot=(-0.6 + t * 0.6, 0, 0))
|
|
||||||
key("LeftLowerLeg", f, rot=(1.0 - t * 0.3, 0, 0))
|
|
||||||
key("RightUpperLeg", f, rot=(-0.6 + t * 0.6, 0, 0))
|
|
||||||
key("RightLowerLeg", f, rot=(1.0 - t * 0.3, 0, 0))
|
|
||||||
key("LeftUpperArm", f, rot=(-t * 2.0, 0, -0.5 - t))
|
|
||||||
key("RightUpperArm", f, rot=(-t * 2.0, 0, 0.5 + t))
|
|
||||||
|
|
||||||
for f in range(9, 16):
|
|
||||||
key("LeftUpperArm", f, rot=(-2.0, 0, -1.5))
|
|
||||||
key("RightUpperArm", f, rot=(-2.0, 0, 1.5))
|
|
||||||
key("LeftLowerArm", f, rot=(-0.2, 0, 0))
|
|
||||||
key("RightLowerArm", f, rot=(-0.2, 0, 0))
|
|
||||||
key("LeftUpperLeg", f, rot=(0.2, 0, 0))
|
|
||||||
key("LeftLowerLeg", f, rot=(0.5, 0, 0))
|
|
||||||
key("RightUpperLeg", f, rot=(0.1, 0, 0))
|
|
||||||
key("RightLowerLeg", f, rot=(0.3, 0, 0))
|
|
||||||
|
|
||||||
for f in range(16, 21):
|
|
||||||
t = (f - 16) / 4
|
|
||||||
key("Hips", f, loc=(0, 0, -t * 0.2))
|
|
||||||
key("LeftUpperLeg", f, rot=(-t * 0.5, 0, 0))
|
|
||||||
key("LeftLowerLeg", f, rot=(t * 0.8, 0, 0))
|
|
||||||
key("RightUpperLeg", f, rot=(-t * 0.5, 0, 0))
|
|
||||||
key("RightLowerLeg", f, rot=(t * 0.8, 0, 0))
|
|
||||||
key("LeftUpperArm", f, rot=(-2.0 + t * 2.0, 0, -1.5 + t * 1.5))
|
|
||||||
key("RightUpperArm", f, rot=(-2.0 + t * 2.0, 0, 1.5 - t * 1.5))
|
|
||||||
|
|
||||||
# Crouch
|
# Crouch
|
||||||
print("Creating Crouch...")
|
def crouch_kf(frame, bone_name):
|
||||||
action_crouch = bpy.data.actions.new(name="Crouch")
|
if bone_name == "Hips":
|
||||||
arm.animation_data.action = action_crouch
|
return (0, 0, -0.5), None
|
||||||
action_crouch.frame_range = (1, 1)
|
elif bone_name == "Spine":
|
||||||
set_rest()
|
return None, (0.4, 0, 0)
|
||||||
|
elif bone_name in ("LeftUpperLeg", "RightUpperLeg"):
|
||||||
|
return None, (-0.9, 0, 0)
|
||||||
|
elif bone_name in ("LeftLowerLeg", "RightLowerLeg"):
|
||||||
|
return None, (1.5, 0, 0)
|
||||||
|
elif bone_name == "LeftUpperArm":
|
||||||
|
return None, (-0.6, 0, 0.3)
|
||||||
|
elif bone_name == "RightUpperArm":
|
||||||
|
return None, (-0.6, 0, -0.3)
|
||||||
|
return None, None
|
||||||
|
|
||||||
key("Hips", 1, loc=(0, 0, -0.4))
|
create_animation("Crouch", 1, crouch_kf)
|
||||||
key("Spine", 1, rot=(0.3, 0, 0))
|
|
||||||
key("LeftUpperLeg", 1, rot=(-0.8, 0, 0))
|
|
||||||
key("LeftLowerLeg", 1, rot=(1.4, 0, 0))
|
|
||||||
key("RightUpperLeg", 1, rot=(-0.8, 0, 0))
|
|
||||||
key("RightLowerLeg", 1, rot=(1.4, 0, 0))
|
|
||||||
key("LeftUpperArm", 1, rot=(-0.5, 0, 0.2))
|
|
||||||
key("RightUpperArm", 1, rot=(-0.5, 0, -0.2))
|
|
||||||
|
|
||||||
# Death
|
# Death
|
||||||
print("Creating Death...")
|
def death_kf(frame, bone_name):
|
||||||
action_death = bpy.data.actions.new(name="Death")
|
if frame <= 14:
|
||||||
arm.animation_data.action = action_death
|
t = (frame - 1) / 13.0
|
||||||
action_death.frame_range = (1, 30)
|
if bone_name == "Hips":
|
||||||
set_rest()
|
return (0, 0, -t * 0.6), (-t * 1.3, 0, t * 0.3)
|
||||||
|
elif bone_name == "Spine":
|
||||||
|
return None, (-t * 0.6, t * 0.4, 0)
|
||||||
|
elif bone_name == "Chest":
|
||||||
|
return None, (-t * 0.5, 0, 0)
|
||||||
|
elif bone_name in ("LeftUpperArm", "RightUpperArm"):
|
||||||
|
sign = -1 if "Left" in bone_name else 1
|
||||||
|
return None, (t * 2.2, 0, sign * -t * 0.6)
|
||||||
|
elif bone_name in ("LeftUpperLeg", "RightUpperLeg"):
|
||||||
|
return None, (-t * 0.4, 0, 0)
|
||||||
|
else:
|
||||||
|
t = (frame - 14) / 15.0
|
||||||
|
if bone_name == "Hips":
|
||||||
|
return (0, 0, -0.6 - t * 0.3), (-1.3 - t * 0.6, 0.8 * (1 - t), 0.3 + t * 0.4)
|
||||||
|
elif bone_name == "Spine":
|
||||||
|
return None, (-0.8 - t * 0.4, 0.5 * (1 - t), 0)
|
||||||
|
elif bone_name == "Chest":
|
||||||
|
return None, (-0.7 - t * 0.3, 0, 0)
|
||||||
|
elif bone_name == "LeftHand":
|
||||||
|
return None, (t * 0.4, t * 0.6, -0.8 - t * 0.6)
|
||||||
|
elif bone_name == "RightHand":
|
||||||
|
return None, (t * 0.5, -t * 0.7, 0.9 + t * 0.5)
|
||||||
|
return None, None
|
||||||
|
|
||||||
for f in range(1, 15):
|
create_animation("Death", 30, death_kf)
|
||||||
t = (f - 1) / 13
|
|
||||||
key("Hips", f, loc=(0, 0, -t * 0.5), rot=(-t * 1.2, 0, t * 0.2))
|
|
||||||
key("Spine", f, rot=(-t * 0.5, t * 0.3, 0))
|
|
||||||
key("Chest", f, rot=(-t * 0.4, 0, 0))
|
|
||||||
key("LeftUpperArm", f, rot=(t * 2.0, 0, -t * 0.5))
|
|
||||||
key("RightUpperArm", f, rot=(t * 2.0, 0, t * 0.5))
|
|
||||||
key("LeftUpperLeg", f, rot=(-t * 0.3, 0, 0))
|
|
||||||
key("RightUpperLeg", f, rot=(-t * 0.3, 0, 0))
|
|
||||||
|
|
||||||
for f in range(15, 31):
|
|
||||||
t = (f - 15) / 15
|
|
||||||
key("Hips", f, loc=(0, 0, -0.5 - t * 0.2), rot=(-1.2 - t * 0.5, 0.8 * (1 - t), 0.2 + t * 0.3))
|
|
||||||
key("Spine", f, rot=(-0.7 - t * 0.3, 0.5 * (1 - t), 0))
|
|
||||||
key("Chest", f, rot=(-0.6 - t * 0.2, 0, 0))
|
|
||||||
key("LeftHand", f, rot=(t * 0.3, t * 0.5, -0.7 - t * 0.5))
|
|
||||||
key("RightHand", f, rot=(t * 0.4, -t * 0.6, 0.8 + t * 0.4))
|
|
||||||
|
|
||||||
print(f"\nTotal animations: {len(bpy.data.actions)}")
|
print(f"\nTotal animations: {len(bpy.data.actions)}")
|
||||||
for action in bpy.data.actions:
|
for action in bpy.data.actions:
|
||||||
print(f" {action.name} ({action.frame_range[0]:.0f}-{action.frame_range[1]:.0f})")
|
print(f" {action.name} ({action.frame_range[0]:.0f}-{action.frame_range[1]:.0f})")
|
||||||
|
|
||||||
# Switch to object mode
|
|
||||||
bpy.ops.object.mode_set(mode='OBJECT')
|
bpy.ops.object.mode_set(mode='OBJECT')
|
||||||
|
|
||||||
# Select all for export
|
|
||||||
bpy.ops.object.select_all(action='DESELECT')
|
bpy.ops.object.select_all(action='DESELECT')
|
||||||
arm.select_set(True)
|
arm.select_set(True)
|
||||||
for m in meshes:
|
for m in meshes:
|
||||||
m.select_set(True)
|
m.select_set(True)
|
||||||
bpy.context.view_layer.objects.active = arm
|
bpy.context.view_layer.objects.active = arm
|
||||||
|
|
||||||
# Export as GLB
|
|
||||||
print(f"\nExporting to {output_path}...")
|
print(f"\nExporting to {output_path}...")
|
||||||
bpy.ops.export_scene.gltf(
|
bpy.ops.export_scene.gltf(
|
||||||
filepath=output_path,
|
filepath=output_path,
|
||||||
@@ -272,9 +273,7 @@ bpy.ops.export_scene.gltf(
|
|||||||
)
|
)
|
||||||
print(f"Exported! {os.path.getsize(output_path)} bytes")
|
print(f"Exported! {os.path.getsize(output_path)} bytes")
|
||||||
|
|
||||||
# Patch skeleton reference
|
import struct, json
|
||||||
import struct
|
|
||||||
import json
|
|
||||||
|
|
||||||
with open(output_path, 'rb') as f:
|
with open(output_path, 'rb') as f:
|
||||||
data = f.read()
|
data = f.read()
|
||||||
@@ -293,9 +292,7 @@ for i, n in enumerate(nodes):
|
|||||||
if skeleton_idx is not None:
|
if skeleton_idx is not None:
|
||||||
skins = gltf.get('skins', [])
|
skins = gltf.get('skins', [])
|
||||||
if skins:
|
if skins:
|
||||||
old = skins[0].get('skeleton')
|
|
||||||
skins[0]['skeleton'] = skeleton_idx
|
skins[0]['skeleton'] = skeleton_idx
|
||||||
print(f"Set skeleton from {old} to {skeleton_idx}")
|
|
||||||
|
|
||||||
new_json = json.dumps(gltf, separators=(',', ':')).encode('utf-8')
|
new_json = json.dumps(gltf, separators=(',', ':')).encode('utf-8')
|
||||||
while len(new_json) % 4 != 0:
|
while len(new_json) % 4 != 0:
|
||||||
@@ -316,5 +313,5 @@ new_data += data[bin_start:]
|
|||||||
with open(output_path, 'wb') as f:
|
with open(output_path, 'wb') as f:
|
||||||
f.write(new_data)
|
f.write(new_data)
|
||||||
|
|
||||||
print(f"Final: {os.path.getsize(output_path)} bytes, {len(gltf.get('animations', []))} animations")
|
print(f"Final: {os.path.getsize(output_path)} bytes")
|
||||||
print("=== Complete ===")
|
print("=== Complete ===")
|
||||||
|
|||||||
Reference in New Issue
Block a user