commit 5511354783f4c7654b3b4cd084b2326bd2f8dd3d Author: lanyizi Date: Sun Jun 14 01:40:04 2026 +0200 first commit diff --git a/.reasonix/desktop-topic-created-at.json b/.reasonix/desktop-topic-created-at.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/.reasonix/desktop-topic-created-at.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/.reasonix/desktop-topic-title-sources.json b/.reasonix/desktop-topic-title-sources.json new file mode 100644 index 0000000..e98e07a --- /dev/null +++ b/.reasonix/desktop-topic-title-sources.json @@ -0,0 +1,5 @@ +{ + "topic_20260611-122932_3dd9a4f50841d56f": "auto", + "topic_20260611-131043_9d22f19ddc2b7f3b": "auto", + "topic_20260611-223643_2b5c3d6c3243701e": "manual" +} \ No newline at end of file diff --git a/.reasonix/desktop-topic-titles.json b/.reasonix/desktop-topic-titles.json new file mode 100644 index 0000000..a51e626 --- /dev/null +++ b/.reasonix/desktop-topic-titles.json @@ -0,0 +1,5 @@ +{ + "topic_20260611-122932_3dd9a4f50841d56f": "Plan mode — read-o…", + "topic_20260611-131043_9d22f19ddc2b7f3b": "Plan mode — read-o…", + "topic_20260611-223643_2b5c3d6c3243701e": "Plan mode — read-o… · 分叉" +} \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..5ac693b --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,187 @@ +# AGENTS.md + +本项目工作:修改 RA3 UI(APT 文件)。 + +## 1. 概述 + +APT 是 RA3 的 Flash-like UI 二进制格式。修改 APT 由两类工作组成: + +- **修改布局和素材**:调整位置、大小、纹理、角色(shape/sprite/button) +- **修改脚本/逻辑**:改写 bytecode(汇编),或通过钩子引入外部 ActionScript + +### edit.xml 是什么 + +`_edit.xml` 是 `.apt` 二进制反编译后的**可编辑 XML 版本**。 +修改 `_edit.xml` 后需用工具编译回 `.apt`。 + +### 两种生成 APT 的路径 + +| 路径 | 适用 | 能写 AS? | +|---|---|---| +| **已有 APT → edit.xml → 修改 → 编译回 APT** | 修改现有 UI | 只能写汇编(bytecode) | +| **新 FLA → SWF → APT** | 创建全新 UI | 可在 Flash 中直接写 ActionScript | + +> 已有 APT **无法逆向转回 FLA/SWF**。这是"为什么必须操作 edit.xml"和"为什么钩子模式有价值"的根本原因。 + +--- + +## 2. 开始前 + +### 必读 + +**APT修改指南.md** — CDATA 格式、指令句法、ru 公式、placeobject flags —— **必须先读**。 + +### 分析流程 + +修改任何 APT 前,按以下顺序分析: + +1. **读反编译 AS** → 理解类结构、方法、逻辑流 + - `Data/decompiled-as-references//scripts/__Packages/<类名>.as` + - 共享类在 `Data/decompiled-as-references/main_mouse/scripts/`(如 `std_mouseRadioButton`、`mouse_chapterSelectComponent`) +2. **读 edit.xml** → 布局、角色(image/shape/sprite/button)、placeobject、bytecode +3. **跨文件追踪** → `export`/`import` 关系:一个 APT 导出的 character 可被其他 APT 引用; + 运行时 `attachMovie()` 按符号名解析——只要有一条 import 链可达即可 +4. **请求原始参考 APT** → 推断 tx 语义、对齐关系必须对比未修改版; + 请求用户提供原始 APT(如本项目中的 `Data/aptui_ref/`) +5. **从原始数据推算** → 不假设 tx 是左边缘或中心,用 shape 尺寸交叉验证; + 按钮与背景图对齐关系需从原始坐标推算 + +--- + +## 3. 工具 + +编辑后需要用 `ra3apt-cli` 编译: + +```sh +# 反编译 .apt → edit.xml +ra3apt-cli expand + +# 编译 edit.xml → .apt +ra3apt-cli build +``` + +> 工具有可能不在当前工作区;如果可执行,用以上命令转换(生成的文件会替换已有文件);如果不可用,提醒用户手动操作 + +### 格式规则 + +- 所有 bytecode(``、``、``)使用 `` 包裹 +- CDATA 内 `&` 等字符**无需** XML 实体转义(写 `&Outline`,非 `&Outline`) +- 修改后用 grep 验证关键值(坐标、depth、label) + +--- + +## 4. 修改布局和素材 + +### depth 规则 + +- `placeobject` 的 `depth` 控制 Z 序(渲染和点击优先级) +- 新增元素前必须 grep 验证该 depth 未被同一帧的其他 placeobject 占用 + +### 单选按钮(std_mouseRadioButton) + +- `m_width` **不控制视觉宽度**(只用于 `labelTF._x` 文本居中) +- 缩放用 `rotm00`(宽)和 `rotm11`(高)。缩放后 `ty` 需补偿垂直偏移: + ``` + new_ty = old_ty + old_visual_height × (1 − scale) / 2 + ``` +- mouse_chapterSelectComponent 的特殊情况(不一定适用于其他 APT 文件):按钮与背景图是独立层,对齐偏移需从原始坐标推算 + +### 纹理 ru + +``` +tex_x = m00 × screen_x + tx +tex_y = m11 × screen_y + ty +``` + +- `t` 命令中坐标为**屏幕坐标** +- 矩阵为单位矩阵(`1:0:0:1`)时简化为 `tex = screen + tx` +- 移动屏幕三角形、保持采样同一纹理区域: + `new_tx = tex_left − new_screen_left` +- 缩小屏幕三角形后要**完整显示纹理**(非裁切): + `m00 = tex_width / new_width`,再重算 `tx` +- 多个 shape 可从同一张 `.tga` 图集的不同坐标区域采样(texture atlas 模型) + +### Content Sprite 结构 + +- 使用帧标签定义状态:`_disable` / `_selected` / `_unselected` / `_selectedRollover` / + `_unselectedRollover` / `_downState` / `_intro` / `_outtro` +- 须包含 `_parent.introComplete()` 和 `_parent.outtroComplete()` 回调 +- 新增 sprite 时**必须从同一 APT 现有 content sprite 复制完整帧结构**(帧数可能不同) + +--- + +## 5. 修改脚本/逻辑 + +### `_global` 通信总线 + +- 所有 APT 和外部 AS 共享同一个 `_global` 命名空间 +- 一个 APT 中定义的全局变量(如 `_global.ENUM.XXX`)可在其他 APT 的 bytecode 中读取 +- 这是钩子模式能成立的基础 + +### this 的含义 + +- `Cafe_BaseUIScreen` 子类的 initaction 中,`this` 是**管理类实例**,不是 MovieClip +- MovieClip 通过 `this.m_screen` 或 `Cafe2_BaseUIScreen.m_screen` 访问 +- 写 bytecode 或钩子函数都需区分两者 + +### 汇编调用与寄存器 + +`Cafe_BaseUIScreen` 子类方法中:`reg1` = `this`,`reg2` = `_global`。 + +| 操作 | 格式 | +|---|---| +| 调函数 | `...args → push nArgs → funcObj → pushundefined → callmethodpop` | +| 调方法 | `...args → push nArgs → obj → callnamedmethodpop "methodName"` | + +- 禁止 `callnamedpop "call"` — 等效 `func["call"]()`,这在 APT 中不被支持 +- 新增 label 名用 grep 验证不与已有冲突 + +### 汇编 vs 钩子:决策 + +| 情况 | 做法 | +|---|---| +| 改动少且机械(复制现有模式) | **直接写汇编**在 edit.xml 中 | +| 改动复杂(多分支、循环) | **用钩子** | +| 目标类在不可编辑的 APT 中(无 `_edit.xml`) | **只能用钩子** | + +### 钩子详解 + +**原理**:`_edit.xml` 中写少量检测 bytecode,实际逻辑写在**新 FLA→SWF→APT** 的 ActionScript 中, +通过 `_global` 变量传递。 + +**具体步骤**: + +1. 在可编辑 screen 的 `initGUI()` 末尾插入以下 bytecode(位置:`registerIntroOuttroComponents()` / `startIntro()` 调用之后、`end` 之前): + +```as +// 等效 AS:if (_global.gHookName != undefined) { _global.gHookName(this); } +pushglobal +getnamedmember "gHookName" +pushundefined +ActionNewEquals // != undefined ? +ActionLogicalNot +ActionLogicalNot +jumptrue lbl_skip +pushreg 1 // this(管理类实例) +EAPushOne // 1 个参数 +pushglobal +getnamedmember "gHookName" +pushundefined // 直接调用,非方法查找 +callmethodpop +lbl_skip: +``` + +2. 在新 FLA 的 ActionScript 中编写处理函数,赋值给 `_global.gHookName` + +3. 钩子函数接收的 `this` 是**管理类实例**(`Cafe_BaseUIScreen` 子类),不是 MovieClip + +> 初始化顺序:`frameDelayedInit() → initGUI() → registerIntroOuttroComponents() → startIntro()` + +若有需要可以为 `` 添加 `` 从而让 ActionScript 能够访问对应 MovieClip 并进行操作(例如`parent.xxx._y = 100`),但不作为默认要求。 + +--- + +## 6. 输出给用户 + +- 需用户配合的内容(引擎常量、本地化 key、外部 AS 代码)→ 输出为独立的 `.md` 指南文件 +- 指南给出可直接使用的代码/值,而非笼统描述 diff --git a/APT修改指南.md b/APT修改指南.md new file mode 100644 index 0000000..a5a2e24 --- /dev/null +++ b/APT修改指南.md @@ -0,0 +1,1736 @@ +# APT / edit.xml 修改指南 + +> **目标读者**:完全不了解 APT 的人。阅读本指南后应能理解 APT 的结构和内容,并能准确无误地修改 edit.xml 来实现自己的需求。 + +--- + +## 1. APT 概述 + +### 1.1 什么是 APT + +APT 是红色警戒3(RA3)使用的 UI 系统,基于 **Flash 7(ActionScript 2.0)** 修改而来。它的核心文件是 `.apt` 二进制文件,游戏运行时加载这些文件来渲染用户界面。 + +APT 中的内容与 Flash SWF 高度类似——包含 MovieClip、Button、Shape(矢量图形)、Image、EditText(文本字段)等"角色"(character),以及 ActionScript 代码来驱动 UI 交互逻辑。 + +### 1.2 文件生态系统 + +每个 APT UI 模块由 **多个文件** 组成。以 `fes_m_factionMenu`(阵营选择界面)为例: + +| 文件 | 说明 | +|---|---| +| `fes_m_factionMenu.apt` | 二进制 APT 文件,游戏实际加载的文件 | +| `fes_m_factionMenu_edit.xml` | **可编辑的 XML 文件**,外部工具(AptToXML)从 .apt 生成,修改后经外部工具(XMLToApt)转回 .apt | +| `fes_m_factionMenu.const` | 常量池文件(二进制),存储字符串等常量 | +| `fes_m_factionMenu.dat` | 数据映射文件,将 character ID 映射到素材(纹理等) | +| `fes_m_factionMenu.tac` | ActionScript 文本表示(可读的汇编格式),**仅作参考、不直接编辑** | +| `fes_m_factionMenu.eaf` | EA 修改版的 Flash 汇编格式(二进制) | +| `fes_m_factionMenu.flm` | 工具链日志文件(记录编译/反编译信息) | +| `fes_m_factionMenu.xml` | 资产声明文件(AssetDeclaration),声明 .apt/.const/.dat 及纹理等依赖 | +| `fes_m_factionMenu_geometry/` | 矢量图形几何数据目录,包含 `.ru` 文件(render units,渲染单元) | +| `fes_m_factionMenu_textures/` | 纹理素材目录 | +| `fes_m_factionMenu_sounds/` | 音效目录 | +| `fes_m_factionMenu_videos/` | 视频目录 | +| `art/` | 源素材目录 | + +### 1.3 工具链流程 + +``` +.apt (二进制) ←→ AptToXML / XMLToApt 工具 ←→ edit.xml (可编辑) + ↕ ↕ +关联文件 (.const, .dat, .eaf 等) 手动编辑 edit.xml 后 + + 素材 (.tga 等) 通过工具重新生成 .apt + 和所有关联文件 +``` + +**核心流程**: +1. 用 AptToXML 工具从 `.apt` 生成 `_edit.xml` +2. 修改 `_edit.xml` 的内容 +3. 用 XMLToApt 工具从 `_edit.xml` 重新生成 `.apt` 及所有关联文件 +4. 生成的 `.apt` 供游戏使用 + +> **重要**:`.const`、`.dat`、`.eaf`、`.tac`、geometry `.ru` 文件等**均由工具自动生成**,你只需要修改 edit.xml 即可。不需要手动编辑这些文件。 + +> **格式说明**:edit.xml 中所有 bytecode 内容(``、``、``)均使用 `` 包裹。这是 XML CDATA 段,确保 bytecode 中的特殊字符(如 `<`、`>`、`&`)不被 XML 解析器误解。在 CDATA 段中所有字符按字面处理,无需使用 `&` 等 XML 实体转义。 + +### 1.4 与 ActionScript 2.0 SWF 的关系 + +APT 源自 Flash 7,因此与 AS 2.0 SWF 有大量相似之处: + +- **概念层面**:MovieClip、Sprite、帧、时间线、depth(深度/层级)、placeobject、removeobject 等概念直接对应 +- **代码层面**:使用 ActionScript 2.0 语法和语义(类、继承、函数、变量等) +- **差异**:APT 使用 **EA 修改版 bytecode**(自定义的操作码如 `EAPushOne`、`setstrvar` 等),部分标准 Flash API 在游戏中被裁剪 + +更多细节见 [第9章:AS 2.0 SWF → APT 知识迁移](#9-as-20-swf--apt-知识迁移)。 + +### 1.5 RA3 UI 模块命名前缀 + +| 前缀 | 含义 | +|---|---| +| `fe` | 公共 UI 组件/资源 | +| `feg` | 公共游戏逻辑 | +| `fes` | 单人游戏(Singleplayer)专属 | +| `fem` | 多人游戏(Multiplayer)专属 | +| `AXX` | 盟军战役 | +| `SXX` | 苏联战役 | +| `JXX` | 旭日(日本)战役 | + +> **反编译代码参考**:如果需要理解 ActionScript 逻辑的详细实现,可以查阅 `Data/decompiled-as-references/` 目录中的反编译 `.as` 文件。其中 `main_mouse` 是整个 APT 系统的入口模块——它初始化了全局常量(`_global.gSM`、`_global.gFM` 等)、UI 框架(Cafe2)和全局类注册。 + +### 1.6 screen APT 与 assets APT 拆分 + +从 `fem_m_gameSetup` 和 `fe_m_assetsGameSetup` 可以看到一种常见设计模式:一个 APT 是实际的 UI screen(画面),另一个 APT 是组件/素材库(assets)。 + +`fem_m_gameSetup` 的 imports 中有: + +```xml + + +``` + +而 `fe_m_assetsGameSetup` 会导出这些可复用组件: + +```xml + + + + +``` + +这意味着修改 UI 时,**不一定只改该 screen 自己的 `edit.xml`**。如果顶层 screen 只是导入并摆放一个外部组件,真正的子控件布局、实例名(poname)和 content sprite 可能定义在 assets APT 中。修改前要先判断目标在 screen APT 里,还是在被导入的 assets APT 里。 + +--- + +## 2. edit.xml 顶层结构 + +### 2.1 整体 XML 树 + +``` + + + + + ... + + + + ... + + + + + + ... + + + + + + + + + + ... + + + ... + + + + + + + + + + +``` + +### 2.2 `` — 主时间线 + +顶层的 `` 表示主时间线,包含以下部分: + +- **`screensizex` / `screensizey`**:屏幕尺寸(像素),如 1365×768 +- **`unknown`**:未知属性,始终为 `"33"`(保持原值即可) +- **``**:从其他 APT 导入的角色声明 +- **``**:导出给其他 APT 使用的角色声明 +- **``**:帧列表,通常只有一个 `frame id="0"`,所有内容(placeobject、action、initaction)都在此帧中 + +### 2.3 `` — 导入角色 + +从其他 APT 文件中导入角色(character)以供本 APT 使用: + +```xml + + + + +``` + +每个 `` 的属性: +- **`name`**:导入角色在原 APT 中的名称 +- **`movie`**:源 APT 文件路径(相对路径,不含扩展名) +- **`character`**:导入后在本 APT 中使用的 **character ID** + +### 2.4 `` — 导出角色 + +将本 APT 中定义的角色导出,供其他 APT 通过 import 引用: + +```xml + + + + + + + + +``` + +- **`character`**:本 APT 中定义的 character ID +- **`name`**:导出的名称。注意 `__Packages.xxx` 是 ActionScript 类的完全限定名,导出后其他 APT 通过 import 可以实例化这些类 + +> **关键**:每个 APT 通常导出 `__Packages.Cafe2_BaseUIScreen`(基类)和 `__Packages.<本模块名>`(本模块类),这些导出使得其他 APT 可以使用相同的基类和共享 UI 框架。 + +### 2.5 `` — 占位符 + +```xml + + + + + +``` + +`` 元素与 `` 中的 import **严格一一对应**。每个 import 的 character ID 都需要一个对应的 `` 元素来声明占位。 + +> **注意**:`` 的数量和 ID 必须与 `` 保持一致。添加新 import 时必须添加对应的 ``,删除 import 时必须删除对应的 ``。 + +--- + +## 3. 角色类型详解 + +APT 中的所有角色(character)都有一个唯一的整数 **ID**(如 `id="7"`)。角色 ID 在整个 APT 文件中必须唯一(包括 import 的角色 ID)。 + +### 3.1 movieclip 与 sprite + +**movieclip** 和 **sprite** 本质上都是 ActionScript 中的 MovieClip——可以包含帧、帧标签、子对象。 + +在 edit.xml 中: +- **``** 特指**顶层主时间线**(每个 APT 只有一个) +- **``** 指被定义的子 MovieClip(可以有多个) + +两者结构几乎相同,都有 ``: + +```xml + + + + + + + + + + + + + + + +``` + +#### sprite 的帧标签命名约定 + +许多 sprite 使用特殊命名的帧标签来配合 UI 框架的状态机: + +| 帧标签 | 含义 | +|---|---| +| `_disable` | 禁用状态 | +| `_selected` | 选中状态 | +| `_unselected` | 未选中状态 | +| `_selectedRollover` | 选中 + 鼠标悬停 | +| `_unselectedRollover` | 未选中 + 鼠标悬停 | +| `_downState` | 鼠标按下状态 | +| `_intro` | 入场动画 | +| `_outtro` | 出场动画 | +| `_unChecked` / `_checked` | checkbox 的选中/未选中状态 | + +这些帧标签被 Cafe2 UI 框架(RA3 的 UI 框架)通过 `gotoAndPlay("_selected")` 等方式调用,实现按钮、checkbox 等组件的状态切换。 + +#### sprite 的特殊帧 + +- **intro 帧**(如 `frame id="60"`)通常带有 `removeobject` 来重置上一状态的显示对象,然后播放过渡动画 +- **outtro 帧**(如 `frame id="87"`)通常在最后一帧(如 `frame id="119"`)调用 `this._parent.outtroComplete()` → `stop()` 来通知父级过渡完成,参考反编译代码: + ```as + // frame 119: this._parent.outtroComplete(); stop(); + ``` +- **select 帧**(如 `frame id="10"` 有 `_selected` 标签)通常从空帧过渡到含有新 placeobject 的帧 + +每个状态段的最后一帧通常有一个 `stop()` 动作,防止播放头继续前进。 + +### 3.2 button + +Button 是一个可点击的交互区域,由顶点(vertices)、三角形(triangles)和按钮记录(buttonrecords)组成: + +```xml + +``` + +**属性解释**: +- **`top` / `left` / `bottom` / `right`**:边界框 +- **``**:定义按钮区域的四个角点 +- **``**:将四个顶点组合为两个三角形(构成矩形区域) +- **``**:按钮在不同状态下的显示内容 + - **`flags`**:状态标志,可选值: + - `StateUp` — 正常/弹起状态 + - `StateOver` — 鼠标悬停状态 + - `StateDown` — 鼠标按下状态 + - `StateHit` — 点击测试区域(不可见,只用于检测鼠标点击的范围) + - **`character`**:该状态下显示的 character ID(通常是一个 shape) + - **`depth`**、**旋转/平移矩阵** 等参数与 placeobject 相同 + +#### button 的 hitRegion 与脚本绑定 + +在组件 sprite 内部,button 通常被放置在名为 `hitRegion` 的实例上。通用组件脚本(如 `std_mouseRadioButton.as`)通过固定实例名绑定鼠标事件: + +```as +this.m_contentClip.hitRegion.onRollOver = ...; +this.m_contentClip.hitRegion.onPress = ...; +this.m_contentClip.hitRegion.onRelease = ...; +``` + +因此: +- **修改点击范围**时应改 button 的 shape(`buttonrecord` 使用的 character)或放置时的矩阵,而不是只改可见图形 +- **扩大可点击区域**要改 hit shape 或 hit button 的 matrix,**不是**改可见 shape +- content sprite 中 depth 5 上的 button(`poname="hitRegion"`)使用了很大的缩放矩阵(如 `rotm00="2.820221" rotm11="11.699921"`),这是故意放大 hit 区域的做法 +- 如果 content sprite 中没有 `hitRegion` 实例,通用组件脚本将无法绑定鼠标事件,按钮将不可点击 + +### 3.3 shape(矢量图形) + +Shape 是矢量图形,通过 **ru**(render unit,渲染单元)命令定义绘制方式: + +```xml + + + c + s tc:255:255:255:255:art\Textures\apt_fes_m_factionMenu_1.tga:1:0:0:1:1368:563 + t 0:451:0:0:269:0 + t 269:0:269:451:0:451 + + +``` + +**属性解释**: +- **`top` / `left` / `bottom` / `right`**:边界框 +- **`geometry`**:外部 `.ru` 文件的路径。**此文件由工具自动生成**,你只需要修改 `` 标签内的内容即可 + +#### ru 命令详解 + +| 命令 | 名称 | 格式 | 说明 | +|---|---|---|---| +| `c` | Clear | `c` | 结束上一段几何图形,开始新的绘制 | +| `s` | Style | 见下方 | 设置绘制样式(填充色、线型、纹理等) | +| `t` | Triangle | `t x1:y1:x2:y2:x3:y3` | 绘制一个三角形 | +| `l` | Line | `l x1:y1:x2:y2` | 绘制一条线段 | + +#### s(Style)命令的变体 + +**1. 纯色填充三角形** — `s s:R:G:B:A` +``` +s s:0:0:0:0 +``` +绘制纯色填充的三角形,4 个数字分别为 R(红)、G(绿)、B(蓝)、A(透明度),范围 0-255。 + +示例:`s s:0:0:0:0` = 黑色;`s s:255:0:0:255` = 不透明红色。 + +**2. 线条样式** — `s l:thickness:R:G:B:A` +``` +s l:2:255:0:0:255 +``` +绘制线条,1 个 thickness(线宽) + 4 个 RGBA。 + +**3. 纹理填充三角形** — `s tc:R:G:B:A:textureId:m00:m01:m10:m11:tx:ty` +``` +s tc:255:255:255:255:art\Textures\apt_fes_m_factionMenu_1.tga:1:0:0:1:1368:563 +``` +参数说明: +- 4 个 RGBA 值(颜色调制) +- 1 个纹理引用(图像路径或 ID) +- 4 个 2×2 矩阵值(m00, m01, m10, m11)— 纹理变换 +- 2 个平移值(tx, ty)— 纹理偏移 + +> **纹理映射公式**:`tex_x = m00 × screen_x + m01 × screen_y + tx`,`tex_y = m10 × screen_x + m11 × screen_y + ty`。 +> 当矩阵为单位矩阵(`1:0:0:1`)时简化为 `tex = screen + tx`。 +> `t` 命令中的坐标为屏幕坐标。要采样纹理中的特定区域,需满足 `tx = tex_left − screen_left`。 + +#### 内联 ru 与外部 .ru 文件的区别 + +edit.xml 中同时存在两种方式: + +**方式一:内联 ru**(如 `fe_m_assetsGameSetup`) +```xml + + + c + s tc:255:255:255:255:art\Textures\apt_fe_m_assetsGameSetup_1.tga:1:0:0:1:737:1 + t 0:31:0:0:188:0 + t 188:0:188:31:0:31 + + +``` + +**方式二:仅引用外部**(如 `fes_m_factionMenu` 的部分 shape) +```xml + + + c + s tc:255:255:255:255:art\Textures\apt_fes_m_factionMenu_1.tga:1:0:0:1:1368:563 + t 0:451:0:0:269:0 + t 269:0:269:451:0:451 + + +``` + +> **关键**:两种方式下,`` 标签内的内容都会被写入到 `geometry` 属性指定的 `.ru` 文件中。**你只需要修改 edit.xml 中的 `` 内容即可**,外部的 `.ru` 文件会在工具转换时自动生成/更新。不需要手动编辑 `.ru` 文件。 + +两种方式只是工具链实现细节的差异,修改时操作完全一致——编辑 `` 标签内的命令即可。 + +#### 组合多个 ru 绘制 + +一个 shape 可以包含多段 `c ... s ... t` 序列来绘制复杂的图形。例如背景 shape `id="23"` 同时绘制了底层背景和上层纹理: + +```xml + + c + s tc:255:255:255:255:art\Textures\apt_fes_m_factionMenu_1.tga:1:0:0:1:1:1 + t 0:768:0:0:1365:0 + t 1365:0:1365:768:0:768 + c + s tc:255:255:255:255:art\Textures\apt_fes_m_factionMenu_2.tga:1:0:0:1:-183.45:-73.85 + t 184.45:685.85:184.45:74.85:1183.45:74.85 + t 1183.45:74.85:1183.45:685.85:184.45:685.85 + +``` + +### 3.4 image + +```xml + +``` + +Image 是最简单的角色类型,仅包含一个 `image` 属性指向纹理文件路径。引用同一个纹理的多个 image 可以有不同 ID,通过不同的 ru 绘制来显示纹理的不同区域。 + +> **图集注意事项**:在 RA3 APT 中,一个 `.tga` 文件往往是**图集**(atlas),不是一张只服务于单个控件的小图。例如 `apt_fes_m_factionMenu_1.tga` 被多个 shape 的不同区域共享使用(通过 `s tc` 中的 tx/ty 偏移从同一图集中取不同区域)。替换贴图时要确认: +> - 其他 shape 是否也采样同一 `.tga` 的其他区域 +> - 图集尺寸、坐标偏移、透明通道是否保持工具链可接受 +> - 资源声明 XML 中的 `OutputFormat`、`GenerateMipMaps`、`AllowAutomaticResize` 是否符合原样本 +> - 新增纹理时,需要在资产声明 XML 中添加 `` 条目 + +### 3.5 edittext(文本字段) + +```xml + + + + + + + +``` + +**属性解释**: +- **`top` / `left` / `bottom` / `right`**:文本边界框 +- **`font`**:引用的 `` 的 ID +- **`alignment`**:对齐方式(`0` = 左对齐,`1` = 右对齐,`2` = 居中) +- **`red` / `green` / `blue` / `alpha`**:文本颜色和透明度 +- **`height`**:字体大小 +- **`readonly`**:只读(`1`)或可编辑(`0`) +- **`multiline`**:多行(`1`)或单行(`0`) +- **`wordwrap`**:自动换行(`1`)或不换行(`0`) +- **``**:初始文本内容 +- **``**:绑定的变量名(通常为空) + +> **注意**:edittext 中出现的 `$VARIABLE_NAME` 格式的文本(如 `$SOVIET_FACTION_DESCRIPTION`)是 RA3 的本地化字符串引用,游戏运行时会替换为对应语言的实际文本。 + +#### 文本来源的三种情况 + +修改 edittext 的显示文本时,需要先判断文本的**运行时来源**,只改 `` 可能无效: + +**1. 静态 `ettext`**:文本直接写在 `` 中,且不会被 AS 覆盖。修改直接生效。 + +**2. AS2 运行时覆盖**:text 在脚本中被动态设置。例如 faction menu 中: +```as +factionDescriptionTF.text = "$SOVIET_FACTION_DESCRIPTION&Outline"; +factionDescriptionShadowTF.text = "$SOVIET_FACTION_DESCRIPTION"; +``` +这种情况下,修改 `` 的默认值没有意义——游戏运行时会被脚本覆盖。 + +**3. 本地化 key**:`$SOME_KEY` 格式的字符串由游戏引擎查找对应语言的实际文本。`&Outline` 等后缀是游戏 UI 的文本特效标记。新增 key 需要游戏侧支持。 + +> **阴影/正文双文本框**:RA3 UI 常常成对出现正文和阴影文本框(例如 `factionDescriptionTF` + `factionDescriptionShadowTF`)。只改其中一个会导致阴影与正文内容不一致。修改时必须两个一起改。 + +#### edittext 属性补充 + +- **`alignment`**:当前样本出现 `0` 和 `2`,可按左对齐/居中等理解,改前最好对照实际显示 +- **`etvar`**:变量绑定字段,当前样本多为空,但遇到非空值时要检查 AS 是否通过变量名驱动文本 +- **`height`**:字体高度,修改文本内容后需确认 `right/bottom` 边界是否足够容纳 + +### 3.6 font + +```xml + + +``` + +定义字体。`name` 指定字体名称,此字体必须在游戏环境中可用。 + +### 3.7 empty + +```xml + +``` + +`` 是占位符声明,与 `` 中的 import 条目严格一一对应。它声明了被导入角色在本 APT 命名空间中的存在。 + +> **规则**:每个 import 的 character ID 必须有一个对应的 ``。添加新 import 时要添加 ``,删除 import 时要删除 ``。 + +--- + +## 4. 帧与时间线 + +### 4.1 帧的基本结构 + +每个 `` 有一个整数 `id` 属性。帧按 id **递增顺序**排列。帧中可以包含以下元素(可同时出现多种): + +```xml + + + + ... + + + +``` + +### 4.2 `` + +```xml + + +``` + +设置帧的背景色。颜色为 32 位整数 ARGB 格式(`-1` = 0xFFFFFFFF = 白色)。通常只在主时间线帧 0 中出现。 + +### 4.3 `` — 放置角色 + +`` 是最核心的操作——将一个角色(character)放置在当前帧的指定深度(depth)。 + +```xml + + + + + + +``` + +#### placeobject 属性详解 + +| 属性 | 类型 | 说明 | +|---|---|---| +| `flags` | 枚举 | 控制哪些属性生效,见下方 flags 表 | +| `depth` | 整数 | **深度(层级)**,值越大越靠前。同一 depth 上的 placeobject 会替换之前帧中同 depth 的对象 | +| `character` | 整数 | 放置的角色的 ID | +| `rotm00` ~ `rotm11` | 浮点数 | 2×2 旋转/缩放矩阵(见下方矩阵说明) | +| `tx` / `ty` | 浮点数 | X/Y 平移(像素) | +| `red` / `green` / `blue` / `alpha` | 0-255 | 颜色调制和透明度 | +| `ratio` | 浮点数 | (未知语义)出现在 `_intro`/`_selectedRollover` 等过渡帧中 | +| `unknown` | 整数 | 未知属性,通常为 `"0"` | + +#### flags 枚举值 + +| Flag | 含义 | +|---|---| +| `HasCharacter` | `character` 属性有效 | +| `HasMatrix` | 旋转/平移矩阵属性有效(rotm00-rotm11, tx, ty) | +| `HasName` | `` 子元素有效(给角色命名,使其可被 ActionScript 访问) | +| `HasClipAction` | `` 子元素有效(为角色附加 ActionScript 初始化代码) | +| `HasColorTransform` | 颜色调制(red, green, blue, alpha)有效 | +| `HasRatio` | `ratio` 属性有效 | + +#### 2×2 变换矩阵 + +``` +rotm00 rotm01 +rotm10 rotm11 +``` + +- 恒等变换(无旋转无缩放):`rotm00="1" rotm01="0" rotm10="0" rotm11="1"` +- X 轴缩放 s:`rotm00="s"`;Y 轴缩放 s:`rotm11="s"` +- 标准缩放 + 平移:对象先缩放/旋转,再平移到 (tx, ty) + +#### `` — 给角色命名 + +```xml + +``` + +通过 `poname` 给放置的角色赋予一个实例名称(类似 Flash 中的 instance name),该名称可以在 ActionScript 中通过以下方式访问: + +```as +Cafe2_BaseUIScreen.m_screen.sovietFaction.show(); +``` + +> **注意**:实例名称在同一层级中必须唯一。 + +#### `` — 角色初始化代码 + +```xml + + + +``` + +`clipaction` 的含义取决于 `flags`: +- **`flags="Construct"`**:构造时执行(角色首次被放置时运行的初始化代码),设置角色的各种属性(如 `m_width`、`m_tabIndex`、`m_label` 等) + +该代码在角色实例化时执行,类似于给对象设置初始属性值。详见 [第5章 ActionScript 汇编参考](#5-actionscript-汇编参考)。 + +### 4.4 `` — 移除角色 + +```xml + +``` + +从当前帧开始,移除指定 depth 上的角色。常用于 sprite 的状态切换中清除上一状态的显示对象。例如: + +```xml + + + + + +``` + +### 4.5 `` — 帧标签 + +```xml + +``` + +给当前帧命名,ActionScript 可以通过 `gotoAndPlay("_disable")` 或 `gotoAndStop("_disable")` 跳转到此帧。AS 中也可以直接使用帧号。 + +`frame` 属性的值与 `` 一致。 + +### 4.6 `` — 帧动作 + +```xml + +``` + +帧动作在当前帧播放时执行。常见用途: +- `stop` — 停止播放头 +- 其他 EA 修改版 ActionScript 汇编指令 + +> **统一 CDATA**:``、`` 和 `` **均使用** `` 包裹 bytecode。CDATA 段内所有字符按字面处理。 + +### 4.7 `` — 初始化动作(类定义) + +`` 是最关键的结构之一——它定义了 ActionScript 类(class): + +```xml + +``` + +**结构分析**: + +1. **检查类是否已定义**:`pushglobal → getnamedmember "ClassName" → ActionLogicalNot × 2`(相当于 `if (_global.ClassName == undefined)`) +2. **用 `function` 定义构造函数**:参数包括 screen、this、super、_global 等寄存器 +3. **`extends`**:设置继承关系(如 `fes_m_factionMenu extends Cafe2_BaseUIScreen`) +4. **在 prototype 上定义方法**:`pushreg 2 → pushconst "methodName" → function (...) { ... } → ActionSetMember` +5. **定义实例属性**:`pushreg 2 → pushconst "propName" → pushvalue → ActionSetMember` +6. **`ASSetPropFlags`**:设置属性保护标志(防止属性被枚举/修改) + +`` 中的 `sprite` 属性指定此 initaction 作用的 sprite ID——这个 sprite 就是此 APT 中 ActionScript 类的宿主角色。 + +> **重要**:每个 APT 通常有两个 initaction: +> - sprite 32:`Cafe2_BaseUIScreen` 基类的 initaction(由工具自动生成,所有 APT 共用相同代码) +> - sprite 33:本模块类的 initaction(如 `fes_m_factionMenu`),包含该 UI 界面的具体逻辑 + +### 4.8 嵌套实例路径追溯法 + +复杂界面会大量使用嵌套组件路径。例如 `GameSetupBase.as` 中有: + +```as +Cafe2_BaseUIScreen.m_screen.gameSettings.mapPanel.mapList +Cafe2_BaseUIScreen.m_screen.gameSettings.rulesPanel.resourcesDropdown +this.m_playerSlots[i].playerSlotOptions +this.m_playerSlots[i].factionOptions +``` + +这些路径来自多层 `placeobject` 的 `poname` 嵌套。定位控件时从脚本路径反推: + +1. `Cafe2_BaseUIScreen.m_screen` 是顶层 screen movieclip +2. 第一级属性(如 `gameSettings`)是顶层 `placeobject` 的 `poname` +3. 后续属性(如 `mapPanel`、`mapList`)来自被导入 sprite 或本地 sprite 内部的 `poname` +4. 如果某一级 character 是 import,继续去对应 assets APT 的 `edit.xml` 查内部结构 + +修改任何一层实例名都会破坏 AS 访问路径。因此修改 poname 前必须全局搜索 AS 中对该名称的所有引用。 + +--- + +## 5. ActionScript 汇编参考 + +APT 使用 **EA 修改版 ActionScript bytecode**,在 edit.xml 中以文本形式表示。以下是最常用的指令。 + +### 5.1 数据操作指令 + +| 指令 | 说明 | 示例 | +|---|---|---| +| `pushstr "..."` | 压入字符串常量 | `pushstr "m_label"` | +| `pushconst "..."` | 压入常量(可以是字符串或数字) | `pushconst "m_width"` | +| `pushbyte N` | 压入 1 字节整数(-128 ~ 127) | `pushbyte 3` | +| `pushshort N` | 压入 2 字节整数 | `pushshort 200` | +| `pushnull` | 压入 null | | +| `pushundefined` | 压入 undefined | | +| `pushtrue` | 压入 true | | +| `pushfalse` | 压入 false | | +| `pushthis` | 压入当前 this | | +| `pushglobal` | 压入 _global | | +| `pushvar "..."` | 压入变量值 | `pushvar "Cafe2_BaseUIScreen"` | +| `pushreg N` | 压入寄存器 N 的值 | `pushreg 2` | +| `EAPushZero` | 压入 0(EA 特有快捷指令) | | +| `EAPushOne` | 压入 1(EA 特有快捷指令) | | +| `zerovar` | 压入 0(等价于 EAPushZero) | | + +### 5.2 变量操作指令 + +| 指令 | 说明 | 示例 | +|---|---|---| +| `ActionSetVariable` | `obj.prop = value` (从栈取 3 个值:obj, prop, value) | | +| `ActionSetMember` | `obj[prop] = value` (类似 SetVariable) | | +| `ActionDefineLocal` | 定义局部变量(栈顶为值,栈第二为名称) | | +| `ActionDelete` | 删除对象的属性 | `pushobj → pushprop → ActionDelete` | +| `setstrvar "..."` | 压入字符串并设置变量(快捷组合指令) | `setstrvar "Up/Down"` | +| `getnamedmember "..."` | 压入对象的命名成员 | `getnamedmember "prototype"` | +| `getstrmember "..."` | 压入对象的字符串成员 | `getstrmember "_parent"` | +| `ActionGetMember` | 通过计算获取成员(栈顶为索引,栈第二为 obj) | | + +### 5.3 控制流指令 + +| 指令 | 说明 | +|---|---| +| `jumptrue L` | 如果栈顶为 true,跳转到标签 L | +| `ActionLogicalNot` | 栈顶取反(`!value`) | +| `ActionReturn` | 从函数返回 | +| `end` | 函数定义结束 | +| `stop` | 停止播放头 | +| `ActionPop` | 弹出并丢弃栈顶值 | + +### 5.4 运算指令 + +| 指令 | 说明 | +|---|---| +| `ActionNewAdd` | 字符串拼接:`a + b` | +| `ActionNewEquals` | 相等比较:`a == b` | +| `ActionNewLessThan` | 小于比较:`a < b` | +| `ActionIncrement` | 栈顶值 +1 | +| `ActionDecrement` | 栈顶值 -1 | +| `gt` | 大于比较(对应 `>` 运算符) | +| `stricteq` | 严格相等(`===`) | + +### 5.5 对象/函数操作 + +| 指令 | 说明 | +|---|---| +| `ActionNew` | 创建对象实例(栈顶为参数个数,栈第二为构造函数) | +| `ActionCallMethod` | 调用方法(从栈取 obj, method, argCount, args...) | +| `ActionCallFunction` | 调用全局函数 | +| `callnamedmethodpop "..."` | 调用命名方法(快捷指令) | +| `callmethodpop` | 调用栈上的方法 | +| `callnamedpop "..."` | 调用命名全局函数(快捷指令) | +| `extends` | 设置继承关系 | +| `function (regs:N) ... end` | 定义函数(regs 声明寄存器总数) | +| `ActionInitArray` | 初始化数组 | +| `setreg N` | 将栈顶值存入寄存器 N | + +### 5.6 通信指令 + +| 指令 | 说明 | +|---|---| +| `geturl "FSCommand:...", "..."` | 调用游戏引擎函数(同步通信) | +| `geturl2` | 获取 URL(参数已在栈上) | +| `ActionTrace` | 输出调试信息 | + +### 5.7 寄存器约定 + +APT bytecode 使用寄存器(reg0, reg1, reg2, ...)来存储局部变量。根据反编译代码,常见的寄存器约定是: + +- **reg0**:controller(事件控制器参数) +- **reg1**:`this` +- **reg2**:`_global` +- **reg3**:`super` 或第一个参数 +- **reg4+**:后续参数 + +函数定义开头的 `(regs:N, reg1:this, reg2:_global)` 表示此函数最多使用 N 个寄存器,并指定了某些寄存器的角色。 + +### 5.8 反编译代码对照示例 + +以下是将 edit.xml 中的 ActionScript bytecode 与反编译的 AS 2.0 代码进行对照的示例: + +**edit.xml 中的 clipaction(构造初始化):** +``` +pushstr "m_width" +pushshort 200 +ActionSetVariable +pushstr "m_textAlign" +pushfalse +ActionSetVariable +pushstr "m_labelPosition" +setstrvar "right align" +pushstr "m_contentSymbol" +pushconst "SovietFactionButtonContent Symbol" +ActionSetVariable +pushstr "m_controller" +pushconst "factionButtonController" +ActionSetVariable +``` + +**对应的反编译 AS 2.0 代码:** +```as +on(construct){ + m_width = 200; + m_textAlign = false; + m_labelPosition = "right align"; + m_contentSymbol = "SovietFactionButtonContent Symbol"; + m_controller = "factionButtonController"; +} +``` + +**对照解读**: +- `pushstr "m_width"` + `pushshort 200` + `ActionSetVariable` = `m_width = 200` +- `pushstr "m_textAlign"` + `pushfalse` + `ActionSetVariable` = `m_textAlign = false` +- `setstrvar "right align"` = 先 `pushstr "m_labelPosition"`,然后 `pushstr "right align"`,然后 `ActionSetVariable` + +> **重要**:`setstrvar "X"` 是一个快捷指令,等价于 `pushstr` + `ActionSetVariable`,用于设置之前已经推入的属性名为值 X。 + +--- + +## 6. 修改指南 + +### 6.1 添加新的 UI 元素(以「添加第四阵营按钮」为例) + +这是一个完整的端到端示例,展示在 `fes_m_factionMenu` 中添加第四个阵营按钮所需的 edit.xml 变更。 + +#### 场景说明 + +当前 fes_m_factionMenu 有三个阵营按钮:Soviet(苏联)、Allies(盟军)、Japanese(旭日/日本)。现在要添加第四个"Empire of the Rising Sun"之外的阵营。 + +#### 需要修改的部分(按顺序) + +**步骤 1:定义新阵营按钮的「内容」角色** + +每个阵营按钮的内容(按钮上的图案)是一个 sprite,包含多个状态帧。需要创建: +- 新 shape(背景图)→ 例如 `shape id="34"`(使用下一个可用 ID) +- 新 shape(选中高亮图)→ 例如 `shape id="35"` +- 新 sprite(按钮内容,包含各个状态帧)→ 例如 `sprite id="36"` +- 相关 image 引用 + +参考现有模式(AlliedFactionButtonContent = sprite 20): +```xml + + + + c + s tc:255:255:255:255:art\Textures\apt_fes_m_factionMenu_1.tga:1:0:0:1:XXX:YYY + t 0:451:0:0:269:0 + t 269:0:269:451:0:451 + + + + + + + ... + + +``` + +**步骤 2:添加新阵营按钮的 import(如果需要)** + +如果新阵营按钮使用已导入的角色类型(如 `std_mouseRadioButtonSymbol`),无需新增 import。否则添加: +```xml + + + +``` + +并添加对应的 ``。 + +**步骤 3:添加 export** + +```xml + + ... + + +``` + +**步骤 4:在主时间线 frame 0 中添加 placeobject** + +在主时间线 frame 0 中放置新按钮(参考 `japaneseFaction` 的放置模式): + +```xml + + + + + + +``` + +**关键注意事项**: +- **`depth`**:需使用未占用的值。sovietFaction 用 depth 9,alliesFaction 用 depth 11,japaneseFaction 用 depth 13,所以新按钮可以用 depth 14(或其他未使用的奇数 depth) +- **`tx`**:X 坐标。参考:sovietFaction tx=256,alliesFaction tx=551,japaneseFaction tx=839,第四个约 tx=1127(间距约 288 像素) +- **`m_tabIndex`**:焦点切换顺序。soviet→1, allies→2, japanese→3, 新→4 +- **`m_contentSymbol`**:指向步骤 1 中创建的 sprite 的 export 名称 +- **`m_controller`**:必须与现有的 `factionButtonController` 一致,使得新按钮也受 RadioButtonController 管理 + +**步骤 5:修改 initaction(sprite 33)中的 ActionScript 逻辑** + +在 initaction 的 `initGUI` 方法中添加新按钮的初始化代码。参照现有模式: + +在 japaneseFaction 的初始化代码之后添加: + +``` +EAPushZero +pushvar "Cafe2_BaseUIScreen" +getnamedmember "m_screen" +getnamedmember "fourthFaction" +callnamedmethodpop "show" +EAPushZero +pushvar "Cafe2_BaseUIScreen" +getnamedmember "m_screen" +getnamedmember "fourthFaction" +callnamedmethodpop "enable" +pushreg 1 +getnamedmember "fourthFactionButtonClicked" +pushreg 1 +pushbyte 2 +pushreg 2 +pushconst "bind0" +ActionCallMethod +EAPushOne +pushvar "Cafe2_BaseUIScreen" +getnamedmember "m_screen" +getnamedmember "fourthFaction" +callnamedmethodpop "setOnMouseDownFunction" +EAPushZero +pushvar "Cafe2_BaseUIScreen" +getnamedmember "m_screen" +getnamedmember "fourthFaction" +callnamedmethodpop "noIntro" +``` + +**步骤 6:添加新的事件处理方法** + +在 initaction 中添加 `fourthFactionButtonClicked` 方法定义(在 `japaneseFactionButtonClicked` 的 `ActionSetMember` 之后): + +``` +pushreg 2 +pushconst "fourthFactionButtonClicked" +function ()(regs:2, reg1:_global) + pushconst "fes_m_factionMenu::fourthFactionButtonClicked()" + ActionTrace + pushreg 1 + getnamedmember "GAME" + pushconst "MODE" + pushreg 1 + getnamedmember "ENUM" + getnamedmember "GAME_MODE_CAMPAIGN" + ActionSetMember + pushreg 1 + getnamedmember "GAME" + pushconst "CAMPAIGN_TYPE" + pushreg 1 + getnamedmember "ENUM" + getnamedmember "CAMPAIGN_TYPE_FOURTH" + ActionSetMember + pushvar "factionDescriptionTF" + pushconst "text" + pushconst "$FOURTH_FACTION_DESCRIPTION&Outline" + ActionSetMember + pushvar "factionDescriptionShadowTF" + pushconst "text" + pushconst "$FOURTH_FACTION_DESCRIPTION" + ActionSetMember +end +ActionSetMember +``` + +**步骤 7:更新 `requestNewCampaign` 方法(如需要)** + +如果需要处理新阵营,修改对应的 geturl 调用以正确传递 CAMPAIGN_TYPE 值。 + +> **注意**:上述 bytecode 是示意性的。实际操作中建议先用 AptToXML 工具验证、或从小修改开始逐步积累经验。 + +#### 添加第四阵营的补充注意事项 + +以上步骤展示了核心的 edit.xml 修改,但以下细节也需关注: + +1. **depth 一致性**:现有三个按钮使用奇数 depth(9→soviet, 11→allies, 13→japanese),新按钮应使用 `depth="15"` 保持此规律。 + +2. **intro/outtro 回调**:如果新按钮的内容 sprite 需要参与入场/出场动画,其 intro 帧和 outtro 帧的最后必须调用 `this._parent.introComplete()` 和 `this._parent.outtroComplete()`(参考 sprite 10 的 frame 86 和 frame 119)。 + +3. **RadioButtonController 默认选中**:initGUI 方法末尾有 `SetSelectedRadioButton(Cafe2_BaseUIScreen.m_screen.sovietFaction)`,将苏联阵营设为默认选中。添加新按钮后不需要修改此行(除非你想让新按钮成为默认选中)。 + +4. **`CAMPAIGN_TYPE_FOURTH` 枚举值**:bytecode 中引用的 `CAMPAIGN_TYPE_FOURTH` 必须在**游戏引擎 C++ 代码**中已有定义。如果引擎不认识此枚举值,`requestNewCampaign` 中的 `geturl` 调用将静默失败——游戏不会启动新战役。 + +5. **资产声明文件(`.xml`)**:添加新 shape 后,记得在对应的 `.xml` 资产声明文件(如 `fes_m_factionMenu.xml`)中添加 `` 条目。如果使用了新纹理,还需添加 `` 声明。 + +6. **`noIntro` 调用顺序**:观察现有代码发现,sovietFaction 和 japaneseFaction 的 `noIntro()` 调用在 `setOnMouseDownFunction` **之前**,而 alliesFaction 的 `noIntro()` 在**之后**。这可能是无关紧要的顺序差异,建议保持一致(推荐先 noIntro 再 setOnMouseDownFunction 的模式)。 + +--- + +### 6.2 修改现有元素 + +#### 改变位置 + +修改 placeobject 的 `tx` / `ty` 属性: + +```xml + + +tx="400.000000" ty="700.000000" +``` + +#### 改变大小 + +修改 placeobject 的 `rotm00`(X 缩放)和/或 `rotm11`(Y 缩放): + +```xml + + +rotm00="1.500000" +``` + +#### 改变颜色/透明度 + +修改 `red` / `green` / `blue` / `alpha`: + +```xml + +red="255" green="255" blue="255" alpha="128" +``` + +> **注意**:只有当 flags 包含 `HasColorTransform` 时颜色属性才有效。 + +#### 改变文本 + +修改 edittext 的 `` 内容或 `` 绑定: + +```xml + +``` + +或修改对应的本地化字符串引用(如 `$NEW_LABEL`)。 + +--- + +### 6.3 添加/修改 ActionScript 逻辑 + +#### 修改 clipaction 中的初始化参数 + +clipaction 用于设置 UI 组件(按钮、dropdown、checkbox 等)的初始属性。例如修改按钮宽度: + +```xml + + ActionSetVariable + ... +]]> +``` + +#### 添加新的帧动作 + +在帧中添加 ``: + +```xml + + + +``` + +#### 修改 initaction 逻辑 + +这是最复杂的修改,需要对 EA bytecode 有较好理解。建议: +1. 先用反编译工具理解现有逻辑(参考 `decompiled-as-references/` 中的 .as 文件) +2. 找出需要修改的 bytecode 段 +3. 按照 bytecode 模式进行修改 + +如果只是添加新方法(如上文添加 `fourthFactionButtonClicked`),可以复制现有类似方法的 bytecode 并修改关键字符串。 + +--- + +### 6.4 导入和导出角色 + +#### 添加新 import + +假设你需要使用另一个 APT 中定义的角色: + +1. 在 `` 中添加: +```xml + +``` + +2. 在角色定义区域添加对应的 ``: +```xml + +``` + +3. 然后就可以在 placeobject 中通过 `character="40"` 引用此角色 + +#### 添加新 export + +如果其他 APT 需要引用你的角色: + +```xml + + + +``` + +--- + +### 6.5 添加或修改 shape / 图形 + +**只需修改 edit.xml 中的 `` 内容。** 外部 `.ru` 文件由工具自动生成,不需要手动编辑。 + +#### 修改现有 shape + +直接编辑 `` 标签内的 ru 命令。 + +#### 创建新 shape + +1. 选择下一个可用的 character ID +2. 创建新的 `` 元素: + +```xml + + + c + s s:255:0:0:255 + t 0:100:0:0:200:0 + t 200:0:200:100:0:100 + + +``` + +3. 在 `fes_m_factionMenu.xml`(资产声明文件)中添加对应的 geometry 声明: +```xml + +``` + +--- + +### 6.6 处理纹理和图片资源 + +新纹理需要: + +1. 将 `.tga` 文件放入 `art/Textures/` 目录 +2. 在 `fes_m_factionMenu.xml`(资产声明)中添加 Texture 声明: +```xml + +``` +3. 在 edit.xml 中创建 `` 引用新纹理,或在 ru 命令中直接引用纹理路径 + +--- + +### 6.7 修改可用性、显示隐藏和初始状态 + +APT 组件状态可能由**多处控制**,修改一处可能被其他地方覆盖: + +- **construct 参数**:`m_bVisible`、`m_bEnabled`、`m_initiallySelected` +- **初始化函数**:`initGUI()` 中的 `show()`、`enable()`、`disable()`、`noIntro()` +- **刷新函数**:`refreshComponents()` 中按游戏数据设置状态 +- **消息处理器**:收到消息后批量启用/禁用(如 `FE_MP_ONLINE_GAME_COUNTDOWN_START`) +- **视觉组件**:内部根据 `isEnabled()`、`isChecked()` 等跳转到不同帧标签 + +如果你改了初始可见性但进游戏后被立刻改回,优先查 `frameDelayedInit()` → `initGUI()` → `refreshComponents()` → message handler 这条链路上的所有状态设置。 + +### 6.8 修改列表、下拉框、滑条 + +在 game setup 类界面中,列表和下拉框的数据多数来自脚本运行时填充,而非 edit.xml 静态定义: + +```as +mapListComponent.init(mouseListboxArr, 26); +mapListComponent.attachScrollbarController(mapListScrollbarComponent); +mapListComponent.setColumnData(0, mapList); +dropDownMenu.setData(labels, values); +dropDownMenu.setSelectedIndex(index); +slider.setIndexRange(min, max, current); +``` + +修改这类控件时,不要只改 `edittext` 或 visual sprite。要检查: + +- construct 参数是否配置了宽度、标签、内容 symbol(`m_contentSymbol`、`m_entrySymbol`) +- 初始化时是否调用 `init()` 或绑定 scrollbar +- 数据是否来自 `loadVariables("QueryGameEngine?...")` +- 选中变化是否绑定了 `setOnChange(...)` 回调 +- 回调是否向游戏侧发送 `FSCommand` + +例如 `fe_m_assetsGameSetup` 的 `playerSlotMC` 内部放置了 `std_mouseDropDownMenuSymbol`(下拉菜单)、`std_MouseCheckBoxSymbol`(复选框)等通用控件。这些控件只有在 screen class 调用 `show/enable/setOnChange/setOnMouseDownFunction` 后才完整可用。 + +### 6.9 新增或复制嵌套组件 + +适用场景:复制一个玩家槽、复制一个规则面板控件、在导入的 assets APT 中新增子控件。 + +步骤: + +1. 确认组件定义在哪个 APT。顶层 screen 可能只是 import +2. 在组件 sprite 内复制 `placeobject`,选择不冲突的 depth +3. 给新实例设置唯一 `poname` +4. 如果组件脚本通过数组管理实例(如 `this.m_playerSlots = [slotMC_1, slotMC_2, ...]`),同步扩展数组 +5. 如果控件需要初始化,加入 `show/enable/setOnChange/setOnMouseDownFunction` 等绑定 +6. 如果控件需要刷新数据,加入对应 `refresh...()` 分支 +7. 如果控件发送命令,确认游戏侧有对应 FSCommand 或 QueryGameEngine 字段 + +> **注意**:只在 XML 里复制一个下拉菜单或复选框,通常只能让它显示出来;没有 AS 初始化和数据刷新时,它可能没有数据、不能交互,或下一次刷新时状态被覆盖。 + +--- + +## 7. 依赖关系与级联变更 + +修改 edit.xml 的某一部分时,可能需要同时更新其他部分。以下是最常见的级联变更情况: + +### 7.1 添加新角色时 + +| 修改内容 | 需要级联更新的地方 | +|---|---| +| 新 shape | `.xml` 资产声明中添加 `` | +| 新 image(新纹理) | `.xml` 资产声明中添加 `` | +| 新 import | 添加对应的 `` | +| 新 export | 确保对应的 character ID 存在 | +| 新 sprite(用于代码类) | 对应的 `` 需要引用此 sprite | +| 新 placeobject | 确保 character ID 已在 imports 或本文件中定义;确保 depth 不冲突 | + +### 7.2 修改 depth 时 + +- 每个 placeobject/removeobject 的 depth 在同一 frame 中应唯一 +- 确保 sprite 中使用的 depth 层次合理(如:背景用 depth 1,按钮图案用 depth 3,文字用 depth 5/7) +- 确保 removeobject 的 depth 与目标 placeobject 的 depth 匹配 + +### 7.3 修改 tabIndex 时 + +- 如果使用 RadioButtonController 或 FocusManager,tabIndex 控制焦点切换顺序 +- 确保同组 UI 元素的 tabIndex 不冲突 + +### 7.4 修改动作逻辑时 + +- initaction 中定义的类名要和 `` 中的名称一致 +- initaction 中引用的 `m_screen.xxx` 必须与 placeobject 的 `` 一致 +- 事件回调方法名(如 `sovietFactionButtonClicked`)在 initaction 中必须有对应的函数定义 + +### 7.5 修改 shape 时 + +- `.xml` 资产声明中 `` 的 `AptID` 必须与 shape 的 `id` 一致 +- `geometry` 路径必须存在对应的 `.xml` 声明 + +### 7.6 修改前关系检查表 + +修改任何可交互 UI 前,建议逐项检查: + +- [ ] character ID 是否唯一,引用是否全部更新 +- [ ] `exports` 的 name 是否与脚本中的 symbol 字符串一致 +- [ ] `imports` 的 character ID 是否与 ``、顶层 `placeobject character` 一致 +- [ ] `placeobject depth` 是否在同一 timeline 中冲突 +- [ ] `poname` 是否与 AS2 中访问的实例名一致 +- [ ] `m_contentSymbol` 是否指向导出的 content sprite +- [ ] `m_controller` 是否指向同级 controller 的 `poname` +- [ ] content sprite 是否包含组件脚本要求的子实例名(如 `hitRegion`、`checkBoxMC`、`labelMC`) +- [ ] 帧标签是否与组件脚本的 `gotoAndPlay` 字符串一致 +- [ ] 文本是否由 AS2 覆盖(而非静态 `ettext`) +- [ ] 点击逻辑是否还依赖全局对象、枚举、消息码或 FSCommand +- [ ] 修改贴图/shape 时,edit.xml 内嵌 ``、`.tga`、资产声明 XML 是否同步 +- [ ] 如果目标来自另一个 APT 的 import,是否去那个 assets APT 检查真正的 sprite、子实例和导出名 +- [ ] 是否存在 `HasColorTransform`、`HasRatio` 等额外状态字段 +- [ ] 是否有 `loadVariables("QueryGameEngine?...")` 数据来源需要同步 +- [ ] 是否有消息处理器会在之后刷新、隐藏、禁用或重建该控件状态 +- [ ] 是否有基类/子类两层脚本共同初始化该界面 +- [ ] 是否有列表、下拉框、滑条等需要脚本填充数据的控件 +- [ ] 是否修改了视觉但遗漏了 hit region,或修改了 hit region 但遗漏了视觉 +- [ ] 是否修改了一个状态帧,但其他状态帧仍使用旧资源 +- [ ] 是否新增了导出符号但没有被任何脚本或 import 使用 +- [ ] 是否删除了导出符号但其他 APT 仍在 import + +--- + +## 8. 注意事项与陷阱 + +### 8.1 Character ID +- **在整个文件中必须唯一**(包括 import 的 ID 和本地定义的 ID) +- 下一个可用 ID 是文件中最大 ID + 1 +- 注意 `` 也占用 ID,不要与 empty 的 ID 冲突 + +### 8.2 Depth 冲突 +- 同一帧中,相同 depth 的 placeobject 会替换之前的 +- 在 sprite 内部的不同帧之间,通过 removeobject + placeobject(同 depth)实现状态切换 +- 开发时确保每个 depth 的用途清晰一致 + +### 8.3 帧标签约定 +- 帧标签命名是 Cafe2 UI 框架的要求,不要随意更改如 `_disable`、`_selected` 等特殊标签名称 +- 帧标签值必须与 frame id 一致(如 `framelabel label="_disable" frame="0"`) + +### 8.4 矩阵变换顺序 +- 对象先应用 rotm00-rotm11 的旋转/缩放,再平移到 (tx, ty) +- 矩阵的默认值(恒等变换)是 `rotm00="1" rotm01="0" rotm10="0" rotm11="1"` + +### 8.5 保持 unknown 属性不变 +- `movieclip` 的 `unknown="33"`、`placeobject` 的 `unknown="0"` 等属性保持原值不变 +- 这些属性的确切含义不明,修改可能导致不可预期的结果 + +### 8.6 保持 ratio 属性不变 +- `ratio` 属性出现在 `_intro`、`_selectedRollover` 等过渡帧中 +- 其确切语义不明(可能与动画混合/插值有关) +- 修改时保持原有的 ratio 值 + +### 8.7 empty 与 import 严格对应 +- 每个 import 必须有对应的 empty,数量必须一致 +- 添加 import 时必须添加 empty,删除 import 时必须删除 empty + +### 8.8 在 bytecode 字符串中使用十进制 HTML 实体 +- bytecode 中的换行符使用 ` ` 而非 `\n`(` ` 是 bytecode 字符串字面量中的十进制字符引用,AS 引擎将其解释为换行;此写法与 CDATA 无关) +- 由于所有 bytecode 均在 CDATA 中,`&` 等字符**不需要** XML 实体转义(CDATA 内所有内容按字面处理) +- 示例:`pushconst " Cafe2_BaseUIScreen::frameDelayedInitCallback() :"` + +### 8.9 工具链限制 +- 修改 edit.xml 后必须通过 XMLToApt 工具重新生成 .apt 和相关文件 +- 不要手动编辑 .const、.dat、.eaf、.tac、geometry .ru 等自动生成的文件 +- 如果修改了 geometry,记得同时更新 .xml 资产声明文件 + +### 8.10 常见错误 +- **忘记添加 empty**:添加新 import 后忘记添加对应的 ``,导致工具转换失败 +- **忘记更新资产声明**:添加新 shape / image 后未在 .xml 中添加对应声明 +- **depth 规划不当**:可能导致对象显示层级错误 +- **tabIndex 冲突**:两个同组元素使用相同 tabIndex +- **CDATA 格式**:所有 bytecode 元素(``、``、``)均使用 `` 包裹,内部无需 XML 实体转义 + +### 8.11 旋转与斜切 +- 旋转变换涉及 `rotm01` 和 `rotm10`(非对角线元素),不要把它们误删成 0 +- 仅改 `rotm00` 和 `rotm11` 做的是缩放,不会旋转对象 +- 如果原始 placeobject 有非零的 `rotm01`/`rotm10`,复制到新实例时不要遗漏 + +### 8.12 API 大小写不一致 +- 反编译代码和 edit.xml 中同时存在 `getURL` 和 `getUrl` 两种写法 +- 同样的还有 `gotoAndPlay` / `gotoAndPlay` 等 +- 这是历史遗留的大小写不一致,不要在未确认工具链行为前自动"修正"它们 +- 修改脚本时保持与原始代码一致的写法 + +### 8.13 图集与 shape 边界 +- `shape` 的 `top/left/bottom/right` 是包围盒,`` 中的 `t`/`l` 命令才是实际绘制几何 +- 改宽高时要同时改两个三角形的相关顶点 +- 如果 shape 被父级 `placeobject` 缩放,最终屏幕尺寸还要乘上父级 matrix + +--- + +## 9. AS 2.0 SWF → APT 知识迁移 + +如果你熟悉 Flash SWF(ActionScript 2.0),以下映射表可以帮助你将 SWF 知识快速迁移到 APT edit.xml。 + +### 9.1 直接对应的概念 + +| Flash SWF 概念 | edit.xml 对应 | 说明 | +|---|---|---| +| SWF 主时间线 | `` | 顶层 movieclip 元素 | +| MovieClip / Sprite | `` / `` | 均包含 `` | +| 帧 | `` | 帧按 id 递增排列 | +| PlaceObject2 标签 | `` | 将角色放置到指定 depth | +| RemoveObject2 标签 | `` | 从指定 depth 移除角色 | +| Frame Label(帧标签) | `` | 命名的帧标签 | +| DoAction 标签(帧动作) | `` | 帧上的 ActionScript 代码 | +| DefineSprite 标签 | `` | 定义子 MovieClip | +| DefineShape 标签 | `` + `` | 矢量图形定义 | +| DefineButton 标签 | `