- Все 189 скелетов Spine сконвертированы в нативные Godot Skeleton2D (.tscn), без Spine в рантайме - Меню + сцены: подземелье (карта комнат), просмотр спрайтов, нативные анимации - Кастомизация цвета (кожа/волосы/глаза) через шейдер - Вытащенные текстуры/UI/шрифты + нарезка спрайтов из атласа - Датасет комнат для LoRA, офлайн-инструменты конвертации в tools/ Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
133 lines
4.9 KiB
C#
133 lines
4.9 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Text.Json;
|
|
using Spine;
|
|
|
|
// Офлайн-экспортёр: читает Spine .skel.bytes+.atlas через spine-csharp и
|
|
// выгружает геометрию (кости setup + привязки с мировыми вершинами/UV/треуг/весами) в свой JSON.
|
|
// Использование: spineconv <base_path_без_расширения> <out.json>
|
|
|
|
class NullTex : TextureLoader
|
|
{
|
|
public void Load(AtlasPage page, string path) { }
|
|
public void Unload(object texture) { }
|
|
}
|
|
|
|
class Program
|
|
{
|
|
static void Main(string[] args)
|
|
{
|
|
string baseP = args[0];
|
|
string outPath = args[1];
|
|
|
|
var atlas = new Atlas(baseP + ".atlas.txt", new NullTex());
|
|
var loader = new AtlasAttachmentLoader(atlas);
|
|
var binary = new SkeletonBinary(loader) { Scale = 1f };
|
|
SkeletonData data = binary.ReadSkeletonData(baseP + ".skel.bytes");
|
|
|
|
var skeleton = new Skeleton(data);
|
|
skeleton.SetToSetupPose();
|
|
skeleton.UpdateWorldTransform(Skeleton.Physics.Update);
|
|
|
|
var boneIndex = new Dictionary<BoneData, int>();
|
|
var bonesOut = new List<object>();
|
|
var bones = data.Bones;
|
|
for (int i = 0; i < bones.Count; i++)
|
|
{
|
|
var b = bones.Items[i];
|
|
boneIndex[b] = i;
|
|
bonesOut.Add(new
|
|
{
|
|
name = b.Name,
|
|
parent = b.Parent != null ? b.Parent.Name : null,
|
|
x = b.X, y = b.Y, rotation = b.Rotation,
|
|
scaleX = b.ScaleX, scaleY = b.ScaleY,
|
|
shearX = b.ShearX, shearY = b.ShearY, length = b.Length
|
|
});
|
|
}
|
|
|
|
var attsOut = new List<object>();
|
|
var pages = new HashSet<string>();
|
|
var drawOrder = skeleton.DrawOrder;
|
|
for (int d = 0; d < drawOrder.Count; d++)
|
|
{
|
|
Slot slot = drawOrder.Items[d];
|
|
Attachment att = slot.Attachment;
|
|
if (att == null) continue;
|
|
int sBone = boneIndex[slot.Data.BoneData];
|
|
var sd = slot.Data;
|
|
float[] color = { sd.R, sd.G, sd.B, sd.A };
|
|
|
|
if (att is RegionAttachment ra)
|
|
{
|
|
float[] wv = new float[8];
|
|
ra.ComputeWorldVertices(slot, wv, 0, 2);
|
|
var reg = (AtlasRegion)ra.Region;
|
|
pages.Add(reg.page.name);
|
|
var weights = new List<object>();
|
|
for (int k = 0; k < 4; k++)
|
|
weights.Add(new object[] { new object[] { sBone, 1f } });
|
|
attsOut.Add(new
|
|
{
|
|
slot = sd.Name, name = ra.Name, type = "region", page = reg.page.name,
|
|
world = wv, uv = ra.UVs, triangles = new int[] { 0, 1, 2, 2, 3, 0 },
|
|
weights, color
|
|
});
|
|
}
|
|
else if (att is MeshAttachment ma)
|
|
{
|
|
int n = ma.WorldVerticesLength;
|
|
float[] wv = new float[n];
|
|
ma.ComputeWorldVertices(slot, 0, n, wv, 0, 2);
|
|
var reg = (AtlasRegion)ma.Region;
|
|
pages.Add(reg.page.name);
|
|
attsOut.Add(new
|
|
{
|
|
slot = sd.Name, name = ma.Name, type = "mesh", page = reg.page.name,
|
|
world = wv, uv = ma.UVs, triangles = ma.Triangles,
|
|
weights = ParseWeights(ma, sBone), color
|
|
});
|
|
}
|
|
}
|
|
|
|
var outObj = new { bones = bonesOut, pages = new List<string>(pages), attachments = attsOut };
|
|
File.WriteAllText(outPath, JsonSerializer.Serialize(outObj));
|
|
int meshes = 0, regions = 0, weighted = 0;
|
|
foreach (var s in drawOrder)
|
|
{
|
|
if (s.Attachment is MeshAttachment m) { meshes++; if (m.Bones != null) weighted++; }
|
|
else if (s.Attachment is RegionAttachment) regions++;
|
|
}
|
|
Console.WriteLine($"OK bones={bonesOut.Count} regions={regions} meshes={meshes} (weighted={weighted}) pages={pages.Count}");
|
|
}
|
|
|
|
static List<object> ParseWeights(MeshAttachment ma, int fallbackBone)
|
|
{
|
|
var res = new List<object>();
|
|
int verts = ma.WorldVerticesLength / 2;
|
|
if (ma.Bones == null)
|
|
{
|
|
for (int i = 0; i < verts; i++)
|
|
res.Add(new object[] { new object[] { fallbackBone, 1f } });
|
|
return res;
|
|
}
|
|
int[] bs = ma.Bones;
|
|
float[] vs = ma.Vertices;
|
|
int vb = 0, vv = 0;
|
|
for (int i = 0; i < verts; i++)
|
|
{
|
|
int cnt = bs[vb++];
|
|
var list = new List<object>();
|
|
for (int k = 0; k < cnt; k++)
|
|
{
|
|
int bi = bs[vb++];
|
|
float x = vs[vv++], y = vs[vv++], w = vs[vv++];
|
|
list.Add(new object[] { bi, w });
|
|
}
|
|
res.Add(list);
|
|
}
|
|
return res;
|
|
}
|
|
}
|