ProjectRR — нативный прототип на ассетах ProjectR
- Все 189 скелетов Spine сконвертированы в нативные Godot Skeleton2D (.tscn), без Spine в рантайме - Меню + сцены: подземелье (карта комнат), просмотр спрайтов, нативные анимации - Кастомизация цвета (кожа/волосы/глаза) через шейдер - Вытащенные текстуры/UI/шрифты + нарезка спрайтов из атласа - Датасет комнат для LoRA, офлайн-инструменты конвертации в tools/ Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
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()
|
||||
Reference in New Issue
Block a user