From 2b23ce59db7d7bcf6e992946c75c2b950c94ae61 Mon Sep 17 00:00:00 2001 From: tianmo Date: Fri, 16 Oct 2026 14:22:48 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E3=80=8A=E7=BC=96=E8=BE=91?= =?UTF-8?q?=E5=99=A8=E4=BA=A4=E4=BA=92=E4=B8=8E=E6=95=B0=E6=8D=AEAPI?= =?UTF-8?q?=E3=80=8B=E8=B0=83=E7=A0=94=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 交互层:工具注册(Create Tool 等)与视口交互入口 - 层级树:VZ(视图) / uo_2(节点) / xw_2(骨架) / gp_2(骨) / acK(插槽) 包装模型 - 当前选择:og_1.D.D 的读取点与菜单动作的依赖关系 - 数据层:所有 New X 都落到 KP.B(父级, 类型名),以及 KP 真实类名待解 - 改删查命令清单、撤销注意点、skeleton/root 删除边界、下一步 5 条 --- 编辑器交互与数据API.md | 123 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 编辑器交互与数据API.md diff --git a/编辑器交互与数据API.md b/编辑器交互与数据API.md new file mode 100644 index 0000000..b7d167d --- /dev/null +++ b/编辑器交互与数据API.md @@ -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:对象树列表 + 新建/删除/改名按钮。