extends SceneTree # Офлайн-запекатель: прогоняет Spine-рантайм и снимает покадрово ЛОКАЛЬНЫЕ трансформы костей # для выбранных анимаций -> tools/rig/_anims.json (только числа, без Spine на выходе). const BASE := "res://characters/elf1/spine/elf1_combat" const OUT := "res://tools/rig/elf1_combat_anims.json" const FPS := 30.0 const ANIMS := ["idle_0", "walk_0", "attack_0", "attack_1", "knockback_0", "stun_0"] func _initialize(): var atlas := SpineAtlasResource.new(); atlas.load_from_atlas_file(BASE + ".atlas.txt") var skf := SpineSkeletonFileResource.new(); skf.load_from_file(BASE + ".skel.bytes") var data := SpineSkeletonDataResource.new() data.set_atlas_res(atlas); data.set_skeleton_file_res(skf) var sprite := SpineSprite.new() sprite.set_skeleton_data_res(data) get_root().add_child(sprite) var st = sprite.get_animation_state() var skel = sprite.get_skeleton() # имена костей в порядке скелета var bone_names := [] for bd in data.get_bones(): bone_names.append(bd.get_bone_name()) # имена слотов + индекс по имени (для draw order) var slot_names := [] var slot_index := {} var live_slots = skel.get_slots() for i in live_slots.size(): var sn = live_slots[i].get_data().get_name() slot_names.append(sn); slot_index[sn] = i var out := { "fps": FPS, "bones": bone_names, "slots": slot_names, "animations": {} } for anim in ANIMS: var sa = data.find_animation(anim) if sa == null: print("skip (нет): ", anim); continue var dur: float = sa.get_duration() var nframes := int(round(dur * FPS)) + 1 st.clear_tracks() st.set_animation(anim, false, 0) var frames := [] var draworder := [] for f in nframes: skel.set_bones_to_setup_pose() skel.set_slots_to_setup_pose() st.apply(skel) var frame := [] for name in bone_names: var b = skel.find_bone(name) frame.append([ snappedf(b.get_x(), 0.001), snappedf(b.get_y(), 0.001), snappedf(b.get_rotation(), 0.001), snappedf(b.get_scale_x(), 0.001), snappedf(b.get_scale_y(), 0.001) ]) frames.append(frame) # порядок отрисовки: индексы слотов сзади->вперёд var order := [] for s in skel.get_draw_order(): order.append(slot_index[s.get_data().get_name()]) draworder.append(order) st.update(1.0 / FPS) out["animations"][anim] = { "duration": dur, "frames": frames, "draworder": draworder } print("baked ", anim, " frames=", nframes, " dur=", dur) var fp := FileAccess.open(OUT, FileAccess.WRITE) fp.store_string(JSON.stringify(out)) fp.close() print("SAVED ", OUT) quit()