场景对象增删改查调研:命令系统入口 + 新建对象的真实落点
- Commands.java:Editor -> pE(命令系统) -> void D(String) 按名字执行命令 - Launcher:新增 -Dspine.runCommand="<命令名>" 便于验证 - SceneReader:Tree/选中项探针(actor 树按 SnapshotArray 结构遍历) - 混淆名对照表.md:补命令系统一节、可执行命令名单、skeleton/root 删除边界、 以及 New X 最终落到静态 KP.B(target, 类型名) 的结论与待办
This commit is contained in:
1 parent
8f7b72271f
commit
a0080428f9
4 files changed
+394
-7
No files matched your search
@@ -0,0 +1,88 @@
|
||||
package com.spineview;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
/**
|
||||
* 用**编辑器自己的命令系统**执行菜单动作 —— 增删改查都从这里走。
|
||||
*
|
||||
* 链路(都是从反编译源码里读出来的,见 `混淆名对照表.md`):
|
||||
* ```
|
||||
* com.esotericsoftware.spine.editor.Editor (OG.D 里的单例)
|
||||
* └─ public pE D; ← 命令 / 热键系统(Editor.<init> 里 new)
|
||||
* ├─ public final void D(String) ← 按名字执行(名字就是 hotkeys.txt 里的名字)
|
||||
* └─ public final pC D(String) ← 按名字取菜单项(不执行)
|
||||
* ```
|
||||
* 可用的名字直接看 `~/Library/Application Support/Spine/hotkeys.txt`,例如:
|
||||
* `New Bone` / `New Slot` / `New Bounding Box` / `New Path` / `New Clipping` /
|
||||
* `New IK Constraint` / `New Animation` / `New Event` / `New Folder` /
|
||||
* `Delete` / `Rename` / `Duplicate` / `Cut` / `Copy` / `Paste` / `Set Parent` /
|
||||
* `Draw Order Up` / `Draw Order Down` / `Visibility` / `Expand` / `Collapse` …
|
||||
*
|
||||
* 注意:这些动作都作用在**编辑器当前的选择**上(Delete/Rename/Duplicate 等),
|
||||
* 所以后续要先能设置选择(Tree 的选中项),才谈得上"对着哪个对象增删改"。
|
||||
*/
|
||||
public final class Commands {
|
||||
|
||||
/** 命令系统的载荷类名(CFR 把它重命名成 pe_0,真实类名是 pE)。 */
|
||||
private static final String COMMANDS = "pE";
|
||||
|
||||
private Commands() {
|
||||
}
|
||||
|
||||
/** 从编辑器单例上取命令系统对象(Editor 的实例字段)。 */
|
||||
public static Object find(ClassLoader loader) throws Exception {
|
||||
Object editor = SceneReader.findEditor(loader);
|
||||
if (editor == null) return null;
|
||||
for (Class<?> type = editor.getClass(); type != null && type != Object.class;
|
||||
type = type.getSuperclass()) {
|
||||
for (Field field : type.getDeclaredFields()) {
|
||||
if (Modifier.isStatic(field.getModifiers())) continue;
|
||||
if (!field.getType().getName().equals(COMMANDS)) continue;
|
||||
field.setAccessible(true);
|
||||
Object value = field.get(editor);
|
||||
if (value != null) return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行一条命令(名字取自 hotkeys.txt / 菜单项)。
|
||||
* 返回 false 表示没找到命令或执行抛了异常(异常已经打进日志)。
|
||||
*/
|
||||
public static boolean run(ClassLoader loader, String name) {
|
||||
try {
|
||||
Object commands = find(loader);
|
||||
if (commands == null) {
|
||||
System.err.println("[cmd] 没找到命令系统(未拿到 Editor 实例?)");
|
||||
return false;
|
||||
}
|
||||
// pE 上有一个参数的 String 方法有四个(返回 pC / void / ArrayList / String),
|
||||
// 只有返回 void 的那个是"执行"。
|
||||
Method execute = null;
|
||||
for (Method method : commands.getClass().getMethods()) {
|
||||
if (!method.getName().equals("D")) continue;
|
||||
if (method.getParameterTypes().length != 1) continue;
|
||||
if (method.getParameterTypes()[0] != String.class) continue;
|
||||
if (method.getReturnType() != void.class) continue;
|
||||
execute = method;
|
||||
break;
|
||||
}
|
||||
if (execute == null) {
|
||||
System.err.println("[cmd] 命令系统上没找到 void D(String)");
|
||||
return false;
|
||||
}
|
||||
System.err.println("[cmd] 执行命令: " + name);
|
||||
execute.setAccessible(true);
|
||||
execute.invoke(commands, name);
|
||||
System.err.println("[cmd] 命令完成: " + name);
|
||||
return true;
|
||||
} catch (Throwable error) {
|
||||
System.err.println("[cmd] 命令失败: " + name + " -> " + error);
|
||||
error.printStackTrace(System.err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user