1737 lines
68 KiB
Markdown
1737 lines
68 KiB
Markdown
# 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 内容(`<action>`、`<clipaction>`、`<initaction>`)均使用 `<![CDATA[...]]>` 包裹。这是 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
|
||
<import name="gameSettings" movie="screens/fe_m_assetsGameSetup" character="5"/>
|
||
<import name="playerSlotMC" movie="screens/fe_m_assetsGameSetup" character="28"/>
|
||
```
|
||
|
||
而 `fe_m_assetsGameSetup` 会导出这些可复用组件:
|
||
|
||
```xml
|
||
<export character="16" name="playerSlotMC"/>
|
||
<export character="31" name="mapPanel"/>
|
||
<export character="44" name="rulesPanel"/>
|
||
<export character="45" name="gameSettings"/>
|
||
```
|
||
|
||
这意味着修改 UI 时,**不一定只改该 screen 自己的 `edit.xml`**。如果顶层 screen 只是导入并摆放一个外部组件,真正的子控件布局、实例名(poname)和 content sprite 可能定义在 assets APT 中。修改前要先判断目标在 screen APT 里,还是在被导入的 assets APT 里。
|
||
|
||
---
|
||
|
||
## 2. edit.xml 顶层结构
|
||
|
||
### 2.1 整体 XML 树
|
||
|
||
```
|
||
<aptdata>
|
||
<movieclip screensizex="..." screensizey="..." unknown="33">
|
||
<imports>
|
||
<import name="..." movie="..." character="..."/>
|
||
...
|
||
</imports>
|
||
<exports>
|
||
<export character="..." name="..."/>
|
||
...
|
||
</exports>
|
||
<frames>
|
||
<frame id="0">
|
||
<background color="..."/>
|
||
<action><![CDATA[...]]></action>
|
||
<placeobject ...>...</placeobject>
|
||
<removeobject .../>
|
||
<initaction sprite="..."><![CDATA[...]]></initaction>
|
||
</frame>
|
||
</frames>
|
||
</movieclip>
|
||
|
||
<!-- 角色定义(在 movieclip 之外) -->
|
||
<image id="1" image="art\Textures\..."/>
|
||
<shape id="2" top="..." left="..." bottom="..." right="..." geometry="...ru">
|
||
<ru>...</ru>
|
||
</shape>
|
||
<sprite id="5">
|
||
<frames>...</frames>
|
||
</sprite>
|
||
<button id="7" top="..." left="..." bottom="..." right="...">
|
||
<vertexes>...</vertexes>
|
||
<triangles>...</triangles>
|
||
<buttonrecords>...</buttonrecords>
|
||
</button>
|
||
<font id="28" name="Lucida Sans Unicode"/>
|
||
<edittext id="29" top="..." left="..." bottom="..." right="..." font="28" ...>
|
||
<ettext text="..."/>
|
||
<etvar variable="..."/>
|
||
</edittext>
|
||
<empty id="24"/>
|
||
<sprite id="32"/> <!-- 空 sprite,仅用于 initaction 挂载代码 -->
|
||
</aptdata>
|
||
```
|
||
|
||
### 2.2 `<movieclip>` — 主时间线
|
||
|
||
顶层的 `<movieclip>` 表示主时间线,包含以下部分:
|
||
|
||
- **`screensizex` / `screensizey`**:屏幕尺寸(像素),如 1365×768
|
||
- **`unknown`**:未知属性,始终为 `"33"`(保持原值即可)
|
||
- **`<imports>`**:从其他 APT 导入的角色声明
|
||
- **`<exports>`**:导出给其他 APT 使用的角色声明
|
||
- **`<frames>`**:帧列表,通常只有一个 `frame id="0"`,所有内容(placeobject、action、initaction)都在此帧中
|
||
|
||
### 2.3 `<imports>` — 导入角色
|
||
|
||
从其他 APT 文件中导入角色(character)以供本 APT 使用:
|
||
|
||
```xml
|
||
<imports>
|
||
<import name="lowerScreenBar Symbol" movie=".\Components\fe_m_generalScreenComponents" character="24"/>
|
||
<import name="std_MousebuttonSymbol" movie="../cafe/mouseComponents/std_MouseButton" character="25"/>
|
||
</imports>
|
||
```
|
||
|
||
每个 `<import>` 的属性:
|
||
- **`name`**:导入角色在原 APT 中的名称
|
||
- **`movie`**:源 APT 文件路径(相对路径,不含扩展名)
|
||
- **`character`**:导入后在本 APT 中使用的 **character ID**
|
||
|
||
### 2.4 `<exports>` — 导出角色
|
||
|
||
将本 APT 中定义的角色导出,供其他 APT 通过 import 引用:
|
||
|
||
```xml
|
||
<exports>
|
||
<export character="7" name="factionButtonHitRegion"/>
|
||
<export character="10" name="JapaneseFactionButtonContent Symbol"/>
|
||
<export character="15" name="SovietFactionButtonContent Symbol"/>
|
||
<export character="20" name="AlliedFactionButtonContent Symbol"/>
|
||
<export character="32" name="__Packages.Cafe2_BaseUIScreen"/>
|
||
<export character="33" name="__Packages.fes_m_factionMenu"/>
|
||
</exports>
|
||
```
|
||
|
||
- **`character`**:本 APT 中定义的 character ID
|
||
- **`name`**:导出的名称。注意 `__Packages.xxx` 是 ActionScript 类的完全限定名,导出后其他 APT 通过 import 可以实例化这些类
|
||
|
||
> **关键**:每个 APT 通常导出 `__Packages.Cafe2_BaseUIScreen`(基类)和 `__Packages.<本模块名>`(本模块类),这些导出使得其他 APT 可以使用相同的基类和共享 UI 框架。
|
||
|
||
### 2.5 `<empty>` — 占位符
|
||
|
||
```xml
|
||
<empty id="24"/>
|
||
<empty id="25"/>
|
||
<empty id="26"/>
|
||
<empty id="27"/>
|
||
<empty id="31"/>
|
||
```
|
||
|
||
`<empty>` 元素与 `<imports>` 中的 import **严格一一对应**。每个 import 的 character ID 都需要一个对应的 `<empty>` 元素来声明占位。
|
||
|
||
> **注意**:`<empty>` 的数量和 ID 必须与 `<imports>` 保持一致。添加新 import 时必须添加对应的 `<empty>`,删除 import 时必须删除对应的 `<empty>`。
|
||
|
||
---
|
||
|
||
## 3. 角色类型详解
|
||
|
||
APT 中的所有角色(character)都有一个唯一的整数 **ID**(如 `id="7"`)。角色 ID 在整个 APT 文件中必须唯一(包括 import 的角色 ID)。
|
||
|
||
### 3.1 movieclip 与 sprite
|
||
|
||
**movieclip** 和 **sprite** 本质上都是 ActionScript 中的 MovieClip——可以包含帧、帧标签、子对象。
|
||
|
||
在 edit.xml 中:
|
||
- **`<movieclip>`** 特指**顶层主时间线**(每个 APT 只有一个)
|
||
- **`<sprite>`** 指被定义的子 MovieClip(可以有多个)
|
||
|
||
两者结构几乎相同,都有 `<frames>`:
|
||
|
||
```xml
|
||
<sprite id="5">
|
||
<frames>
|
||
<frame id="0">
|
||
<framelabel label="_unChecked" frame="0"/>
|
||
</frame>
|
||
<frame id="9">
|
||
<framelabel label="_checked" frame="9"/>
|
||
<placeobject flags="HasCharacter|HasMatrix" depth="1" character="4"
|
||
rotm00="1.000000" rotm01="0.000000" rotm10="0.000000" rotm11="1.000000"
|
||
tx="0.000000" ty="0.000000"
|
||
red="255" green="255" blue="255" alpha="255" unknown="0">
|
||
</placeobject>
|
||
</frame>
|
||
<frame id="18">
|
||
<action><![CDATA[
|
||
stop
|
||
actionEnd
|
||
]]></action>
|
||
</frame>
|
||
</frames>
|
||
</sprite>
|
||
```
|
||
|
||
#### 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
|
||
<button id="7" top="-0.500000" left="-0.500000" bottom="37.500000" right="89.500000">
|
||
<vertexes>
|
||
<vertex x="0.000000" y="37.000000"/>
|
||
<vertex x="0.000000" y="0.000000"/>
|
||
<vertex x="89.000000" y="0.000000"/>
|
||
<vertex x="89.000000" y="37.000000"/>
|
||
</vertexes>
|
||
<triangles>
|
||
<triangle v1="0" v2="1" v3="2"/>
|
||
<triangle v1="2" v2="3" v3="0"/>
|
||
</triangles>
|
||
<buttonrecords>
|
||
<buttonrecord flags="StateHit" character="6" depth="1"
|
||
rotm00="1.000000" rotm01="0.000000" rotm10="0.000000" rotm11="1.000000"
|
||
tx="0.000000" ty="0.000000"/>
|
||
</buttonrecords>
|
||
</button>
|
||
```
|
||
|
||
**属性解释**:
|
||
- **`top` / `left` / `bottom` / `right`**:边界框
|
||
- **`<vertexes>`**:定义按钮区域的四个角点
|
||
- **`<triangles>`**:将四个顶点组合为两个三角形(构成矩形区域)
|
||
- **`<buttonrecords>`**:按钮在不同状态下的显示内容
|
||
- **`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
|
||
<shape id="2" top="0.000000" left="0.000000" bottom="451.000000" right="269.000000"
|
||
geometry="fes_m_factionMenu_geometry\2.ru">
|
||
<ru>
|
||
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
|
||
</ru>
|
||
</shape>
|
||
```
|
||
|
||
**属性解释**:
|
||
- **`top` / `left` / `bottom` / `right`**:边界框
|
||
- **`geometry`**:外部 `.ru` 文件的路径。**此文件由工具自动生成**,你只需要修改 `<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
|
||
<shape id="4" top="0" left="0" bottom="31" right="188" geometry="fe_m_assetsGameSetup_geometry\4.ru">
|
||
<ru>
|
||
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
|
||
</ru>
|
||
</shape>
|
||
```
|
||
|
||
**方式二:仅引用外部**(如 `fes_m_factionMenu` 的部分 shape)
|
||
```xml
|
||
<shape id="2" top="0" left="0" bottom="451" right="269" geometry="fes_m_factionMenu_geometry\2.ru">
|
||
<ru>
|
||
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
|
||
</ru>
|
||
</shape>
|
||
```
|
||
|
||
> **关键**:两种方式下,`<ru>` 标签内的内容都会被写入到 `geometry` 属性指定的 `.ru` 文件中。**你只需要修改 edit.xml 中的 `<ru>` 内容即可**,外部的 `.ru` 文件会在工具转换时自动生成/更新。不需要手动编辑 `.ru` 文件。
|
||
|
||
两种方式只是工具链实现细节的差异,修改时操作完全一致——编辑 `<ru>` 标签内的命令即可。
|
||
|
||
#### 组合多个 ru 绘制
|
||
|
||
一个 shape 可以包含多段 `c ... s ... t` 序列来绘制复杂的图形。例如背景 shape `id="23"` 同时绘制了底层背景和上层纹理:
|
||
|
||
```xml
|
||
<ru>
|
||
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
|
||
</ru>
|
||
```
|
||
|
||
### 3.4 image
|
||
|
||
```xml
|
||
<image id="1" image="art\Textures\apt_fes_m_factionMenu_1.tga"/>
|
||
```
|
||
|
||
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 中添加 `<Texture>` 条目
|
||
|
||
### 3.5 edittext(文本字段)
|
||
|
||
```xml
|
||
<font id="28" name="Lucida Sans Unicode">
|
||
</font>
|
||
|
||
<edittext id="29" top="-2.000000" left="-2.000000" bottom="85.099998" right="835.950012"
|
||
font="28" alignment="0"
|
||
red="0" green="0" blue="0" alpha="255"
|
||
height="18.000000"
|
||
readonly="1" multiline="1" wordwrap="1">
|
||
<ettext text="Faction Description Text"/>
|
||
<etvar variable=""/>
|
||
</edittext>
|
||
```
|
||
|
||
**属性解释**:
|
||
- **`top` / `left` / `bottom` / `right`**:文本边界框
|
||
- **`font`**:引用的 `<font>` 的 ID
|
||
- **`alignment`**:对齐方式(`0` = 左对齐,`1` = 右对齐,`2` = 居中)
|
||
- **`red` / `green` / `blue` / `alpha`**:文本颜色和透明度
|
||
- **`height`**:字体大小
|
||
- **`readonly`**:只读(`1`)或可编辑(`0`)
|
||
- **`multiline`**:多行(`1`)或单行(`0`)
|
||
- **`wordwrap`**:自动换行(`1`)或不换行(`0`)
|
||
- **`<ettext>`**:初始文本内容
|
||
- **`<etvar>`**:绑定的变量名(通常为空)
|
||
|
||
> **注意**:edittext 中出现的 `$VARIABLE_NAME` 格式的文本(如 `$SOVIET_FACTION_DESCRIPTION`)是 RA3 的本地化字符串引用,游戏运行时会替换为对应语言的实际文本。
|
||
|
||
#### 文本来源的三种情况
|
||
|
||
修改 edittext 的显示文本时,需要先判断文本的**运行时来源**,只改 `<ettext>` 可能无效:
|
||
|
||
**1. 静态 `ettext`**:文本直接写在 `<ettext text="..."/>` 中,且不会被 AS 覆盖。修改直接生效。
|
||
|
||
**2. AS2 运行时覆盖**:text 在脚本中被动态设置。例如 faction menu 中:
|
||
```as
|
||
factionDescriptionTF.text = "$SOVIET_FACTION_DESCRIPTION&Outline";
|
||
factionDescriptionShadowTF.text = "$SOVIET_FACTION_DESCRIPTION";
|
||
```
|
||
这种情况下,修改 `<ettext>` 的默认值没有意义——游戏运行时会被脚本覆盖。
|
||
|
||
**3. 本地化 key**:`$SOME_KEY` 格式的字符串由游戏引擎查找对应语言的实际文本。`&Outline` 等后缀是游戏 UI 的文本特效标记。新增 key 需要游戏侧支持。
|
||
|
||
> **阴影/正文双文本框**:RA3 UI 常常成对出现正文和阴影文本框(例如 `factionDescriptionTF` + `factionDescriptionShadowTF`)。只改其中一个会导致阴影与正文内容不一致。修改时必须两个一起改。
|
||
|
||
#### edittext 属性补充
|
||
|
||
- **`alignment`**:当前样本出现 `0` 和 `2`,可按左对齐/居中等理解,改前最好对照实际显示
|
||
- **`etvar`**:变量绑定字段,当前样本多为空,但遇到非空值时要检查 AS 是否通过变量名驱动文本
|
||
- **`height`**:字体高度,修改文本内容后需确认 `right/bottom` 边界是否足够容纳
|
||
|
||
### 3.6 font
|
||
|
||
```xml
|
||
<font id="28" name="Lucida Sans Unicode">
|
||
</font>
|
||
```
|
||
|
||
定义字体。`name` 指定字体名称,此字体必须在游戏环境中可用。
|
||
|
||
### 3.7 empty
|
||
|
||
```xml
|
||
<empty id="24"/>
|
||
```
|
||
|
||
`<empty>` 是占位符声明,与 `<imports>` 中的 import 条目严格一一对应。它声明了被导入角色在本 APT 命名空间中的存在。
|
||
|
||
> **规则**:每个 import 的 character ID 必须有一个对应的 `<empty>`。添加新 import 时要添加 `<empty>`,删除 import 时要删除 `<empty>`。
|
||
|
||
---
|
||
|
||
## 4. 帧与时间线
|
||
|
||
### 4.1 帧的基本结构
|
||
|
||
每个 `<frame>` 有一个整数 `id` 属性。帧按 id **递增顺序**排列。帧中可以包含以下元素(可同时出现多种):
|
||
|
||
```xml
|
||
<frame id="0">
|
||
<background color="-1"/>
|
||
<framelabel label="_disable" frame="0"/>
|
||
<placeobject ...>...</placeobject>
|
||
<removeobject depth="3"/>
|
||
<action><![CDATA[
|
||
stop
|
||
actionEnd
|
||
]]></action>
|
||
</frame>
|
||
```
|
||
|
||
### 4.2 `<background>`
|
||
|
||
```xml
|
||
<background color="-1"/>
|
||
<background color="-3355444"/>
|
||
```
|
||
|
||
设置帧的背景色。颜色为 32 位整数 ARGB 格式(`-1` = 0xFFFFFFFF = 白色)。通常只在主时间线帧 0 中出现。
|
||
|
||
### 4.3 `<placeobject>` — 放置角色
|
||
|
||
`<placeobject>` 是最核心的操作——将一个角色(character)放置在当前帧的指定深度(depth)。
|
||
|
||
```xml
|
||
<placeobject flags="HasCharacter|HasMatrix|HasName|HasClipAction"
|
||
depth="5" character="25"
|
||
rotm00="1.000000" rotm01="0.000000" rotm10="0.000000" rotm11="1.000000"
|
||
tx="344.000000" ty="692.799988"
|
||
red="255" green="255" blue="255" alpha="255"
|
||
unknown="0">
|
||
<poname name="backButton"/>
|
||
<clipactions>
|
||
<clipaction flags="Construct" flags2="0" keycode="0"><![CDATA[
|
||
...
|
||
actionEnd
|
||
]]></clipaction>
|
||
</clipactions>
|
||
</placeobject>
|
||
```
|
||
|
||
#### 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` | `<poname>` 子元素有效(给角色命名,使其可被 ActionScript 访问) |
|
||
| `HasClipAction` | `<clipactions>` 子元素有效(为角色附加 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)
|
||
|
||
#### `<poname>` — 给角色命名
|
||
|
||
```xml
|
||
<poname name="sovietFaction"/>
|
||
```
|
||
|
||
通过 `poname` 给放置的角色赋予一个实例名称(类似 Flash 中的 instance name),该名称可以在 ActionScript 中通过以下方式访问:
|
||
|
||
```as
|
||
Cafe2_BaseUIScreen.m_screen.sovietFaction.show();
|
||
```
|
||
|
||
> **注意**:实例名称在同一层级中必须唯一。
|
||
|
||
#### `<clipactions>` — 角色初始化代码
|
||
|
||
```xml
|
||
<clipactions>
|
||
<clipaction flags="Construct" flags2="0" keycode="0"><![CDATA[
|
||
pushstr "m_width"
|
||
pushshort 200
|
||
ActionSetVariable
|
||
...
|
||
actionEnd
|
||
]]></clipaction>
|
||
</clipactions>
|
||
```
|
||
|
||
`clipaction` 的含义取决于 `flags`:
|
||
- **`flags="Construct"`**:构造时执行(角色首次被放置时运行的初始化代码),设置角色的各种属性(如 `m_width`、`m_tabIndex`、`m_label` 等)
|
||
|
||
该代码在角色实例化时执行,类似于给对象设置初始属性值。详见 [第5章 ActionScript 汇编参考](#5-actionscript-汇编参考)。
|
||
|
||
### 4.4 `<removeobject>` — 移除角色
|
||
|
||
```xml
|
||
<removeobject depth="3"/>
|
||
```
|
||
|
||
从当前帧开始,移除指定 depth 上的角色。常用于 sprite 的状态切换中清除上一状态的显示对象。例如:
|
||
|
||
```xml
|
||
<frame id="3">
|
||
<removeobject depth="3"/>
|
||
<framelabel label="_selected" frame="3"/>
|
||
<placeobject ... depth="3" character="12" .../>
|
||
</frame>
|
||
```
|
||
|
||
### 4.5 `<framelabel>` — 帧标签
|
||
|
||
```xml
|
||
<framelabel label="_disable" frame="0"/>
|
||
```
|
||
|
||
给当前帧命名,ActionScript 可以通过 `gotoAndPlay("_disable")` 或 `gotoAndStop("_disable")` 跳转到此帧。AS 中也可以直接使用帧号。
|
||
|
||
`frame` 属性的值与 `<frame id="...">` 一致。
|
||
|
||
### 4.6 `<action>` — 帧动作
|
||
|
||
```xml
|
||
<action><![CDATA[
|
||
stop
|
||
actionEnd
|
||
]]></action>
|
||
```
|
||
|
||
帧动作在当前帧播放时执行。常见用途:
|
||
- `stop` — 停止播放头
|
||
- 其他 EA 修改版 ActionScript 汇编指令
|
||
|
||
> **统一 CDATA**:`<action>`、`<clipaction>` 和 `<initaction>` **均使用** `<![CDATA[...]]>` 包裹 bytecode。CDATA 段内所有字符按字面处理。
|
||
|
||
### 4.7 `<initaction>` — 初始化动作(类定义)
|
||
|
||
`<initaction>` 是最关键的结构之一——它定义了 ActionScript 类(class):
|
||
|
||
```xml
|
||
<initaction sprite="33"><![CDATA[
|
||
pushglobal
|
||
getnamedmember "fes_m_factionMenu"
|
||
ActionLogicalNot
|
||
ActionLogicalNot
|
||
jumptrue lxnx30
|
||
pushglobal
|
||
pushconst "fes_m_factionMenu"
|
||
function (reg4:screen)(regs:5, reg1:this, reg2:super, reg3:_global)
|
||
...
|
||
end
|
||
setreg 1
|
||
ActionSetMember
|
||
pushglobal
|
||
getnamedmember "fes_m_factionMenu"
|
||
pushvar "Cafe2_BaseUIScreen"
|
||
extends
|
||
pushreg 1
|
||
getnamedmember "prototype"
|
||
setreg 2
|
||
ActionPop
|
||
...(在 prototype 上设置方法)
|
||
EAPushOne
|
||
pushnull
|
||
pushglobal
|
||
getnamedmember "fes_m_factionMenu"
|
||
getnamedmember "prototype"
|
||
pushbyte 3
|
||
pushconst "ASSetPropFlags"
|
||
ActionCallFunction
|
||
lxnx30:
|
||
ActionPop
|
||
actionEnd
|
||
]]></initaction>
|
||
```
|
||
|
||
**结构分析**:
|
||
|
||
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`**:设置属性保护标志(防止属性被枚举/修改)
|
||
|
||
`<initaction sprite="33">` 中的 `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
|
||
<image id="36img" image="art\Textures\apt_fes_m_factionMenu_1.tga"/>
|
||
<shape id="34" top="0" left="0" bottom="451" right="269" geometry="fes_m_factionMenu_geometry\34.ru">
|
||
<ru>
|
||
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
|
||
</ru>
|
||
</shape>
|
||
<sprite id="36">
|
||
<frames>
|
||
<!-- 按照与 sprite 10/15/20 完全相同的帧结构复制 -->
|
||
<!-- 关键帧:_disable(0), _selected(10), _unselected(20),
|
||
_selectedRollover(30), _unselectedRollover(40),
|
||
_downState(50), _intro(60), _outtro(87) -->
|
||
...
|
||
</frames>
|
||
</sprite>
|
||
```
|
||
|
||
**步骤 2:添加新阵营按钮的 import(如果需要)**
|
||
|
||
如果新阵营按钮使用已导入的角色类型(如 `std_mouseRadioButtonSymbol`),无需新增 import。否则添加:
|
||
```xml
|
||
<imports>
|
||
<!-- 如有需要,添加新的 import -->
|
||
</imports>
|
||
```
|
||
|
||
并添加对应的 `<empty id="..."/>`。
|
||
|
||
**步骤 3:添加 export**
|
||
|
||
```xml
|
||
<exports>
|
||
...
|
||
<export character="36" name="FourthFactionButtonContent Symbol"/>
|
||
</exports>
|
||
```
|
||
|
||
**步骤 4:在主时间线 frame 0 中添加 placeobject**
|
||
|
||
在主时间线 frame 0 中放置新按钮(参考 `japaneseFaction` 的放置模式):
|
||
|
||
```xml
|
||
<placeobject flags="HasCharacter|HasMatrix|HasName|HasClipAction" depth="15"
|
||
character="26" rotm00="1" rotm01="0" rotm10="0" rotm11="1"
|
||
tx="1127.000000" ty="116.099998"
|
||
red="255" green="255" blue="255" alpha="255" unknown="0">
|
||
<poname name="fourthFaction"/>
|
||
<clipactions>
|
||
<clipaction flags="Construct" flags2="0" keycode="0"><![CDATA[
|
||
pushconst "m_width"
|
||
pushshort 200
|
||
ActionSetVariable
|
||
pushconst "m_textAlign"
|
||
pushfalse
|
||
ActionSetVariable
|
||
pushconst "m_labelPosition"
|
||
pushconst "right align"
|
||
ActionSetVariable
|
||
pushconst "m_label_x"
|
||
zerovar
|
||
pushconst "m_label_y"
|
||
zerovar
|
||
pushconst "m_focusDirs"
|
||
pushconst "Up/Down"
|
||
ActionSetVariable
|
||
pushconst "m_iconType"
|
||
pushconst "default"
|
||
ActionSetVariable
|
||
pushconst "m_extLabel"
|
||
pushconst ""
|
||
ActionSetVariable
|
||
pushconst "m_nTruncateType"
|
||
zerovar
|
||
pushconst "m_label"
|
||
pushconst ""
|
||
ActionSetVariable
|
||
pushconst "m_bEnabled"
|
||
pushtrue
|
||
ActionSetVariable
|
||
pushconst "m_refFM"
|
||
pushconst "_global.gFM"
|
||
ActionSetVariable
|
||
pushconst "m_initiallySelected"
|
||
pushfalse
|
||
ActionSetVariable
|
||
pushconst "m_tabIndex"
|
||
pushbyte 4
|
||
ActionSetVariable
|
||
pushconst "m_bVisible"
|
||
pushtrue
|
||
ActionSetVariable
|
||
pushconst "m_contentSymbol"
|
||
pushconst "FourthFactionButtonContent Symbol"
|
||
ActionSetVariable
|
||
pushconst "m_controller"
|
||
pushconst "factionButtonController"
|
||
ActionSetVariable
|
||
actionEnd
|
||
]]></clipaction>
|
||
</clipactions>
|
||
</placeobject>
|
||
```
|
||
|
||
**关键注意事项**:
|
||
- **`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`)中添加 `<AptGeometryData>` 条目。如果使用了新纹理,还需添加 `<Texture>` 声明。
|
||
|
||
6. **`noIntro` 调用顺序**:观察现有代码发现,sovietFaction 和 japaneseFaction 的 `noIntro()` 调用在 `setOnMouseDownFunction` **之前**,而 alliesFaction 的 `noIntro()` 在**之后**。这可能是无关紧要的顺序差异,建议保持一致(推荐先 noIntro 再 setOnMouseDownFunction 的模式)。
|
||
|
||
---
|
||
|
||
### 6.2 修改现有元素
|
||
|
||
#### 改变位置
|
||
|
||
修改 placeobject 的 `tx` / `ty` 属性:
|
||
|
||
```xml
|
||
<!-- 原:tx="344.000000" ty="692.799988" -->
|
||
<!-- 改为 -->
|
||
tx="400.000000" ty="700.000000"
|
||
```
|
||
|
||
#### 改变大小
|
||
|
||
修改 placeobject 的 `rotm00`(X 缩放)和/或 `rotm11`(Y 缩放):
|
||
|
||
```xml
|
||
<!-- 原:rotm00="1.000000" -->
|
||
<!-- 设为 1.5 = 150% 宽度 -->
|
||
rotm00="1.500000"
|
||
```
|
||
|
||
#### 改变颜色/透明度
|
||
|
||
修改 `red` / `green` / `blue` / `alpha`:
|
||
|
||
```xml
|
||
<!-- 半透明:alpha="128" -->
|
||
red="255" green="255" blue="255" alpha="128"
|
||
```
|
||
|
||
> **注意**:只有当 flags 包含 `HasColorTransform` 时颜色属性才有效。
|
||
|
||
#### 改变文本
|
||
|
||
修改 edittext 的 `<ettext>` 内容或 `<etvar>` 绑定:
|
||
|
||
```xml
|
||
<ettext text="New Text"/>
|
||
```
|
||
|
||
或修改对应的本地化字符串引用(如 `$NEW_LABEL`)。
|
||
|
||
---
|
||
|
||
### 6.3 添加/修改 ActionScript 逻辑
|
||
|
||
#### 修改 clipaction 中的初始化参数
|
||
|
||
clipaction 用于设置 UI 组件(按钮、dropdown、checkbox 等)的初始属性。例如修改按钮宽度:
|
||
|
||
```xml
|
||
<clipaction flags="Construct" flags2="0" keycode="0"><![CDATA[
|
||
pushstr "m_width"
|
||
pushshort 300 <!-- 原来可能是 200 -->
|
||
ActionSetVariable
|
||
...
|
||
]]></clipaction>
|
||
```
|
||
|
||
#### 添加新的帧动作
|
||
|
||
在帧中添加 `<action>`:
|
||
|
||
```xml
|
||
<frame id="0">
|
||
<action><![CDATA[
|
||
stop
|
||
actionEnd
|
||
]]></action>
|
||
</frame>
|
||
```
|
||
|
||
#### 修改 initaction 逻辑
|
||
|
||
这是最复杂的修改,需要对 EA bytecode 有较好理解。建议:
|
||
1. 先用反编译工具理解现有逻辑(参考 `decompiled-as-references/` 中的 .as 文件)
|
||
2. 找出需要修改的 bytecode 段
|
||
3. 按照 bytecode 模式进行修改
|
||
|
||
如果只是添加新方法(如上文添加 `fourthFactionButtonClicked`),可以复制现有类似方法的 bytecode 并修改关键字符串。
|
||
|
||
---
|
||
|
||
### 6.4 导入和导出角色
|
||
|
||
#### 添加新 import
|
||
|
||
假设你需要使用另一个 APT 中定义的角色:
|
||
|
||
1. 在 `<imports>` 中添加:
|
||
```xml
|
||
<import name="MySymbol" movie="path\to\sourceApt" character="40"/>
|
||
```
|
||
|
||
2. 在角色定义区域添加对应的 `<empty>`:
|
||
```xml
|
||
<empty id="40"/>
|
||
```
|
||
|
||
3. 然后就可以在 placeobject 中通过 `character="40"` 引用此角色
|
||
|
||
#### 添加新 export
|
||
|
||
如果其他 APT 需要引用你的角色:
|
||
|
||
```xml
|
||
<exports>
|
||
<export character="7" name="factionButtonHitRegion"/>
|
||
</exports>
|
||
```
|
||
|
||
---
|
||
|
||
### 6.5 添加或修改 shape / 图形
|
||
|
||
**只需修改 edit.xml 中的 `<ru>` 内容。** 外部 `.ru` 文件由工具自动生成,不需要手动编辑。
|
||
|
||
#### 修改现有 shape
|
||
|
||
直接编辑 `<ru>` 标签内的 ru 命令。
|
||
|
||
#### 创建新 shape
|
||
|
||
1. 选择下一个可用的 character ID
|
||
2. 创建新的 `<shape>` 元素:
|
||
|
||
```xml
|
||
<shape id="34" top="0" left="0" bottom="100" right="200"
|
||
geometry="fes_m_factionMenu_geometry\34.ru">
|
||
<ru>
|
||
c
|
||
s s:255:0:0:255
|
||
t 0:100:0:0:200:0
|
||
t 200:0:200:100:0:100
|
||
</ru>
|
||
</shape>
|
||
```
|
||
|
||
3. 在 `fes_m_factionMenu.xml`(资产声明文件)中添加对应的 geometry 声明:
|
||
```xml
|
||
<AptGeometryData id="fes_m_factionMenu_34" File="fes_m_factionMenu_geometry\34.ru" AptID="34"/>
|
||
```
|
||
|
||
---
|
||
|
||
### 6.6 处理纹理和图片资源
|
||
|
||
新纹理需要:
|
||
|
||
1. 将 `.tga` 文件放入 `art/Textures/` 目录
|
||
2. 在 `fes_m_factionMenu.xml`(资产声明)中添加 Texture 声明:
|
||
```xml
|
||
<Texture id="apt_fes_m_factionMenu_4" File="art\Textures\apt_fes_m_factionMenu_4.tga"
|
||
OutputFormat="A8R8G8B8" GenerateMipMaps="false" AllowAutomaticResize="false"/>
|
||
```
|
||
3. 在 edit.xml 中创建 `<image>` 引用新纹理,或在 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` 资产声明中添加 `<AptGeometryData>` |
|
||
| 新 image(新纹理) | `.xml` 资产声明中添加 `<Texture>` |
|
||
| 新 import | 添加对应的 `<empty>` |
|
||
| 新 export | 确保对应的 character ID 存在 |
|
||
| 新 sprite(用于代码类) | 对应的 `<initaction>` 需要引用此 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 中定义的类名要和 `<exports>` 中的名称一致
|
||
- initaction 中引用的 `m_screen.xxx` 必须与 placeobject 的 `<poname>` 一致
|
||
- 事件回调方法名(如 `sovietFactionButtonClicked`)在 initaction 中必须有对应的函数定义
|
||
|
||
### 7.5 修改 shape 时
|
||
|
||
- `.xml` 资产声明中 `<AptGeometryData>` 的 `AptID` 必须与 shape 的 `id` 一致
|
||
- `geometry` 路径必须存在对应的 `.xml` 声明
|
||
|
||
### 7.6 修改前关系检查表
|
||
|
||
修改任何可交互 UI 前,建议逐项检查:
|
||
|
||
- [ ] character ID 是否唯一,引用是否全部更新
|
||
- [ ] `exports` 的 name 是否与脚本中的 symbol 字符串一致
|
||
- [ ] `imports` 的 character ID 是否与 `<empty 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 内嵌 `<ru>`、`.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
|
||
- 注意 `<empty>` 也占用 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 后忘记添加对应的 `<empty>`,导致工具转换失败
|
||
- **忘记更新资产声明**:添加新 shape / image 后未在 .xml 中添加对应声明
|
||
- **depth 规划不当**:可能导致对象显示层级错误
|
||
- **tabIndex 冲突**:两个同组元素使用相同 tabIndex
|
||
- **CDATA 格式**:所有 bytecode 元素(`<action>`、`<clipaction>`、`<initaction>`)均使用 `<![CDATA[...]]>` 包裹,内部无需 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` 是包围盒,`<ru>` 中的 `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 元素 |
|
||
| MovieClip / Sprite | `<movieclip>` / `<sprite>` | 均包含 `<frames>` |
|
||
| 帧 | `<frame id="0">` | 帧按 id 递增排列 |
|
||
| PlaceObject2 标签 | `<placeobject>` | 将角色放置到指定 depth |
|
||
| RemoveObject2 标签 | `<removeobject>` | 从指定 depth 移除角色 |
|
||
| Frame Label(帧标签) | `<framelabel>` | 命名的帧标签 |
|
||
| DoAction 标签(帧动作) | `<action>` | 帧上的 ActionScript 代码 |
|
||
| DefineSprite 标签 | `<sprite id="...">` | 定义子 MovieClip |
|
||
| DefineShape 标签 | `<shape>` + `<ru>` | 矢量图形定义 |
|
||
| DefineButton 标签 | `<button>` | 按钮交互区域 |
|
||
| DefineEditText 标签 | `<edittext>` | 文本字段 |
|
||
| DefineFont 标签 | `<font>` | 字体声明 |
|
||
| ExportAssets 标签 | `<exports>` | 导出角色供其他 SWF 使用 |
|
||
| ImportAssets 标签 | `<imports>` | 导入其他 SWF 的角色 |
|
||
| Instance Name(实例名) | `<poname>` | 给放置的角色命名 |
|
||
| Clip Actions(onClipEvent) | `<clipactions>` | 角色关联的代码 |
|
||
| depth(深度) | `depth` 属性 | 层级控制,越大越靠前 |
|
||
| Color Transform(颜色变换) | `red/green/blue/alpha` 属性 | 颜色调制 |
|
||
| Matrix(变换矩阵) | `rotm00-rotm11, tx, ty` | 2×2 矩阵 + 平移 |
|
||
|
||
### 9.2 EA 修改版 bytecode vs 标准 Flash 7 bytecode
|
||
|
||
APT 使用的 bytecode 基于 Flash 7 ActionScript 但有以下关键差异:
|
||
|
||
#### EA 特有操作码
|
||
|
||
这些指令在标准 Flash 7 bytecode 中不存在:
|
||
|
||
| EA 指令 | 等价标准操作 |
|
||
|---|---|
|
||
| `EAPushZero` | `push 0` |
|
||
| `EAPushOne` | `push 1` |
|
||
| `setstrvar "X"` | `pushstr propName → pushstr X → ActionSetVariable`(快捷组合) |
|
||
| `zerovar` | `push 0`(别名) |
|
||
| `callnamedmethodpop "..."` | 组合指令:查找命名方法 + 调用 + pop |
|
||
| `callnamedpop "..."` | 组合指令:查找命名函数 + 调用 + pop |
|
||
|
||
#### 裁剪的 API
|
||
|
||
以下标准 Flash API 在游戏 APT 环境中**不可用**或受限:
|
||
|
||
- **网络请求**:不能使用 `getURL` 访问外部网站,只能通过 `FSCommand:` 前缀与游戏引擎通信
|
||
- **文件系统**:不能进行文件 I/O
|
||
- **某些内置类**:游戏环境可能不包含完整的 Flash 内置类
|
||
|
||
可用的通信方式:
|
||
```as
|
||
// 调用游戏引擎函数(单向,无返回值)
|
||
getUrl("FSCommand:CallGameFunction", "%Campaign_NewGame");
|
||
|
||
// 查询游戏引擎(有返回值)
|
||
loadVariables("QueryGameEngine?MP_MAP_LIST", retObj);
|
||
```
|
||
|
||
#### 寄存器式参数
|
||
|
||
EA bytecode 使用显式的寄存器声明来标记函数的 this、_global、参数等:
|
||
```
|
||
function (reg3:screen)(regs:4, reg1:this, reg2:_global)
|
||
```
|
||
|
||
这在标准 Flash bytecode 中是对应于隐式的作用域链。
|
||
|
||
### 9.3 类定义模式
|
||
|
||
标准 AS 2.0:
|
||
```as
|
||
class fes_m_factionMenu extends Cafe2_BaseUIScreen {
|
||
function fes_m_factionMenu(screen) {
|
||
super(screen);
|
||
}
|
||
}
|
||
```
|
||
|
||
edit.xml 中对应的 bytecode 模式(在 initaction 中):
|
||
1. 检查 `_global.fes_m_factionMenu` 是否已定义
|
||
2. 用 `function` 指令定义构造函数
|
||
3. 用 `extends` 设置继承链
|
||
4. 在 `prototype` 上逐个设置方法(`pushreg 2 → pushconst "methodName" → function... → ActionSetMember`)
|
||
5. 调用 `ASSetPropFlags` 保护 prototype
|
||
|
||
### 9.4 快速对照:看懂 bytecode 的技巧
|
||
|
||
| 你想在 AS 中做什么 | 在 bytecode 中看什么 |
|
||
|---|---|
|
||
| `obj.prop = value` | `pushstr "value" → pushstr "prop" → pushobj → ActionSetVariable` |
|
||
| `var x = 42` | `pushbyte 42 → setreg N`(N 是寄存器号) |
|
||
| `if (x) { ... }` | `push x → ActionLogicalNot → ActionLogicalNot → jumptrue L` |
|
||
| `obj.method(a,b)` | `push b → push a → push 2 → pushobj → pushstr "method" → ActionCallMethod` |
|
||
| `_global.XXX` | `pushglobal → getnamedmember "XXX"` |
|
||
| `super.method(a)` | `push a → EAPushOne → pushreg 2(super) → pushundefined → callmethodpop` |
|
||
| `trace("msg")` | `pushstr "msg" → ActionTrace` |
|
||
| `a + b`(字符串拼接) | `push a → push b → ActionNewAdd` |
|
||
| `a == b` | `push a → push b → ActionNewEquals` |
|
||
|
||
---
|
||
|
||
## 10. 已知未解问题
|
||
|
||
以下是一些在调研中发现但语义尚未完全明确的内容:
|
||
|
||
### 10.1 `ratio` 属性
|
||
|
||
- **出现位置**:`_intro`、`_selectedRollover`、`_unselectedRollover` 等过渡动画帧中的 placeobject
|
||
- **观察到的模式**:ratio 值随帧递增(如 `0.000046` → `0.000092` → `0.000137`)
|
||
- **推测**:可能与动画补间/混合因子有关,用于控制过渡动画的插值进度
|
||
- **建议**:修改时保持原有的 ratio 值,或参考同类型元素的 pattern
|
||
|
||
### 10.2 `unknown` 属性
|
||
|
||
- **出现位置**:`<movieclip unknown="33">`、`<placeobject unknown="0">`
|
||
- **`unknown="33"`** 始终出现在 movieclip 中,意义不明
|
||
- **`unknown="0"`** 始终出现在 placeobject 中
|
||
- **建议**:保持原值不变,不要尝试修改
|
||
|
||
### 10.3 `.dat` 文件的精确生成机制
|
||
|
||
- `.dat` 文件(如 `fes_m_factionMenu.dat`)内容为 `A->B` 格式的映射行
|
||
- 由工具自动生成,但映射规则不甚明确
|
||
- **建议**:不需要手动编辑
|
||
|
||
### 10.4 `symbols.csv` 的重复行
|
||
|
||
- `fes_m_factionMenu` 的 `symbols.csv` 中有大量重复的 `7;factionButtonHitRegion` 条目
|
||
- 这可能是反编译工具对每一帧的 export 声明都输出了一行
|
||
- 不影响实际功能
|
||
|
||
---
|
||
|
||
## 11. 常见排错线索
|
||
|
||
### 11.1 控件看得见但点不了
|
||
- 检查 hit region 是否存在:content sprite 中 depth 5 的 `button`(`poname="hitRegion"`)
|
||
- 检查 `buttonrecord StateHit` 是否定义了可点击区域
|
||
- 检查事件绑定:`setOnMouseDownFunction` / `setOnMouseUpFunction` 是否在 `initGUI()` 中调用
|
||
- 检查 `enable()` 是否被调用(`m_bEnabled` 初始值、初始化、刷新)
|
||
|
||
### 11.2 控件点了但没反应
|
||
- 检查 `setOnMouseDownFunction` / `setOnMouseUpFunction` / `setOnChange` 是否绑定
|
||
- 检查回调函数是否在 initaction 中有定义
|
||
- 检查 FSCommand 是否正确(如 `"%Campaign_NewGame"`)
|
||
|
||
### 11.3 文本改了但游戏里没变
|
||
- 检查 AS 是否运行时覆盖 `.text`(查看反编译 AS 中的 `.text =` 赋值)
|
||
- 检查是否使用本地化 key(`$SOME_KEY`)
|
||
- 检查是否有阴影/正文双文本框(两个都要改)
|
||
|
||
### 11.4 按钮状态显示错
|
||
- 检查 `_selected` / `_unselected` / `_disable` / `_downState` 等帧标签是否存在
|
||
- 检查状态帧上的 `placeobject` / `removeobject` 是否正确
|
||
- 检查每个状态段的最后一帧是否有 `stop`
|
||
|
||
### 11.5 新增实例游戏里报错或空白
|
||
- 检查 character ID 是否在 exports 中注册
|
||
- 检查 `m_contentSymbol` 字符串是否与 exports 的 name 一致
|
||
- 检查 content sprite 内是否有脚本期望的子实例名(如 `hitRegion`)
|
||
- 如果 character 是 import,检查对应的 `<empty>` 和源 APT
|
||
|
||
### 11.6 位置看起来不对
|
||
- 检查父级和子级 matrix 是否叠加(多层 placeobject 的矩阵会相乘)
|
||
- 检查是否改了内部 sprite 而不是顶层实例
|
||
- 检查同一 depth 是否被后续帧的 placeobject 覆盖
|
||
|
||
### 11.7 刷新后状态被还原
|
||
- 检查 `refreshComponents()` 和 message handler 中的数据驱动逻辑
|
||
- 检查 `loadVariables("QueryGameEngine?...")` 返回的数据是否覆盖了你的修改
|
||
- 检查 `enableControl()` / `disableControl()` 是否根据游戏数据重新设置状态
|
||
|
||
### 11.8 转换后资源缺失
|
||
- 检查资产声明 XML 是否包含所有新的 geometry 和纹理
|
||
- 检查贴图路径是否与实际文件匹配
|
||
- 检查 shape 的 `geometry` 属性路径是否正确
|
||
- 检查工具是否重新输出了 `.ru` / `.dat`
|
||
|
||
---
|
||
|
||
## 12. 推荐工作流
|
||
|
||
1. **定位目标**:用 `poname`、export name、character ID 定位需要修改的元素
|
||
2. **画关系链**:顶层实例 → imported/exported character → content sprite → shape/image → script
|
||
3. **跨 APT 追踪**:如果链路里出现 import,打开对应 assets APT 继续追踪
|
||
4. **读反编译 AS**:确认真正的运行时逻辑、数据查询和游戏命令
|
||
5. **修改 edit.xml**:必要时同步纹理和资产声明;`.ru` 通常由转换工具从 edit.xml 输出
|
||
6. **转换**:用外部工具重新转换为游戏用 APT 文件组
|
||
7. **游戏验证**:验证视觉、鼠标、键盘焦点、返回/继续流程、文本、本地化、声音、游戏侧数据刷新
|
||
8. **排错**:如果出现黑屏、按钮无响应或状态卡住,优先检查 depth 冲突、实例名、帧标签、intro/outtro 回调、脚本初始化、消息刷新和跨 APT 导入导出
|
||
|
||
---
|
||
|
||
## 附录 A:完整 ru 命令速查
|
||
|
||
```
|
||
c // Clear — 结束上一段几何,开始新的绘制
|
||
s s:R:G:B:A // Solid fill style — 纯色填充
|
||
s l:thickness:R:G:B:A // Line style — 线条
|
||
s tc:R:G:B:A:texture:m00:m01:m10:m11:tx:ty // Textured fill — 纹理填充,tex = M × screen + (tx,ty)
|
||
l x1:y1:x2:y2 // Line — 线段(注意:实测中较少使用)
|
||
t x1:y1:x2:y2:x3:y3 // Triangle — 三角形(屏幕坐标)
|
||
|
||
```
|
||
// ── 场景一:移动三角形(矩阵不变)───────────────────────
|
||
// 原始:采样纹理 x=[129,255] 区域,屏幕位置 x=[177,303],m00=1, tx=-48.25
|
||
// 验证:tex_left = 177 + (-48.25) = 128.75 ≈ 129 ✓
|
||
// 三角形移到 x=[30,110](宽80),保持采样 129..209(左上角 80px):
|
||
// new_tx = tex_left - new_screen_left = 129 - 30 = 99.0
|
||
// 结果:只显示纹理左侧 80px(图片被裁切)。
|
||
//
|
||
// ── 场景二:缩小三角形 + 调整矩阵(完整缩放图片)──────
|
||
// 三角形从 126×87 缩小到 95×65,希望显示完整纹理(非裁切):
|
||
// m00 = tex_width / new_width = 126/95 ≈ 1.3263
|
||
// m11 = tex_height / new_height = 87/65 ≈ 1.3385
|
||
// new_tx = tex_left - m00 × new_screen_left
|
||
// new_ty = tex_top - m11 × new_screen_top
|
||
//
|
||
// 实例(完整改装 mouse_chapterSelectComponent 四个旗帜):
|
||
// 原三角 126×87→新三角 95×65,完整采样各阵营纹理区域(含矩阵缩放):
|
||
// s tc:255:...:1.3263:0:0:1.3385:95.842:-32.462
|
||
// t 25:90:25:25:120:25
|
||
// t 120:25:120:90:25:90
|
||
// ...
|
||
// 验证:tex_left = 1.3263×25 + 95.842 = 33.158 + 95.842 = 129 ✓
|
||
// tex_right= 1.3263×120+ 95.842 = 159.16 + 95.842 = 255 ✓ (完整 126px)
|
||
```
|
||
|
||
## 附录 B:placeobject flags 速查
|
||
|
||
| Flag | 含义 | 需配合的属性 |
|
||
|---|---|---|
|
||
| `HasCharacter` | 指定角色 | `character` |
|
||
| `HasMatrix` | 指定变换矩阵 | `rotm00, rotm01, rotm10, rotm11, tx, ty` |
|
||
| `HasName` | 指定实例名称 | `<poname name="..."/>` |
|
||
| `HasClipAction` | 附加初始化代码 | `<clipactions>` |
|
||
| `HasColorTransform` | 颜色变换 | `red, green, blue, alpha` |
|
||
| `HasRatio` | 过渡比例 | `ratio` |
|
||
|
||
## 附录 C:bytecode 指令速查(按功能分类)
|
||
|
||
**数据压入**:`pushstr`, `pushconst`, `pushbyte`, `pushshort`, `pushnull`, `pushundefined`, `pushtrue`, `pushfalse`, `pushthis`, `pushglobal`, `pushvar`, `pushreg`, `EAPushZero`, `EAPushOne`, `zerovar`
|
||
|
||
**变量操作**:`ActionSetVariable`, `ActionSetMember`, `ActionDefineLocal`, `ActionDelete`, `setstrvar`, `getnamedmember`, `getstrmember`, `ActionGetMember`
|
||
|
||
**控制流**:`jumptrue`, `ActionLogicalNot`, `ActionReturn`, `end`, `stop`, `ActionPop`
|
||
|
||
**运算**:`ActionNewAdd`, `ActionNewEquals`, `ActionNewLessThan`, `ActionIncrement`, `ActionDecrement`, `gt`, `stricteq`
|
||
|
||
**对象/函数**:`ActionNew`, `ActionCallMethod`, `ActionCallFunction`, `callnamedmethodpop`, `callmethodpop`, `callnamedpop`, `extends`, `function`, `ActionInitArray`, `setreg`
|
||
|
||
**通信/调试**:`geturl`, `geturl2`, `ActionTrace`
|