Files
Leonid PershinandClaude Opus 4.8 04cf5f90b0 ProjectRR — нативный прототип на ассетах ProjectR
- Все 189 скелетов Spine сконвертированы в нативные Godot Skeleton2D (.tscn), без Spine в рантайме
- Меню + сцены: подземелье (карта комнат), просмотр спрайтов, нативные анимации
- Кастомизация цвета (кожа/волосы/глаза) через шейдер
- Вытащенные текстуры/UI/шрифты + нарезка спрайтов из атласа
- Датасет комнат для LoRA, офлайн-инструменты конвертации в tools/

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-03 16:29:21 +03:00

76 lines
2.7 KiB
GDScript

extends SceneTree
# Офлайн-запекатель: прогоняет Spine-рантайм и снимает покадрово ЛОКАЛЬНЫЕ трансформы костей
# для выбранных анимаций -> tools/rig/<name>_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()