Files
Spine_lab/编辑器交互与数据API.md
T
tianmo 2b23ce59db 新增《编辑器交互与数据API》调研文档
- 交互层:工具注册(Create Tool 等)与视口交互入口
- 层级树:VZ(视图) / uo_2(节点) / xw_2(骨架) / gp_2(骨) / acK(插槽) 包装模型
- 当前选择:og_1.D.D 的读取点与菜单动作的依赖关系
- 数据层:所有 New X 都落到 KP.B(父级, 类型名),以及 KP 真实类名待解
- 改删查命令清单、撤销注意点、skeleton/root 删除边界、下一步 5 条
2026-10-16 14:22:48 +08:00

5.9 KiB
Raw Blame History

编辑器交互逻辑 & 数据 API 调研

目标:在动手写"面板里对场景对象增删改查"之前,先把编辑器用户交互链路和真正生成数据的代码摸清楚。 本文只记录查到的事实 + 出处 + 还没弄清的地方。(混淆名 ⇒ 真实名见 混淆名对照表.md。)


一、交互层:工具(Tool)

所有工具都在菜单注册表 ll 的构造函数里注册(~/Desktop/spine398/src-readable/ll.java):

菜单名 动作类(CFR 名) 备注
Create Tool oo 新建对象用(靠视口点拖)
Scale/Rotate/Translate/Shear/Pose/Weights Tool oi_1 / oj_0 / ok_1 / om / on_0 / op
Last Tool oq_1 切回上一个工具

oo(Create Tool)点下去做的事:

og_1.D.D.D.I(true);   // 打开"创建"交互(og_1.D = 编辑器上下文单例,后面是逐层嵌套的字段)

→ 新建对象本身是交互式的:工具打开后由视口里的按下/拖动事件驱动;菜单里的 New Bone 只是"用当前选择当父级"的等价入口(见下)。

二、层级树(Tree 视图)与节点模型

  • Tree 视图 Actor = VZ(2819 行,us.Tree 面板的内容类)。
  • 树节点 = uo_2(Node),每个节点包一个包装对象:
包装类 代表的场景对象(按用法推断)
xw_2 skeleton 节点(xw_2.D = 骨架数据)
gp_2 bone 节点(xw_2.java:367 有 "Bone: " + ((gp_2)object).D)
acK / acW slot 节点(acK.java 里有 "Set Parent" / "Sets the slot's parent bone")
GU Draw Order 里的对象
Yr xw_2 的父类(一批节点包装的基类)
md_1 节点里保存的模型引用

树里的固定结构(见 VZ.java 建节点处):skeleton → root → bone/slot…, 另有 Draw Order#startcaps、Skins、Events、Animations 这些 icon-*Root 固定根。

当前选择放在哪

反编译源码里到处是这种读取:

et_0 sel = og_1.D;   // 编辑器上下文单例
sel = sel.D;         // ← 当前选择对象(菜单动作都作用在它上面)
... og_1.D.D.D       // 再深一层(视图/工具相关)

mn_1(New Bone)的判断就依赖它:

if (sel instanceof xw_2) { KP.B(((xw_2)sel).D, "Bone"); return true; }  // 选中 skeleton
if (sel instanceof acK)  { KP.B(((acK)sel).D,  "Bone"); return true; }  // 选中 slot
return false;                                                           // 没选中/不支持 → 什么都不做

三、数据层:真正"建出对象"的 API

所有 New X 最后都落到同一个方法:

KP.B(target, "Bone");                    // mn_1  New Bone
KP.B(target, "Slot");                    // mo    New Slot
KP.B(target, "Skin Placeholder");        // mp
KP.B(target, "Bounding Box");            // mq
KP.B(target, "Path");                    // ms_1
KP.B(target, "Clipping");                // mt
KP.B(target, "IK Constraint");           // mu_1
KP.B(og_1.D.D.D.D, "Path Constraint");   // mv_0
KP.B(og_1.D.D.D.D, "Skin");              // mx_0

另有树视图自己的分支:acM.java:98、yi_0.java:28。

这就是"增"的直接入口:第一个参数是父级对象(选中项),第二个参数是类型名。 也就是说编辑器内部不是"凭空建一根骨头",模型本身就是"挂在父对象下面的树"。

⚠️ 还没定死:KP 到底是哪个类

  • 载荷里确实有个 KP.class,但它是 KP extends eW 的 SelectBox 控件, 只有 public static fI D(fI) 一个静态方法,没有 B,不是我们要的那个。
  • 可读源码树里的 KP 来自解码 invokedynamic 调用点(编辑器把几乎所有调用都编译成 invokedynamic,所以 javap 里看不到 invokestatic KP.B)。调用映射在 ~/Desktop/spine398/obmap.tsv,能看到几十条 KP.B / KP.D / KP.L(如 RE mq KP.B、Yr gy KP.D)。

→ 下一步:用 tools/decrypt/ 把某个调用点(例如 mn 里那次)的真实 owner 类解出来, 或在运行时按"方法名 + 描述符(对象 + String 两参)"在类表 LPt4.mc 里搜候选类逐个验证。

四、改 / 删 / 查

操作 入口 说明
删 Delete(pb_0) 作用在当前选择上
改名 Rename(lo)
复制 Duplicate(lp_0)、Cut/Copy/Paste(pv/pw/px_2)
层级 Set Parent(mm_1)、Parent/Child/Previous Sibling/Next Sibling
顺序 Draw Order Up/Down(lu_1/ls_1 等)
查 树节点本身就是查询结果;Select - Bones/Select - Attachments 负责筛选

撤销:Undo/Redo 也在这套命令系统里(lm_0/nw)。菜单路径的动作会进撤销栈; 直接反射调 KP.B 很可能绕过撤销 —— 真要写入时优先走菜单命令(Commands.run), 或先看清动作类自己有没有压栈。

五、删除边界(需求确认)

  • skeleton:整个场景的根节点,可以删(删它等于清空所有场景对象);
  • root:所有对象的根骨骼,只要 skeleton 在就必然存在,不允许删除。

面板做删除时要在选择集里拦住 root。

六、下一步(按顺序)

  1. 解出 KP 的真实类名(indy 调用映射 / 运行时按签名搜 LPt4.mc),确认 B(Object,String) 的描述符。
  2. 确认"父级对象"参数的确切类型:xw_2.D(skeleton 节点里的骨架数据)、acK.D(slot 节点里的数据)。
  3. 搞清选择怎么设置(og_1.D.D 是可直接写的字段,还是只能由视图点击设置)—— 否则"对着指定对象增删改"无从指定。
  4. 确认新建/删除后谁负责刷新界面(VZ 重扫模型?动作里带 invalidate?)。
  5. 最后才做面板 UI:对象树列表 + 新建/删除/改名按钮。