Files
AptXmlAI/APT修改指南.md
T
2026-06-14 01:40:04 +02:00

1737 lines
68 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# APT / edit.xml 修改指南
> **目标读者**:完全不了解 APT 的人。阅读本指南后应能理解 APT 的结构和内容,并能准确无误地修改 edit.xml 来实现自己的需求。
---
## 1. APT 概述
### 1.1 什么是 APT
APT 是红色警戒3(RA3)使用的 UI 系统,基于 **Flash 7ActionScript 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 段中所有字符按字面处理,无需使用 `&amp;` 等 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` | 绘制一条线段 |
#### sStyle)命令的变体
**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 9alliesFaction 用 depth 11japaneseFaction 用 depth 13,所以新按钮可以用 depth 14(或其他未使用的奇数 depth)
- **`tx`**X 坐标。参考:sovietFaction tx=256alliesFaction tx=551japaneseFaction tx=839,第四个约 tx=1127(间距约 288 像素)
- **`m_tabIndex`**:焦点切换顺序。soviet→1, allies→2, japanese→3, 新→4
- **`m_contentSymbol`**:指向步骤 1 中创建的 sprite 的 export 名称
- **`m_controller`**:必须与现有的 `factionButtonController` 一致,使得新按钮也受 RadioButtonController 管理
**步骤 5:修改 initactionsprite 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 一致性**:现有三个按钮使用奇数 depth9→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 或 FocusManagertabIndex 控制焦点切换顺序
- 确保同组 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 中的换行符使用 `&#10;` 而非 `\n``&#10;` 是 bytecode 字符串字面量中的十进制字符引用,AS 引擎将其解释为换行;此写法与 CDATA 无关)
- 由于所有 bytecode 均在 CDATA 中,`&` 等字符**不需要** XML 实体转义(CDATA 内所有内容按字面处理)
- 示例:`pushconst "&#10;&#10;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 SWFActionScript 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 ActionsonClipEvent | `<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
```
## 附录 Bplaceobject 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`