freemode fishing tekton autoplay + static tekton throw fix
- Added tekton_fishing_autoplay.gd: plays both GLB animations simultaneously, looped - Attached autoplay script to 3 fishing tekton instances in freemode.tscn - Fixed static tekton throw: faces throw direction + plays throw animation - Fixed AnimationPlayer path in tekton.gd for static turrets - Fixed animation names (tekton_throw_tile -> ted_bones_001|Tekton Throwing Tiles|Anima_Layer) - Fixed static_tekton_controller.gd idle resume - Rebuilt animation-pack.res with new animations (holding_1, put_1, stun_1, etc.) - Fixed GutBottomPanel.tscn broken UID
This commit is contained in:
+153
-133
@@ -1,106 +1,47 @@
|
||||
extends SceneTree
|
||||
|
||||
## Headless script: loads animation-0.glb, extracts animations with
|
||||
## original Blender bone names, applies rest-pose correction per bone,
|
||||
## and saves animation-pack.res.
|
||||
## Build animation-pack.res from animation-0.glb (original Blender bone names).
|
||||
##
|
||||
## Usage: godot --headless --path <project> --script tools/build_animation_pack.gd
|
||||
## Usage:
|
||||
## godot --headless --path <project> --script tools/build_animation_pack.gd
|
||||
##
|
||||
## No rest-pose correction — animations are copied verbatim from the source GLB.
|
||||
## All characters share the same rig structure (identical 25 bones), so the same
|
||||
## animation works on all of them.
|
||||
|
||||
const SOURCE_GLB := "res://assets/characters/animation-0.glb"
|
||||
const TARGET_SKELETON_GLB := "res://assets/characters/Bob.glb"
|
||||
const OUTPUT_PATH := "res://assets/characters/animations/animation-pack.res"
|
||||
|
||||
var _corrections: Dictionary = {} # bone_name -> Quaternion correction
|
||||
var _correction_src_rest: Dictionary = {} # "Skeleton3D:bone" -> Vector3
|
||||
var _correction_tgt_rest: Dictionary = {} # "Skeleton3D:bone" -> Vector3
|
||||
|
||||
func _init() -> void:
|
||||
print("[build_animation_pack] Starting...")
|
||||
print("[build_animation_pack] Starting build...")
|
||||
|
||||
# Step 1: Load source skeleton (animation-0.glb)
|
||||
var gltf_doc := GLTFDocument.new()
|
||||
var gltf_state := GLTFState.new()
|
||||
var err := gltf_doc.append_from_file(
|
||||
ProjectSettings.globalize_path(SOURCE_GLB), gltf_state
|
||||
# Step 1: Load source animations
|
||||
var src_doc := GLTFDocument.new()
|
||||
var src_state := GLTFState.new()
|
||||
var err := src_doc.append_from_file(
|
||||
ProjectSettings.globalize_path(SOURCE_GLB), src_state
|
||||
)
|
||||
if err != OK:
|
||||
push_error("[build_animation_pack] Failed to load source GLB: error %d" % err)
|
||||
quit(1)
|
||||
return
|
||||
|
||||
var src_scene := gltf_doc.generate_scene(gltf_state)
|
||||
var src_scene: Node = src_doc.generate_scene(src_state)
|
||||
if not src_scene:
|
||||
push_error("[build_animation_pack] source generate_scene returned null")
|
||||
push_error("[build_animation_pack] Failed to generate source scene")
|
||||
quit(1)
|
||||
return
|
||||
|
||||
var src_skel: Skeleton3D = src_scene.find_child("Skeleton3D", true, false)
|
||||
if not src_skel:
|
||||
push_error("[build_animation_pack] No Skeleton3D in source")
|
||||
src_scene.queue_free()
|
||||
quit(1)
|
||||
return
|
||||
|
||||
# Step 2: Load target skeleton (Bob, raw)
|
||||
var tgt_gltf_doc := GLTFDocument.new()
|
||||
var tgt_gltf_state := GLTFState.new()
|
||||
err = tgt_gltf_doc.append_from_file(
|
||||
ProjectSettings.globalize_path(TARGET_SKELETON_GLB), tgt_gltf_state
|
||||
)
|
||||
if err != OK:
|
||||
push_error("[build_animation_pack] Failed to load target GLB: error %d" % err)
|
||||
src_scene.queue_free()
|
||||
quit(1)
|
||||
return
|
||||
var tgt_scene: Node = tgt_gltf_doc.generate_scene(tgt_gltf_state)
|
||||
if not tgt_scene:
|
||||
push_error("[build_animation_pack] target generate_scene returned null")
|
||||
src_scene.queue_free()
|
||||
quit(1)
|
||||
return
|
||||
var tgt_skel: Skeleton3D = tgt_scene.find_child("Skeleton3D", true, false)
|
||||
if not tgt_skel:
|
||||
push_error("[build_animation_pack] No Skeleton3D in target")
|
||||
src_scene.queue_free()
|
||||
tgt_scene.queue_free()
|
||||
quit(1)
|
||||
return
|
||||
|
||||
# Step 3: Build per-bone correction quaternions (original bone names, 1:1)
|
||||
var src_bone_index: Dictionary = {}
|
||||
for i in src_skel.get_bone_count():
|
||||
src_bone_index[src_skel.get_bone_name(i)] = i
|
||||
|
||||
for i in tgt_skel.get_bone_count():
|
||||
var bone_name: String = tgt_skel.get_bone_name(i)
|
||||
if not src_bone_index.has(bone_name):
|
||||
continue
|
||||
var src_idx: int = src_bone_index[bone_name]
|
||||
var src_rest: Transform3D = src_skel.get_bone_rest(src_idx)
|
||||
var tgt_rest: Transform3D = tgt_skel.get_bone_rest(i)
|
||||
# correction = tgt_rest.rot * src_rest.rot.inverse()
|
||||
var correction: Quaternion = Quaternion(tgt_rest.basis) * Quaternion(src_rest.basis.inverse())
|
||||
_corrections[bone_name] = correction
|
||||
_correction_src_rest["Character/Skeleton3D:%s" % bone_name] = src_rest.origin
|
||||
_correction_tgt_rest["Character/Skeleton3D:%s" % bone_name] = tgt_rest.origin
|
||||
|
||||
print("[build_animation_pack] Built %d rest-pose corrections" % _corrections.size())
|
||||
|
||||
# Step 4: Extract animations with original bone names
|
||||
# We use the generated scene's AnimationPlayer, which contains the
|
||||
# already-stripped tracks (immutable ones removed by GLTF importer).
|
||||
# Step 2: Extract animations
|
||||
var anim_player: AnimationPlayer = src_scene.find_child("AnimationPlayer", true, false)
|
||||
if not anim_player:
|
||||
push_error("[build_animation_pack] No AnimationPlayer in source")
|
||||
src_scene.queue_free()
|
||||
tgt_scene.queue_free()
|
||||
quit(1)
|
||||
return
|
||||
|
||||
var out_lib := AnimationLibrary.new()
|
||||
var count := 0
|
||||
var total_tracks := 0
|
||||
var corrected_keys := 0
|
||||
|
||||
for lib_name in anim_player.get_animation_library_list():
|
||||
var src_lib := anim_player.get_animation_library(lib_name)
|
||||
@@ -110,87 +51,166 @@ func _init() -> void:
|
||||
continue
|
||||
|
||||
var new_anim := Animation.new()
|
||||
new_anim.length = src_anim.length
|
||||
new_anim.loop_mode = src_anim.loop_mode
|
||||
|
||||
# holding_1 must loop while player carries tekton
|
||||
if anim_name == "holding_1":
|
||||
new_anim.loop_mode = Animation.LOOP_LINEAR
|
||||
|
||||
var max_time := 0.0
|
||||
for i in src_anim.get_track_count():
|
||||
var path_str: String = str(src_anim.track_get_path(i))
|
||||
var track_type: int = src_anim.track_get_type(i)
|
||||
|
||||
# Parse "retarget/Skeleton3D:bone_name" -> bone_name
|
||||
var bone_name := ""
|
||||
if ":" in path_str:
|
||||
bone_name = path_str.split(":")[1]
|
||||
else:
|
||||
# Non-bone track: skip GLB root "retarget"
|
||||
if "retarget" in path_str:
|
||||
continue
|
||||
_copy_track(src_anim, i, new_anim, path_str, Quaternion.IDENTITY, track_type)
|
||||
# Skip the GLB root "retarget" node
|
||||
if "retarget" in path_str and ":" not in path_str:
|
||||
continue
|
||||
|
||||
# Skip the GLB scene root "retarget" — not a real bone.
|
||||
if bone_name == "retarget":
|
||||
continue
|
||||
# Convert track path: "retarget/Skeleton3D:bone" -> "Character/Skeleton3D:bone"
|
||||
if "retarget/Skeleton3D:" in path_str:
|
||||
path_str = path_str.replace("retarget/Skeleton3D:", "Character/Skeleton3D:")
|
||||
|
||||
# Use original bone name with character-rig-relative path.
|
||||
# All characters have rig root renamed to "Character" via GLB modification,
|
||||
# so the path "Character/Skeleton3D:bone" is consistent across all of them.
|
||||
var new_path := "Character/Skeleton3D:%s" % bone_name
|
||||
var correction: Quaternion = _corrections.get(bone_name, Quaternion())
|
||||
_copy_track(src_anim, i, new_anim, new_path, correction, track_type)
|
||||
var track_last := _copy_track(src_anim, i, new_anim, path_str, track_type)
|
||||
if track_last > max_time:
|
||||
max_time = track_last
|
||||
total_tracks += 1
|
||||
corrected_keys += _last_key_count
|
||||
|
||||
new_anim.length = max_time
|
||||
|
||||
if out_lib.has_animation(anim_name):
|
||||
push_warning("[build_animation_pack] Duplicate: %s" % anim_name)
|
||||
continue
|
||||
out_lib.add_animation(anim_name, new_anim)
|
||||
count += 1
|
||||
print(" + %s (%d tracks)" % [anim_name, new_anim.get_track_count()])
|
||||
print(" [%d] %s (%d tracks, %.3fs)" % [count, anim_name, new_anim.get_track_count(), max_time])
|
||||
|
||||
print("[build_animation_pack] Extracted %d animations, %d tracks, corrected %d rotation keys" % [count, total_tracks, corrected_keys])
|
||||
# Step 4: Build holding_walk (walk legs + holding arms)
|
||||
if out_lib.has_animation("walk_forward") and out_lib.has_animation("holding_1"):
|
||||
var holding_walk := _build_holding_walk(
|
||||
out_lib.get_animation("walk_forward"),
|
||||
out_lib.get_animation("holding_1")
|
||||
)
|
||||
out_lib.add_animation("holding_walk", holding_walk)
|
||||
var hw_path := "res://assets/characters/animations/holding_walk.res"
|
||||
var err_hw := ResourceSaver.save(holding_walk, ProjectSettings.globalize_path(hw_path))
|
||||
if err_hw != OK:
|
||||
push_warning("[build_animation_pack] Failed to save holding_walk: %d" % err_hw)
|
||||
else:
|
||||
print(" [+] holding_walk (%d tracks, %.3fs, looped)" % [holding_walk.get_track_count(), holding_walk.length])
|
||||
|
||||
err = ResourceSaver.save(out_lib, OUTPUT_PATH)
|
||||
if err != OK:
|
||||
push_error("[build_animation_pack] Failed to save: error %d" % err)
|
||||
# Step 5: Save animation-pack.res
|
||||
var err_save := ResourceSaver.save(out_lib, ProjectSettings.globalize_path(OUTPUT_PATH))
|
||||
if err_save != OK:
|
||||
push_error("[build_animation_pack] Failed to save: error %d" % err_save)
|
||||
src_scene.queue_free()
|
||||
tgt_scene.queue_free()
|
||||
quit(1)
|
||||
return
|
||||
|
||||
print("[build_animation_pack] Saved to %s" % OUTPUT_PATH)
|
||||
# Step 4: Save individual clip .res files alongside animation-pack.res
|
||||
for anim_name in out_lib.get_animation_list():
|
||||
var clip_path := "res://assets/characters/animations/%s.res" % anim_name
|
||||
var clip_res: Animation = out_lib.get_animation(anim_name)
|
||||
var err_clip := ResourceSaver.save(clip_res, ProjectSettings.globalize_path(clip_path))
|
||||
if err_clip != OK:
|
||||
push_warning("[build_animation_pack] Failed to save clip %s: %d" % [anim_name, err_clip])
|
||||
|
||||
# Step 5: Build holding_walk (walk legs + holding arms)
|
||||
if out_lib.has_animation("walk_forward") and out_lib.has_animation("holding_1"):
|
||||
var holding_walk := _build_holding_walk(
|
||||
out_lib.get_animation("walk_forward"),
|
||||
out_lib.get_animation("holding_1")
|
||||
)
|
||||
out_lib.add_animation("holding_walk", holding_walk)
|
||||
var hw_path := "res://assets/characters/animations/holding_walk.res"
|
||||
var err_hw := ResourceSaver.save(holding_walk, ProjectSettings.globalize_path(hw_path))
|
||||
if err_hw != OK:
|
||||
push_warning("[build_animation_pack] Failed to save holding_walk: %d" % err_hw)
|
||||
else:
|
||||
print(" [+] holding_walk (%d tracks, %.3fs, looped)" % [holding_walk.get_track_count(), holding_walk.length])
|
||||
|
||||
src_scene.queue_free()
|
||||
tgt_scene.queue_free()
|
||||
print("[build_animation_pack] Extracted %d animations, %d tracks" % [count, total_tracks])
|
||||
print("[build_animation_pack] Saved to %s + %d clips" % [OUTPUT_PATH, count])
|
||||
quit(0)
|
||||
|
||||
var _last_key_count: int = 0
|
||||
|
||||
func _copy_track(src_anim: Animation, src_idx: int, dst_anim: Animation, new_path: String, correction: Quaternion, track_type: int = -1) -> void:
|
||||
if track_type < 0:
|
||||
track_type = src_anim.track_get_type(src_idx)
|
||||
var dst_idx := dst_anim.add_track(track_type)
|
||||
dst_anim.track_set_path(dst_idx, NodePath(new_path))
|
||||
dst_anim.track_set_interpolation_type(dst_idx, src_anim.track_get_interpolation_type(src_idx))
|
||||
func _copy_track(src: Animation, src_idx: int, dst: Animation, new_path: String, track_type: int) -> float:
|
||||
var track_idx := dst.add_track(track_type)
|
||||
dst.track_set_path(track_idx, new_path)
|
||||
|
||||
var key_count := src_anim.track_get_key_count(src_idx)
|
||||
_last_key_count = 0
|
||||
var key_count := src.track_get_key_count(src_idx)
|
||||
var last_time := 0.0
|
||||
for k in key_count:
|
||||
var time := src_anim.track_get_key_time(src_idx, k)
|
||||
var value = src_anim.track_get_key_value(src_idx, k)
|
||||
var transition := src_anim.track_get_key_transition(src_idx, k)
|
||||
var t := src.track_get_key_time(src_idx, k)
|
||||
dst.track_insert_key(track_idx, t, src.track_get_key_value(src_idx, k))
|
||||
if t > last_time:
|
||||
last_time = t
|
||||
return last_time
|
||||
|
||||
match track_type:
|
||||
Animation.TYPE_POSITION_3D:
|
||||
var src_pos: Vector3 = value
|
||||
var src_rest_pos: Vector3 = _correction_src_rest.get(new_path, Vector3.ZERO)
|
||||
var tgt_rest_pos: Vector3 = _correction_tgt_rest.get(new_path, Vector3.ZERO)
|
||||
var corrected_pos: Vector3 = correction * (src_pos - src_rest_pos) + tgt_rest_pos
|
||||
dst_anim.position_track_insert_key(dst_idx, time, corrected_pos)
|
||||
Animation.TYPE_ROTATION_3D:
|
||||
var src_q: Quaternion = value
|
||||
var corrected_q: Quaternion = correction * src_q
|
||||
dst_anim.rotation_track_insert_key(dst_idx, time, corrected_q)
|
||||
_last_key_count += 1
|
||||
Animation.TYPE_SCALE_3D:
|
||||
dst_anim.scale_track_insert_key(dst_idx, time, value)
|
||||
Animation.TYPE_BLEND_SHAPE:
|
||||
dst_anim.blend_shape_track_insert_key(dst_idx, time, value)
|
||||
_:
|
||||
dst_anim.track_insert_key(dst_idx, time, value, transition)
|
||||
|
||||
func _build_holding_walk(walk: Animation, hold: Animation) -> Animation:
|
||||
"""Merge walk_forward (full body) with holding_1 (upper body override).
|
||||
Lower body bones (spine, thighs, legs) come from walk_forward.
|
||||
Upper body bones (spine.001, head, shoulders, arms) come from holding_1.
|
||||
"""
|
||||
# Upper body bone prefixes — holding_1 overrides these
|
||||
var upper_bones := [
|
||||
"spine.001", "head",
|
||||
"shoulder.L", "upper_arm.L", "forearm.L", "hand.L",
|
||||
"shoulder.R", "upper_arm.R", "forearm.R", "hand.R",
|
||||
]
|
||||
var result := Animation.new()
|
||||
result.loop_mode = Animation.LOOP_LINEAR
|
||||
result.length = walk.length
|
||||
|
||||
# Collect all track bone names from both animations
|
||||
var walk_tracks: Dictionary = {} # bone_name -> [track_idx, ...]
|
||||
for i in walk.get_track_count():
|
||||
var path := str(walk.track_get_path(i))
|
||||
if "Skeleton3D:" in path:
|
||||
var bone := path.split(":")[1]
|
||||
if not walk_tracks.has(bone):
|
||||
walk_tracks[bone] = []
|
||||
walk_tracks[bone].append(i)
|
||||
|
||||
var hold_tracks: Dictionary = {}
|
||||
for i in hold.get_track_count():
|
||||
var path := str(hold.track_get_path(i))
|
||||
if "Skeleton3D:" in path:
|
||||
var bone := path.split(":")[1]
|
||||
if not hold_tracks.has(bone):
|
||||
hold_tracks[bone] = []
|
||||
hold_tracks[bone].append(i)
|
||||
|
||||
# Add all walk_forward tracks as base
|
||||
for bone in walk_tracks:
|
||||
for idx in walk_tracks[bone]:
|
||||
var path := str(walk.track_get_path(idx))
|
||||
var track_type := walk.track_get_type(idx)
|
||||
_copy_track(walk, idx, result, path, track_type)
|
||||
|
||||
# Override upper body tracks with holding_1 values
|
||||
for bone in upper_bones:
|
||||
if hold_tracks.has(bone) and walk_tracks.has(bone):
|
||||
# Replace matching walk tracks with hold values
|
||||
var h_indices: Array = hold_tracks[bone]
|
||||
var w_indices: Array = walk_tracks[bone]
|
||||
for hi in h_indices:
|
||||
var hold_path := str(hold.track_get_path(hi))
|
||||
var hold_type := hold.track_get_type(hi)
|
||||
# Find matching walk track (same type)
|
||||
for wi in w_indices:
|
||||
if walk.track_get_type(wi) == hold_type:
|
||||
# Remove the walk track from result, add hold track
|
||||
var result_path := str(result.track_get_path(wi))
|
||||
# Find this track in result and remove it
|
||||
for ri in result.get_track_count():
|
||||
if str(result.track_get_path(ri)) == result_path and result.track_get_type(ri) == hold_type:
|
||||
result.remove_track(ri)
|
||||
break
|
||||
# Copy hold track
|
||||
_copy_track(hold, hi, result, hold_path, hold_type)
|
||||
w_indices.erase(wi)
|
||||
break
|
||||
|
||||
return result
|
||||
|
||||
Reference in New Issue
Block a user