From f696c1567165ea7d39769caff676cea295b168e6 Mon Sep 17 00:00:00 2001 From: tianmo Date: Fri, 16 Oct 2026 17:52:04 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=8C=89=E9=92=AE=20+10=20Re?= =?UTF-8?q?d=20Bones=EF=BC=9A=E5=9C=A8=E5=BD=93=E5=89=8D=E9=80=89=E4=B8=AD?= =?UTF-8?q?=E9=AA=A8=E9=AA=BC=E4=B8=8B=E7=94=A8=E7=BC=96=E8=BE=91=E5=99=A8?= =?UTF-8?q?=E8=87=AA=E5=B7=B1=E7=9A=84=20New=20Bone=20=E5=BB=BA=2010=20?= =?UTF-8?q?=E6=A0=B9=EF=BC=8C=E4=BD=8D=E7=BD=AE=E9=9A=8F=E6=9C=BA=EF=BC=88?= =?UTF-8?q?=C2=B1200=EF=BC=89=EF=BC=8C=E9=A2=9C=E8=89=B2=E5=86=99=20red?= =?UTF-8?q?=EF=BC=88Gp=20=E7=9A=84=20x/y=20=E8=B5=B0=20names.tsv=20?= =?UTF-8?q?=E5=8F=8D=E6=9F=A5=E3=80=81color=20=E6=8C=89=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E6=89=BE=E5=AD=97=E6=AE=B5=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- viewpanel/src/com/spineview/ObjectPanel.java | 148 +++++++++++++++++-- 1 file changed, 137 insertions(+), 11 deletions(-) diff --git a/viewpanel/src/com/spineview/ObjectPanel.java b/viewpanel/src/com/spineview/ObjectPanel.java index 8e5dbf1..424afeb 100644 --- a/viewpanel/src/com/spineview/ObjectPanel.java +++ b/viewpanel/src/com/spineview/ObjectPanel.java @@ -129,9 +129,11 @@ public final class ObjectPanel { Object rename = button(loader, skin, "Rename"); Object inspect = button(loader, skin, "Inspect"); Object setValue = button(loader, skin, "Set Value"); + Object addTen = button(loader, skin, "+10 Red Bones"); // 按钮直接放进根表格:两行按钮 + 一行滚动列表(与阶段四验证过的结构一致) - Object[] top = {refresh, newBone, newSlot, newBBox, newPath, delete, rename, inspect, setValue}; + Object[] top = {refresh, newBone, newSlot, newBBox, newPath, delete, rename, inspect, setValue, + addTen}; Object buttons = tableClass.getConstructor().newInstance(); call(buttons, Obf.TABLE_TOP, new Class[0], tableClass); call(buttons, Obf.TABLE_LEFT, new Class[0], tableClass); // 按钮块靠左上(不然整宽后会居中) @@ -181,6 +183,12 @@ public final class ObjectPanel { System.err.println("[panel] 设置中段高度失败: " + error); } + wire(loader, addTen, new Runnable() { + @Override + public void run() { + createTenRedBones(state); + } + }); wire(loader, setValue, new Runnable() { @Override public void run() { @@ -618,6 +626,109 @@ public final class ObjectPanel { thread.start(); } + /** + * 在当前选中的骨骼下新建 10 根骨骼:位置随机、颜色红色。 + * 建用编辑器自己的 New Bone(带撤销),建完立刻改该骨骼的 x/y 与 color 字段。 + */ + private static void createTenRedBones(final State state) { + SceneActions.onEditorThread(state.loader, new Runnable() { + @Override + public void run() { + Object parent = state.target; + if (parent == null) { + setStatus(state, "+10 Red Bones: select a parent bone first."); + return; + } + try { + Class colorClass = Class.forName("com.badlogic.gdx.graphics.Color", true, state.loader); + Object red = colorClass.getConstructor(float.class, float.class, float.class, + float.class).newInstance(Float.valueOf(1f), Float.valueOf(0f), Float.valueOf(0f), + Float.valueOf(1f)); + int made = 0; + for (int i = 0; i < 10; i++) { + java.util.List before = SceneObjects.listAllNodes(state.loader); + SceneActions.create(state.loader, parent, "Bone"); + Object fresh = firstNew(before, SceneObjects.listAllNodes(state.loader)); + if (fresh == null) continue; + float x = (float) (Math.random() * 400.0 - 200.0); + float y = (float) (Math.random() * 400.0 - 200.0); + setBoneNumber(fresh, "x", x); + setBoneNumber(fresh, "y", y); + setBoneColor(fresh, red); + made++; + } + setStatus(state, "made " + made + " red bones under " + SceneObjects.nameOf(parent)); + } catch (Throwable error) { + setStatus(state, "+10 Red Bones failed: " + error); + error.printStackTrace(System.err); + } + refreshList(state); + } + }); + } + + /** 前后列表对比,取第一个新出现的对象。 */ + private static Object firstNew(java.util.List before, java.util.List after) { + for (Object item : after) { + boolean seen = false; + for (Object old : before) { + if (old == item) { + seen = true; + break; + } + } + if (!seen) return item; + } + return null; + } + + /** 按"真名"(x/y/rotation…)改骨骼上的 float 字段:真名→混淆名走 names.tsv。 */ + private static void setBoneNumber(Object bone, String realName, float value) { + try { + String obf = obfNameOf("Gp", "F", realName); + if (obf == null) return; + for (Class type = bone.getClass(); type != null && type != Object.class; + type = type.getSuperclass()) { + for (java.lang.reflect.Field field : type.getDeclaredFields()) { + if (!field.getName().equals(obf) || field.getType() != float.class) continue; + field.setAccessible(true); + field.setFloat(bone, value); + return; + } + } + } catch (Throwable error) { + System.err.println("[panel] 设置 " + realName + " 失败: " + error); + } + } + + /** 骨骼颜色:找类型是 libGDX Color 的字段写进去。 */ + private static void setBoneColor(Object bone, Object color) { + try { + for (Class type = bone.getClass(); type != null && type != Object.class; + type = type.getSuperclass()) { + for (java.lang.reflect.Field field : type.getDeclaredFields()) { + if (!field.getType().getName().equals("com.badlogic.gdx.graphics.Color")) continue; + field.setAccessible(true); + field.set(bone, color); + return; + } + } + } catch (Throwable error) { + System.err.println("[panel] 设置颜色失败: " + error); + } + } + + /** 映射表反查:owner + 描述符 + 真名 → 混淆名。 */ + private static String obfNameOf(String owner, String desc, String realName) { + loadNames(); + for (java.util.Map.Entry entry : NAMES.entrySet()) { + if (!entry.getValue().equals(realName)) continue; + String[] parts = entry.getKey().split("#"); + if (parts.length == 3 && parts[0].equals(owner) && parts[2].equals(desc)) return parts[1]; + } + return null; + } + private static int countFields(Object object) { int count = 0; for (Class type = object.getClass(); type != null && type != Object.class; @@ -1261,16 +1372,31 @@ public final class ObjectPanel { StringBuilder out = new StringBuilder(); out.append("buttons: ").append(buttonLabels()).append('\n'); press(0, out); // Refresh - out.append("after Refresh: ").append(rows(loader)).append('\n'); - // 列表是 Refresh 之后才建出来的,找到 "bone bone3" 那一行并按它 - press(panelButton("bone bone3"), "row bone bone3", out); - out.append("after select: ").append(rows(loader)).append('\n'); - press(2, out); // New Slot(会弹输入框,看面板能不能自己填+点 OK) - sleepQuiet(3000); - out.append("after New Slot: ").append(rows(loader)).append('\n'); - press(3, out); // New BBox - sleepQuiet(3000); - out.append("after New BBox: ").append(rows(loader)).append('\n'); + // 直接"选中 bone3 当父级"(列表行的标签取不到,就程序化选中) + press(9, out); // +10 Red Bones(未选父级时应提示) + sleepQuiet(1000); + Object parent = null; + try { + parent = SceneActions.findByName(loader, "bone3"); + } catch (Throwable error) { + out.append("find bone3 failed: ").append(error).append('\n'); + } + out.append("parent = ").append(parent == null ? "null" : SceneObjects.nameOf(parent)).append('\n'); + if (parent != null) { + final Object target = parent; + SceneActions.onEditorThread(loader, new Runnable() { + @Override + public void run() { + SceneActions.select(loader, target); + } + }); + // 让 state.target 记住它(正常是点列表行设置的) + Object st = LAST_STATE; + if (st instanceof State) ((State) st).target = parent; + } + press(9, out); // 现在应当真的建 10 根 + sleepQuiet(4000); + out.append("after +10 Red Bones: ").append(rows(loader)).append('\n'); return out.toString(); }