- 交互层:工具注册(Create Tool 等)与视口交互入口 - 层级树:VZ(视图) / uo_2(节点) / xw_2(骨架) / gp_2(骨) / acK(插槽) 包装模型 - 当前选择:og_1.D.D 的读取点与菜单动作的依赖关系 - 数据层:所有 New X 都落到 KP.B(父级, 类型名),以及 KP 真实类名待解 - 改删查命令清单、撤销注意点、skeleton/root 删除边界、下一步 5 条
5.9 KiB
编辑器交互逻辑 & 数据 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。
六、下一步(按顺序)
- 解出
KP的真实类名(indy 调用映射 / 运行时按签名搜LPt4.mc),确认B(Object,String)的描述符。 - 确认"父级对象"参数的确切类型:
xw_2.D(skeleton 节点里的骨架数据)、acK.D(slot 节点里的数据)。 - 搞清选择怎么设置(
og_1.D.D是可直接写的字段,还是只能由视图点击设置)—— 否则"对着指定对象增删改"无从指定。 - 确认新建/删除后谁负责刷新界面(
VZ重扫模型?动作里带 invalidate?)。 - 最后才做面板 UI:对象树列表 + 新建/删除/改名按钮。