feat: update

This commit is contained in:
2026-06-12 18:05:04 +08:00
parent 8a2fb36a98
commit 9dd3c59edf
67 changed files with 2298 additions and 74 deletions
+63
View File
@@ -0,0 +1,63 @@
# Dasher Animation Tools
The dasher character (`assets/characters/dashers/dasher_*.glb`) provides six
animation clips. They need to be retargeted to the player rig (which uses
`GeneralSkeleton` + Mixamo bone names) and packed into a single
`AnimationLibrary` that `player.tscn` can reference.
## One-time generation (headless, no editor needed)
```bash
godot --headless --path /home/beng/Godot/Projects/tekton-enet \
--script res://tools/convert_dasher_animations_headless.gd
godot --headless --path /home/beng/Godot/Projects/tekton-enet \
--script res://tools/build_dasher_pack_headless.gd
```
Output:
- `assets/characters/animations/dasher_<name>.res` (one per glb)
- `assets/characters/animations/dasher-pack.res` (combined library)
`scenes/player.tscn` already references `dasher-pack.res` as a second
AnimationLibrary on the AnimationPlayer node, alongside the original
`animation-pack.res`.
## Or run inside the editor
Use the EditorScript variants:
- `tools/convert_dasher_animations.gd` (`@tool extends EditorScript`)
- `tools/build_dasher_pack.gd` (`@tool extends EditorScript`)
Open each in the editor and `File > Run` (`Ctrl+Shift+X`).
## Usage
```gdscript
anim_player.play("dasher-pack/dasher_hit")
anim_player.play("dasher-pack/dasher_stun")
# etc.
```
## How it works
Each `dasher_*.glb` contains three source animations in a single
`AnimationLibrary` (e.g. `dasher_hold.glb` has `bob-rig|Hold|Anima_Layer`,
`bob-rig|Put|Anima_Layer`, `bob-rig|bob ani|Anima_Layer`). The converter
picks the most relevant one based on the glb filename
(`ANIM_PICK` constant in `convert_dasher_animations_headless.gd`) and
retargets the bone names. The resulting `.res` has one animation per glb
named after the glb (`dasher_hold`, `dasher_hit`, etc.), retargeted to
`GeneralSkeleton:<MixamoBone>` paths so they apply against the player's
shared skeleton.
The `BONE_REMAP` table in the converter defines Blender → Mixamo bone
name translations (e.g. `head``Head`, `hand.L``LeftHand`).
Helper nodes (`head_end`, `hand.L_end`, etc.) that don't exist in the
player rig are silently dropped.
## Editing the bone remap
If a dasher animation doesn't apply correctly, the most likely cause is a
bone name in the glb that's not in `BONE_REMAP`. Add the entry in
`convert_dasher_animations_headless.gd` and re-run both tools.
+52
View File
@@ -0,0 +1,52 @@
@tool
extends EditorScript
# Editor tool: combine all dasher_<name>.tres AnimationLibrary files into a
# single dasher-pack.tres for player.tscn to reference.
#
# Run AFTER convert_dasher_animations.gd.
# File > Run (Ctrl+Shift+X) on this script.
#
# Output: res://assets/characters/animations/dasher-pack.tres
#
# Then add an ext_resource to scenes/player.tscn:
# [ext_resource type="AnimationLibrary" path="res://assets/characters/animations/dasher-pack.tres" id="..."]
# And on the AnimationPlayer node:
# libraries/dasher-pack = ExtResource("...")
const ANIM_DIR := "res://assets/characters/animations"
const OUTPUT := "res://assets/characters/animations/dasher-pack.res"
func _run() -> void:
var combined := AnimationLibrary.new()
var dir := DirAccess.open(ANIM_DIR)
if not dir:
push_error("[Build] Cannot open %s" % ANIM_DIR)
return
var loaded: Array[String] = []
for fname in dir.get_files():
if not fname.begins_with("dasher_"): continue
if not fname.ends_with(".res"): continue
if fname == "dasher-pack.res": continue
var path := "%s/%s" % [ANIM_DIR, fname]
var res: Resource = load(path)
if not res is AnimationLibrary:
push_warning("[Build] %s is not an AnimationLibrary, skipping" % fname)
continue
var lib: AnimationLibrary = res
for anim_name in lib.get_animation_list():
if combined.has_animation(anim_name):
push_warning("[Build] Duplicate anim name '%s' in %s, skipping" % [anim_name, fname])
continue
combined.add_animation(anim_name, lib.get_animation(anim_name))
loaded.append("%s/%s" % [fname, anim_name])
var err := ResourceSaver.save(combined, OUTPUT)
if err != OK:
push_error("[Build] ResourceSaver.save failed: %d" % err)
return
print("[Build] %d animation(s) written to %s" % [loaded.size(), OUTPUT])
for l in loaded:
print(" - %s" % l)
+48
View File
@@ -0,0 +1,48 @@
extends SceneTree
# Headless equivalent of build_dasher_pack.gd.
# Run from CLI AFTER convert_dasher_animations_headless.gd:
# godot --headless --path /home/beng/Godot/Projects/tekton-enet --script res://tools/build_dasher_pack_headless.gd
#
# Produces: res://assets/characters/animations/dasher-pack.tres
const ANIM_DIR := "res://assets/characters/animations"
const OUTPUT := "res://assets/characters/animations/dasher-pack.res"
func _init() -> void:
print("[Build] Headless run starting...")
var combined := AnimationLibrary.new()
var dir := DirAccess.open(ANIM_DIR)
if not dir:
push_error("[Build] Cannot open %s" % ANIM_DIR)
quit(1)
return
var loaded: Array[String] = []
for fname in dir.get_files():
if not fname.begins_with("dasher_"): continue
if not fname.ends_with(".res"): continue
if fname == "dasher-pack.res": continue
var path := "%s/%s" % [ANIM_DIR, fname]
var res: Resource = load(path)
if not res is AnimationLibrary:
push_warning("[Build] %s is not an AnimationLibrary, skipping" % fname)
continue
var lib: AnimationLibrary = res
for anim_name in lib.get_animation_list():
if combined.has_animation(anim_name):
push_warning("[Build] Duplicate anim name '%s' in %s, skipping" % [anim_name, fname])
continue
combined.add_animation(anim_name, lib.get_animation(anim_name))
loaded.append("%s/%s" % [fname, anim_name])
var err := ResourceSaver.save(combined, OUTPUT)
if err != OK:
push_error("[Build] ResourceSaver.save failed: %d" % err)
quit(1)
return
print("[Build] Done. %d animation(s) written to %s" % [loaded.size(), OUTPUT])
for l in loaded:
print(" - %s" % l)
quit(0)
-1
View File
@@ -1 +0,0 @@
uid://baco51hmps6s1
+185
View File
@@ -0,0 +1,185 @@
@tool
extends EditorScript
# Editor tool: convert each dasher_*.glb into an AnimationLibrary .tres file
# with tracks retargeted to the player rig (GeneralSkeleton + Mixamo bone names).
#
# Run once in the Godot editor:
# File > Run (Ctrl+Shift+X) on this script, OR
# from a @tool script: EditorInterface.get_resource_filesystem().scan()
#
# Output:
# assets/characters/animations/dasher_<name>.tres (one per glb)
# Plus a combined library:
# assets/characters/animations/dasher-pack.tres
#
# Run build_dasher_pack.gd after this to consolidate.
const DASHER_DIR := "res://assets/characters/dashers"
const ANIM_DIR := "res://assets/characters/animations"
# Map dasher glb bone names (Blender/standard) -> player rig bone names (Mixamo).
# Tracks whose node name is not in this map are dropped (e.g. helper "head_end" empties).
const BONE_REMAP := {
"bob-hold": "Hips", # root of the dasher glb
"spine": "Hips",
"spine.001": "Spine",
"head": "Head",
"hand.L": "LeftHand",
"hand.R": "RightHand",
"forearm.L": "LeftLowerArm",
"forearm.R": "RightLowerArm",
"upper_arm.L": "LeftUpperArm",
"upper_arm.R": "RightUpperArm",
"shoulder.L": "LeftShoulder",
"shoulder.R": "RightShoulder",
"leg.L": "LeftLowerLeg",
"leg.R": "RightLowerLeg",
"thigh.L": "LeftUpperLeg",
"thigh.R": "RightUpperLeg",
"foot.L": "LeftFoot",
"foot.R": "RightFoot",
"toe.L": "LeftToeBase",
"toe.R": "RightToeBase",
}
const TRACK_TYPES := [
"position", # 3D position tracks
"rotation", # 3D rotation tracks (quaternion)
"scale", # 3D scale tracks
]
func _run() -> void:
var dasher_dir := DirAccess.open(DASHER_DIR)
if not dasher_dir:
push_error("[Convert] Cannot open %s" % DASHER_DIR)
return
var converted: Array[String] = []
for fname in dasher_dir.get_files():
if not fname.ends_with(".glb"): continue
if not fname.begins_with("dasher_"): continue
var stem := fname.get_basename() # e.g. "dasher_hold"
var out_path := "%s/%s.res" % [ANIM_DIR, stem]
var ok := _convert_one("%s/%s" % [DASHER_DIR, fname], stem, out_path)
if ok:
converted.append(out_path)
else:
push_warning("[Convert] Skipped %s" % fname)
print("[Convert] %d dasher glb(s) converted: %s" % [converted.size(), converted])
func _convert_one(glb_path: String, anim_name: String, out_path: String) -> bool:
print("[Convert] %s -> %s" % [glb_path, out_path])
var doc := GLTFDocument.new()
var state := GLTFState.new()
var err := doc.append_from_file(glb_path, state)
if err != OK:
push_error("[Convert] append_from_file failed: %d" % err)
return false
var scene := doc.generate_scene(state)
if not scene:
push_error("[Convert] generate_scene returned null")
return false
var src_player: AnimationPlayer = scene.find_child("AnimationPlayer", true, false)
if not src_player:
push_error("[Convert] No AnimationPlayer in %s" % glb_path)
scene.queue_free()
return false
var out_lib := AnimationLibrary.new()
for lib_name in src_player.get_animation_library_list():
var src_lib := src_player.get_animation_library(lib_name)
for src_anim_name in src_lib.get_animation_list():
var src_anim: Animation = src_lib.get_animation(src_anim_name)
var retargeted := _retarget(src_anim)
if retargeted and retargeted.get_track_count() > 0:
out_lib.add_animation(anim_name, retargeted)
print(" + %s (%d tracks)" % [anim_name, retargeted.get_track_count()])
else:
push_warning(" - %s produced 0 tracks after retarget" % src_anim_name)
scene.queue_free()
if out_lib.get_animation_list().is_empty():
push_error("[Convert] Output library is empty for %s" % glb_path)
return false
var save_err := ResourceSaver.save(out_lib, out_path)
if save_err != OK:
push_error("[Convert] ResourceSaver.save failed: %d" % save_err)
return false
return true
func _retarget(src: Animation) -> Animation:
var dst := Animation.new()
dst.length = src.length
dst.loop_mode = src.loop_mode
dst.step = src.step
# Map: original_track_idx -> new track path or -1 to drop
for i in src.get_track_count():
var orig_path: String = src.track_get_path(i)
var bone := _extract_bone_name(orig_path)
if not BONE_REMAP.has(bone):
# drop helper nodes like head_end, hand.L_end, etc.
continue
var new_bone: String = BONE_REMAP[bone]
var new_path := "%%GeneralSkeleton:%s" % new_bone
# Match the suffix type (:position/:rotation/:scale)
var type_suffix := ""
if orig_path.ends_with(":position"): type_suffix = "position"
elif orig_path.ends_with(":rotation"): type_suffix = "rotation"
elif orig_path.ends_with(":scale"): type_suffix = "scale"
else:
push_warning(" Unrecognized track path: %s" % orig_path)
continue
match type_suffix:
"position":
if new_bone != "Hips": continue
dst.add_track(Animation.TYPE_POSITION_3D)
"rotation":
dst.add_track(Animation.TYPE_ROTATION_3D)
"scale":
if new_bone != "Hips": continue
dst.add_track(Animation.TYPE_SCALE_3D)
var new_idx := dst.get_track_count() - 1
dst.track_set_path(new_idx, NodePath(new_path))
dst.track_set_interpolation_type(new_idx, src.track_get_interpolation_type(i))
dst.track_set_imported(new_idx, src.track_is_imported(i))
dst.track_set_enabled(new_idx, src.track_is_enabled(i))
# Copy keyframes
var key_count := src.track_get_key_count(i)
for k in key_count:
var t: float = src.track_get_key_time(i, k)
var v: Variant = src.track_get_key_value(i, k)
var trans := src.track_get_key_transition(i, k)
match type_suffix:
"position":
dst.track_insert_key(new_idx, t, v)
"rotation":
dst.track_insert_key(new_idx, t, v)
"scale":
dst.track_insert_key(new_idx, t, v)
dst.track_set_key_transition(new_idx, k, trans)
return dst
func _extract_bone_name(path: String) -> String:
# Tracks look like: "bob-hold/Skeleton3D:hand.L:rotation"
# We want the bone name "hand.L".
# Find the last ":" that precedes a type suffix; everything between the previous
# ":" (or start) and that ":" is the bone name. Easier: split on ":" and take
# the second-to-last segment.
var parts := path.split(":")
if parts.size() < 2:
return ""
# parts[-1] is the type ("rotation" etc), parts[-2] is the bone name
# but bone names can contain "." (e.g. "hand.L"), so just take parts[-2] as-is.
return parts[-2]
+256
View File
@@ -0,0 +1,256 @@
extends SceneTree
# Headless: convert each dasher_*.glb into a dasher_<name>.res with proper retargeting.
# Reads rest poses from both source GLB skeleton and target GeneralSkeleton (player.tscn)
# to compute per-bone correction quaternions.
const DASHER_DIR := "res://assets/characters/dashers"
const ANIM_DIR := "res://assets/characters/animations"
const BONE_REMAP := {
"bob-hold": "Hips",
"spine": "Hips",
"spine.001": "Spine",
"head": "Head",
"hand.L": "LeftHand",
"hand.R": "RightHand",
"forearm.L": "LeftLowerArm",
"forearm.R": "RightLowerArm",
"upper_arm.L": "LeftUpperArm",
"upper_arm.R": "RightUpperArm",
"shoulder.L": "LeftShoulder",
"shoulder.R": "RightShoulder",
"leg.L": "LeftLowerLeg",
"leg.R": "RightLowerLeg",
"thigh.L": "LeftUpperLeg",
"thigh.R": "RightUpperLeg",
"foot.L": "LeftFoot",
"foot.R": "RightFoot",
"toe.L": "LeftToeBase",
"toe.R": "RightToeBase",
}
const ANIM_PICK := {
"dasher_getting_hit": "Getting Hit",
"dasher_hit": "Hit",
"dasher_hold": "Hold",
"dasher_put": "Put",
"dasher_stun": "Stun",
"dasher_take": "bob ani",
}
# Source bone rest transforms (from GLB)
var src_rest: Dictionary = {} # bone_name -> Transform3D
# Target bone rest transforms (from GeneralSkeleton)
var tgt_rest: Dictionary = {} # bone_name -> Transform3D
# Precomputed correction quaternions
var corrections: Dictionary = {} # bone_name -> Quaternion
func _init() -> void:
print("[Convert] Headless run starting...")
# Step 1: Load target skeleton rest poses from player scene
_load_target_rest()
# Step 2: Convert each GLB
var dasher_dir := DirAccess.open(DASHER_DIR)
if not dasher_dir:
push_error("[Convert] Cannot open %s" % DASHER_DIR)
quit(1); return
var converted: Array[String] = []
for fname in dasher_dir.get_files():
if not fname.ends_with(".glb"): continue
if not fname.begins_with("dasher_"): continue
var stem := fname.get_basename()
var out_path := "%s/%s.res" % [ANIM_DIR, stem]
var ok := _convert_one("%s/%s" % [DASHER_DIR, fname], stem, out_path)
if ok:
converted.append(out_path)
else:
push_warning("[Convert] Skipped %s" % fname)
print("[Convert] Done. %d dasher glb(s) converted:" % converted.size())
for p in converted:
print(" - %s" % p)
quit(0)
func _load_target_rest() -> void:
# Load Masbro.glb directly to extract GeneralSkeleton rest poses
# (player.tscn may fail to load if dasher-pack.res doesn't exist yet)
var doc := GLTFDocument.new()
var state := GLTFState.new()
var err := doc.append_from_file("res://assets/characters/Masbro.glb", state)
if err != OK:
push_error("[Convert] Cannot load Masbro.glb: %d" % err)
return
var instance = doc.generate_scene(state)
if not instance:
push_error("[Convert] generate_scene returned null for Masbro.glb")
return
# Find the Skeleton3D (named GeneralSkeleton in the scene tree)
var skeleton: Skeleton3D = instance.find_child("GeneralSkeleton", true, false)
if not skeleton:
# Try finding any Skeleton3D
skeleton = instance.find_child("Skeleton3D", true, false)
if not skeleton:
push_error("[Convert] No Skeleton3D in Masbro.glb")
instance.free()
return
print("[Convert] Target skeleton: '%s' with %d bones" % [skeleton.name, skeleton.get_bone_count()])
for bone_idx in skeleton.get_bone_count():
var bone_name := skeleton.get_bone_name(bone_idx)
tgt_rest[bone_name] = skeleton.get_bone_rest(bone_idx)
instance.free()
print("[Convert] Loaded %d target bone rest poses" % tgt_rest.size())
func _convert_one(glb_path: String, anim_name: String, out_path: String) -> bool:
print("[Convert] %s -> %s" % [glb_path, out_path])
var doc := GLTFDocument.new()
var state := GLTFState.new()
var err := doc.append_from_file(glb_path, state)
if err != OK:
push_error("[Convert] append_from_file failed: %d" % err)
return false
var scene: Node = doc.generate_scene(state)
if not scene:
push_error("[Convert] generate_scene returned null")
return false
# Extract source skeleton rest poses
src_rest.clear()
var src_skeleton: Skeleton3D = scene.find_child("Skeleton3D", true, false)
if src_skeleton:
for bone_idx in src_skeleton.get_bone_count():
var bone_name := src_skeleton.get_bone_name(bone_idx)
src_rest[bone_name] = src_skeleton.get_bone_rest(bone_idx)
print(" Source skeleton: %d bones" % src_rest.size())
else:
push_error("[Convert] No Skeleton3D in %s" % glb_path)
scene.queue_free()
return false
# Compute per-bone correction quaternions
corrections.clear()
for src_bone in BONE_REMAP:
var tgt_bone: String = BONE_REMAP[src_bone]
if src_rest.has(src_bone) and tgt_rest.has(tgt_bone):
var src_basis: Basis = src_rest[src_bone].basis
var tgt_basis: Basis = tgt_rest[tgt_bone].basis
# correction = tgt_rest.inverse() * src_rest
# Applied as: final_q = correction * keyframe_q
corrections[src_bone] = tgt_basis.inverse() * src_basis
# print(" Correction for %s -> %s: %s" % [src_bone, tgt_bone, str(corrections[src_bone])])
var src_player: AnimationPlayer = scene.find_child("AnimationPlayer", true, false)
if not src_player:
push_error("[Convert] No AnimationPlayer in %s" % glb_path)
scene.queue_free()
return false
# Pick the animation whose middle segment matches the keyword
var pick_keyword: String = ANIM_PICK.get(anim_name, anim_name.replace("dasher_", ""))
var picked: Animation = null
for lib_name in src_player.get_animation_library_list():
var src_lib: AnimationLibrary = src_player.get_animation_library(lib_name)
for src_anim_name in src_lib.get_animation_list():
var segments := src_anim_name.split("|")
var mid_segment: String = segments[1] if segments.size() >= 3 else src_anim_name
if pick_keyword.to_lower() == mid_segment.to_lower():
picked = src_lib.get_animation(src_anim_name)
break
if picked: break
if not picked:
for lib_name in src_player.get_animation_library_list():
var src_lib: AnimationLibrary = src_player.get_animation_library(lib_name)
if src_lib.get_animation_list().size() > 0:
picked = src_lib.get_animation(src_lib.get_animation_list()[0])
break
if not picked:
push_error("[Convert] No animations in %s" % glb_path)
scene.queue_free()
return false
var retargeted := _retarget(picked)
scene.queue_free()
if retargeted.get_track_count() == 0:
push_error("[Convert] Retarget produced 0 tracks for %s" % glb_path)
return false
var out_lib := AnimationLibrary.new()
out_lib.add_animation(anim_name, retargeted)
print(" + %s (%d tracks from %s)" % [anim_name, retargeted.get_track_count(), picked.resource_name if picked.resource_name else "<anon>"])
var save_err := ResourceSaver.save(out_lib, out_path)
if save_err != OK:
push_error("[Convert] ResourceSaver.save failed: %d" % save_err)
return false
return true
func _retarget(src: Animation) -> Animation:
var dst := Animation.new()
dst.length = src.length
dst.loop_mode = src.loop_mode
dst.step = src.step
var usable: Array[int] = []
for i in src.get_track_count():
var orig_path: String = src.track_get_path(i)
var bone := _extract_bone_name(orig_path)
if BONE_REMAP.has(bone):
usable.append(i)
for i in usable:
var orig_path: String = src.track_get_path(i)
var bone: String = _extract_bone_name(orig_path)
var new_bone: String = BONE_REMAP[bone]
var new_path := "%%GeneralSkeleton:%s" % new_bone
var track_type := src.track_get_type(i)
match track_type:
Animation.TYPE_POSITION_3D:
if new_bone != "Hips": continue
dst.add_track(Animation.TYPE_POSITION_3D)
Animation.TYPE_ROTATION_3D:
dst.add_track(Animation.TYPE_ROTATION_3D)
Animation.TYPE_SCALE_3D:
if new_bone != "Hips": continue
dst.add_track(Animation.TYPE_SCALE_3D)
Animation.TYPE_BLEND_SHAPE: continue
_: continue
var new_idx := dst.get_track_count() - 1
dst.track_set_path(new_idx, NodePath(new_path))
dst.track_set_interpolation_type(new_idx, src.track_get_interpolation_type(i))
dst.track_set_imported(new_idx, src.track_is_imported(i))
dst.track_set_enabled(new_idx, src.track_is_enabled(i))
var key_count := src.track_get_key_count(i)
for k in key_count:
var t: float = src.track_get_key_time(i, k)
var v: Variant = src.track_get_key_value(i, k)
var trans := src.track_get_key_transition(i, k)
# Apply rest-pose correction to rotation tracks
if track_type == Animation.TYPE_ROTATION_3D and corrections.has(bone):
var orig_q: Quaternion = v
var corr_q: Quaternion = corrections[bone]
v = corr_q * orig_q
dst.track_insert_key(new_idx, t, v)
dst.track_set_key_transition(new_idx, k, trans)
return dst
func _extract_bone_name(path: String) -> String:
var parts := path.split(":")
if parts.size() < 2:
return ""
return parts[-1]
@@ -0,0 +1 @@
uid://dgwmo0tdt8dwl
+20
View File
@@ -0,0 +1,20 @@
extends SceneTree
const DASHER_DIR := "res://assets/characters/dashers"
func _init() -> void:
var dir := DirAccess.open(DASHER_DIR)
for fname in dir.get_files():
if not fname.ends_with(".glb"): continue
var doc = GLTFDocument.new()
var state = GLTFState.new()
doc.append_from_file("%s/%s" % [DASHER_DIR, fname], state)
var scene = doc.generate_scene(state)
var ap: AnimationPlayer = scene.find_child("AnimationPlayer", true, false)
if ap:
print("=== %s ===" % fname)
for lib_name in ap.get_animation_library_list():
var lib = ap.get_animation_library(lib_name)
for anim_name in lib.get_animation_list():
print(" ", anim_name)
quit()
+1
View File
@@ -0,0 +1 @@
uid://cau1rt1kfa32y
+32
View File
@@ -0,0 +1,32 @@
extends SceneTree
func _init() -> void:
var scene = preload("res://scenes/player.tscn").instantiate()
var skel: Skeleton3D = null
# Find GeneralSkeleton anywhere in the player scene
var queue = [scene]
while queue.size() > 0:
var n = queue.pop_front()
if n is Skeleton3D and n.name == "GeneralSkeleton":
skel = n
break
for c in n.get_children():
queue.append(c)
if skel:
print("Found GeneralSkeleton! Bones:")
for i in skel.get_bone_count():
print(" - ", skel.get_bone_name(i))
else:
print("GeneralSkeleton not found in player.tscn!")
print("Tree:")
_print_tree(scene, 0)
quit()
func _print_tree(n: Node, depth: int) -> void:
var indent = " ".repeat(depth)
print("%s- %s (%s)" % [indent, n.name, n.get_class()])
for c in n.get_children():
_print_tree(c, depth + 1)
+1
View File
@@ -0,0 +1 @@
uid://dxxtbdm3usgdp
+15
View File
@@ -0,0 +1,15 @@
extends SceneTree
func _init() -> void:
var scene = preload("res://scenes/player.tscn").instantiate()
var masbro = scene.get_node("Masbro")
if masbro:
print("Masbro found! Children:")
_print_tree(masbro, 0)
else:
print("No Masbro")
quit()
func _print_tree(n: Node, depth: int) -> void:
var indent = " ".repeat(depth)
print("%s- %s (%s)" % [indent, n.name, n.get_class()])
for c in n.get_children():
_print_tree(c, depth + 1)
+1
View File
@@ -0,0 +1 @@
uid://dt8bew0d4r6bp
+8
View File
@@ -0,0 +1,8 @@
extends SceneTree
func _init() -> void:
print("TYPE_VALUE: ", Animation.TYPE_VALUE)
print("TYPE_POSITION_3D: ", Animation.TYPE_POSITION_3D)
print("TYPE_ROTATION_3D: ", Animation.TYPE_ROTATION_3D)
print("TYPE_SCALE_3D: ", Animation.TYPE_SCALE_3D)
quit()
+1
View File
@@ -0,0 +1 @@
uid://dl8svqqgt6m3n
+33
View File
@@ -0,0 +1,33 @@
extends SceneTree
func _init() -> void:
var lib = load("res://assets/characters/animations/dasher-pack.res") as AnimationLibrary
if not lib:
quit(1)
return
var anim_name = "dasher_hit"
if lib.has_animation(anim_name):
var anim = lib.get_animation(anim_name)
print("Animation: ", anim_name)
var has_pos = 0
var has_rot = 0
var has_scale = 0
var pos_bones = []
for i in anim.get_track_count():
var type = anim.track_get_type(i)
var path = anim.track_get_path(i)
if type == Animation.TYPE_POSITION_3D:
has_pos += 1
pos_bones.append(str(path))
elif type == Animation.TYPE_ROTATION_3D:
has_rot += 1
elif type == Animation.TYPE_SCALE_3D:
has_scale += 1
print(" Positions: ", has_pos, " (", pos_bones, ")")
print(" Rotations: ", has_rot)
print(" Scales: ", has_scale)
quit(0)
+1
View File
@@ -0,0 +1 @@
uid://bleaj3miugqrm
+13
View File
@@ -0,0 +1,13 @@
extends SceneTree
func _init() -> void:
var lib = load("res://assets/characters/animations/dasher-pack.res") as AnimationLibrary
for anim_name in lib.get_animation_list():
var anim = lib.get_animation(anim_name)
print("=== %s (%d tracks) ===" % [anim_name, anim.get_track_count()])
for i in anim.get_track_count():
var type = anim.track_get_type(i)
var path = anim.track_get_path(i)
var type_str = ["VALUE","POSITION","ROTATION","SCALE"][type]
print(" [%d] type=%s path=%s" % [i, type_str, path])
quit()
+1
View File
@@ -0,0 +1 @@
uid://yyhb801dk5ve
+16
View File
@@ -0,0 +1,16 @@
extends SceneTree
func _init() -> void:
var lib = load("res://assets/characters/animations/dasher-pack.res") as AnimationLibrary
if not lib:
print("Failed to load dasher-pack.res")
quit(1)
return
var anim_name = lib.get_animation_list()[0]
var anim = lib.get_animation(anim_name)
print("First animation: ", anim_name)
for i in min(anim.get_track_count(), 5):
print(" Track ", i, " path: ", anim.track_get_path(i))
quit(0)
+1
View File
@@ -0,0 +1 @@
uid://ce58ow1re1l24
+19
View File
@@ -0,0 +1,19 @@
extends SceneTree
func _init() -> void:
var doc = GLTFDocument.new()
var state = GLTFState.new()
var err = doc.append_from_file("res://assets/characters/dashers/dasher_hit.glb", state)
var scene = doc.generate_scene(state)
var ap: AnimationPlayer = scene.find_child("AnimationPlayer", true, false)
var lib = ap.get_animation_library("")
var anim_name = "dasher_take|Hit|Anima_Layer"
if lib.has_animation(anim_name):
var anim = lib.get_animation(anim_name)
print("=== RAW TRACKS ===")
for i in anim.get_track_count():
var type = anim.track_get_type(i)
var path = anim.track_get_path(i)
print(" Track ", i, " type: ", type, " path: ", path)
quit()
+1
View File
@@ -0,0 +1 @@
uid://dcoduco766d6w
+34
View File
@@ -0,0 +1,34 @@
extends SceneTree
func _init() -> void:
var lib = load("res://assets/characters/animations/animation-pack.res") as AnimationLibrary
if not lib:
print("Failed to load animation-pack.res")
quit(1)
return
var anim_name = "walk_forward"
if lib.has_animation(anim_name):
var anim = lib.get_animation(anim_name)
print("Animation: ", anim_name)
var has_pos = 0
var has_rot = 0
var has_scale = 0
var pos_bones = []
for i in anim.get_track_count():
var type = anim.track_get_type(i)
var path = anim.track_get_path(i)
if type == Animation.TYPE_POSITION_3D:
has_pos += 1
pos_bones.append(str(path))
elif type == Animation.TYPE_ROTATION_3D:
has_rot += 1
elif type == Animation.TYPE_SCALE_3D:
has_scale += 1
print(" Positions: ", has_pos, " (", pos_bones, ")")
print(" Rotations: ", has_rot)
print(" Scales: ", has_scale)
quit(0)
+1
View File
@@ -0,0 +1 @@
uid://c1boh2egfnnfu
+12
View File
@@ -0,0 +1,12 @@
extends SceneTree
func _init() -> void:
var lib = load("res://assets/characters/animations/animation-pack.res") as AnimationLibrary
var anim = lib.get_animation("walk_forward")
print("=== walk_forward tracks ===")
for i in anim.get_track_count():
var type = anim.track_get_type(i)
var path = anim.track_get_path(i)
var type_str = ["VALUE","POSITION","ROTATION","SCALE"][type]
print(" [%d] type=%s path=%s" % [i, type_str, path])
quit()
+1
View File
@@ -0,0 +1 @@
uid://dajp15327baah