新增《编辑器交互与数据API》调研文档
- 交互层:工具注册(Create Tool 等)与视口交互入口 - 层级树:VZ(视图) / uo_2(节点) / xw_2(骨架) / gp_2(骨) / acK(插槽) 包装模型 - 当前选择:og_1.D.D 的读取点与菜单动作的依赖关系 - 数据层:所有 New X 都落到 KP.B(父级, 类型名),以及 KP 真实类名待解 - 改删查命令清单、撤销注意点、skeleton/root 删除边界、下一步 5 条
This commit is contained in:
1 parent
a0080428f9
commit
2b23ce59db
1 file changed
+123
+123
@@ -0,0 +1,123 @@
|
||||
# 编辑器交互逻辑 & 数据 API 调研
|
||||
|
||||
目标:在动手写"面板里对场景对象增删改查"之前,先把编辑器**用户交互链路**和**真正生成数据的代码**摸清楚。
|
||||
本文只记录查到的事实 + 出处 + 还没弄清的地方。(混淆名 ⇒ 真实名见 [`混淆名对照表.md`](混淆名对照表.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)点下去做的事:
|
||||
|
||||
```java
|
||||
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` 固定根。
|
||||
|
||||
### 当前选择放在哪
|
||||
|
||||
反编译源码里到处是这种读取:
|
||||
|
||||
```java
|
||||
et_0 sel = og_1.D; // 编辑器上下文单例
|
||||
sel = sel.D; // ← 当前选择对象(菜单动作都作用在它上面)
|
||||
... og_1.D.D.D // 再深一层(视图/工具相关)
|
||||
```
|
||||
|
||||
`mn_1`(New Bone)的判断就依赖它:
|
||||
|
||||
```java
|
||||
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` 最后都落到**同一个方法**:
|
||||
|
||||
```java
|
||||
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:对象树列表 + 新建/删除/改名按钮。
|
||||
Reference in new issue
Block a user