Files
tekton/tools/build_animation_pack.gd
T
adtpdn 5354d8b30f 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
2026-06-15 18:15:39 +08:00

217 lines
7.9 KiB
GDScript

extends SceneTree
## Build animation-pack.res from animation-0.glb (original Blender bone names).
##
## 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 OUTPUT_PATH := "res://assets/characters/animations/animation-pack.res"
func _init() -> void:
print("[build_animation_pack] Starting build...")
# 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: Node = src_doc.generate_scene(src_state)
if not src_scene:
push_error("[build_animation_pack] Failed to generate source scene")
quit(1)
return
# 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()
quit(1)
return
var out_lib := AnimationLibrary.new()
var count := 0
var total_tracks := 0
for lib_name in anim_player.get_animation_library_list():
var src_lib := anim_player.get_animation_library(lib_name)
for anim_name in src_lib.get_animation_list():
var src_anim: Animation = src_lib.get_animation(anim_name)
if not src_anim:
continue
var new_anim := Animation.new()
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)
# Skip the GLB root "retarget" node
if "retarget" in path_str and ":" not in path_str:
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:")
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
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(" [%d] %s (%d tracks, %.3fs)" % [count, anim_name, new_anim.get_track_count(), max_time])
# 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])
# 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()
quit(1)
return
# 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()
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)
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.track_get_key_count(src_idx)
var last_time := 0.0
for k in key_count:
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
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