68 KiB
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
和所有关联文件
核心流程:
- 用 AptToXML 工具从
.apt生成_edit.xml - 修改
_edit.xml的内容 - 用 XMLToApt 工具从
_edit.xml重新生成.apt及所有关联文件 - 生成的
.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 知识迁移。
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 中有:
<import name="gameSettings" movie="screens/fe_m_assetsGameSetup" character="5"/>
<import name="playerSlotMC" movie="screens/fe_m_assetsGameSetup" character="28"/>
而 fe_m_assetsGameSetup 会导出这些可复用组件:
<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×768unknown:未知属性,始终为"33"(保持原值即可)<imports>:从其他 APT 导入的角色声明<exports>:导出给其他 APT 使用的角色声明<frames>:帧列表,通常只有一个frame id="0",所有内容(placeobject、action、initaction)都在此帧中
2.3 <imports> — 导入角色
从其他 APT 文件中导入角色(character)以供本 APT 使用:
<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 引用:
<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 IDname:导出的名称。注意__Packages.xxx是 ActionScript 类的完全限定名,导出后其他 APT 通过 import 可以实例化这些类
关键:每个 APT 通常导出
__Packages.Cafe2_BaseUIScreen(基类)和__Packages.<本模块名>(本模块类),这些导出使得其他 APT 可以使用相同的基类和共享 UI 框架。
2.5 <empty> — 占位符
<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>:
<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()来通知父级过渡完成,参考反编译代码:// frame 119: this._parent.outtroComplete(); stop(); - select 帧(如
frame id="10"有_selected标签)通常从空帧过渡到含有新 placeobject 的帧
每个状态段的最后一帧通常有一个 stop() 动作,防止播放头继续前进。
3.2 button
Button 是一个可点击的交互区域,由顶点(vertices)、三角形(triangles)和按钮记录(buttonrecords)组成:
<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)通过固定实例名绑定鼠标事件:
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,渲染单元)命令定义绘制方式:
<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)
<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)
<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" 同时绘制了底层背景和上层纹理:
<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
<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(文本字段)
<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>的 IDalignment:对齐方式(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 中:
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
<font id="28" name="Lucida Sans Unicode">
</font>
定义字体。name 指定字体名称,此字体必须在游戏环境中可用。
3.7 empty
<empty id="24"/>
<empty> 是占位符声明,与 <imports> 中的 import 条目严格一一对应。它声明了被导入角色在本 APT 命名空间中的存在。
规则:每个 import 的 character ID 必须有一个对应的
<empty>。添加新 import 时要添加<empty>,删除 import 时要删除<empty>。
4. 帧与时间线
4.1 帧的基本结构
每个 <frame> 有一个整数 id 属性。帧按 id 递增顺序排列。帧中可以包含以下元素(可同时出现多种):
<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>
<background color="-1"/>
<background color="-3355444"/>
设置帧的背景色。颜色为 32 位整数 ARGB 格式(-1 = 0xFFFFFFFF = 白色)。通常只在主时间线帧 0 中出现。
4.3 <placeobject> — 放置角色
<placeobject> 是最核心的操作——将一个角色(character)放置在当前帧的指定深度(depth)。
<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> — 给角色命名
<poname name="sovietFaction"/>
通过 poname 给放置的角色赋予一个实例名称(类似 Flash 中的 instance name),该名称可以在 ActionScript 中通过以下方式访问:
Cafe2_BaseUIScreen.m_screen.sovietFaction.show();
注意:实例名称在同一层级中必须唯一。
<clipactions> — 角色初始化代码
<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 汇编参考。
4.4 <removeobject> — 移除角色
<removeobject depth="3"/>
从当前帧开始,移除指定 depth 上的角色。常用于 sprite 的状态切换中清除上一状态的显示对象。例如:
<frame id="3">
<removeobject depth="3"/>
<framelabel label="_selected" frame="3"/>
<placeobject ... depth="3" character="12" .../>
</frame>
4.5 <framelabel> — 帧标签
<framelabel label="_disable" frame="0"/>
给当前帧命名,ActionScript 可以通过 gotoAndPlay("_disable") 或 gotoAndStop("_disable") 跳转到此帧。AS 中也可以直接使用帧号。
frame 属性的值与 <frame id="..."> 一致。
4.6 <action> — 帧动作
<action><![CDATA[
stop
actionEnd
]]></action>
帧动作在当前帧播放时执行。常见用途:
stop— 停止播放头- 其他 EA 修改版 ActionScript 汇编指令
统一 CDATA:
<action>、<clipaction>和<initaction>均使用<![CDATA[...]]>包裹 bytecode。CDATA 段内所有字符按字面处理。
4.7 <initaction> — 初始化动作(类定义)
<initaction> 是最关键的结构之一——它定义了 ActionScript 类(class):
<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>
结构分析:
- 检查类是否已定义:
pushglobal → getnamedmember "ClassName" → ActionLogicalNot × 2(相当于if (_global.ClassName == undefined)) - 用
function定义构造函数:参数包括 screen、this、super、_global 等寄存器 extends:设置继承关系(如fes_m_factionMenu extends Cafe2_BaseUIScreen)- 在 prototype 上定义方法:
pushreg 2 → pushconst "methodName" → function (...) { ... } → ActionSetMember - 定义实例属性:
pushreg 2 → pushconst "propName" → pushvalue → ActionSetMember 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 中有:
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 嵌套。定位控件时从脚本路径反推:
Cafe2_BaseUIScreen.m_screen是顶层 screen movieclip- 第一级属性(如
gameSettings)是顶层placeobject的poname - 后续属性(如
mapPanel、mapList)来自被导入 sprite 或本地 sprite 内部的poname - 如果某一级 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 代码:
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 = 200pushstr "m_textAlign"+pushfalse+ActionSetVariable=m_textAlign = falsesetstrvar "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):
<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。否则添加:
<imports>
<!-- 如有需要,添加新的 import -->
</imports>
并添加对应的 <empty id="..."/>。
步骤 3:添加 export
<exports>
...
<export character="36" name="FourthFactionButtonContent Symbol"/>
</exports>
步骤 4:在主时间线 frame 0 中添加 placeobject
在主时间线 frame 0 中放置新按钮(参考 japaneseFaction 的放置模式):
<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, 新→4m_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 修改,但以下细节也需关注:
-
depth 一致性:现有三个按钮使用奇数 depth(9→soviet, 11→allies, 13→japanese),新按钮应使用
depth="15"保持此规律。 -
intro/outtro 回调:如果新按钮的内容 sprite 需要参与入场/出场动画,其 intro 帧和 outtro 帧的最后必须调用
this._parent.introComplete()和this._parent.outtroComplete()(参考 sprite 10 的 frame 86 和 frame 119)。 -
RadioButtonController 默认选中:initGUI 方法末尾有
SetSelectedRadioButton(Cafe2_BaseUIScreen.m_screen.sovietFaction),将苏联阵营设为默认选中。添加新按钮后不需要修改此行(除非你想让新按钮成为默认选中)。 -
CAMPAIGN_TYPE_FOURTH枚举值:bytecode 中引用的CAMPAIGN_TYPE_FOURTH必须在游戏引擎 C++ 代码中已有定义。如果引擎不认识此枚举值,requestNewCampaign中的geturl调用将静默失败——游戏不会启动新战役。 -
资产声明文件(
.xml):添加新 shape 后,记得在对应的.xml资产声明文件(如fes_m_factionMenu.xml)中添加<AptGeometryData>条目。如果使用了新纹理,还需添加<Texture>声明。 -
noIntro调用顺序:观察现有代码发现,sovietFaction 和 japaneseFaction 的noIntro()调用在setOnMouseDownFunction之前,而 alliesFaction 的noIntro()在之后。这可能是无关紧要的顺序差异,建议保持一致(推荐先 noIntro 再 setOnMouseDownFunction 的模式)。
6.2 修改现有元素
改变位置
修改 placeobject 的 tx / ty 属性:
<!-- 原:tx="344.000000" ty="692.799988" -->
<!-- 改为 -->
tx="400.000000" ty="700.000000"
改变大小
修改 placeobject 的 rotm00(X 缩放)和/或 rotm11(Y 缩放):
<!-- 原:rotm00="1.000000" -->
<!-- 设为 1.5 = 150% 宽度 -->
rotm00="1.500000"
改变颜色/透明度
修改 red / green / blue / alpha:
<!-- 半透明:alpha="128" -->
red="255" green="255" blue="255" alpha="128"
注意:只有当 flags 包含
HasColorTransform时颜色属性才有效。
改变文本
修改 edittext 的 <ettext> 内容或 <etvar> 绑定:
<ettext text="New Text"/>
或修改对应的本地化字符串引用(如 $NEW_LABEL)。
6.3 添加/修改 ActionScript 逻辑
修改 clipaction 中的初始化参数
clipaction 用于设置 UI 组件(按钮、dropdown、checkbox 等)的初始属性。例如修改按钮宽度:
<clipaction flags="Construct" flags2="0" keycode="0"><![CDATA[
pushstr "m_width"
pushshort 300 <!-- 原来可能是 200 -->
ActionSetVariable
...
]]></clipaction>
添加新的帧动作
在帧中添加 <action>:
<frame id="0">
<action><![CDATA[
stop
actionEnd
]]></action>
</frame>
修改 initaction 逻辑
这是最复杂的修改,需要对 EA bytecode 有较好理解。建议:
- 先用反编译工具理解现有逻辑(参考
decompiled-as-references/中的 .as 文件) - 找出需要修改的 bytecode 段
- 按照 bytecode 模式进行修改
如果只是添加新方法(如上文添加 fourthFactionButtonClicked),可以复制现有类似方法的 bytecode 并修改关键字符串。
6.4 导入和导出角色
添加新 import
假设你需要使用另一个 APT 中定义的角色:
- 在
<imports>中添加:
<import name="MySymbol" movie="path\to\sourceApt" character="40"/>
- 在角色定义区域添加对应的
<empty>:
<empty id="40"/>
- 然后就可以在 placeobject 中通过
character="40"引用此角色
添加新 export
如果其他 APT 需要引用你的角色:
<exports>
<export character="7" name="factionButtonHitRegion"/>
</exports>
6.5 添加或修改 shape / 图形
只需修改 edit.xml 中的 <ru> 内容。 外部 .ru 文件由工具自动生成,不需要手动编辑。
修改现有 shape
直接编辑 <ru> 标签内的 ru 命令。
创建新 shape
- 选择下一个可用的 character ID
- 创建新的
<shape>元素:
<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>
- 在
fes_m_factionMenu.xml(资产声明文件)中添加对应的 geometry 声明:
<AptGeometryData id="fes_m_factionMenu_34" File="fes_m_factionMenu_geometry\34.ru" AptID="34"/>
6.6 处理纹理和图片资源
新纹理需要:
- 将
.tga文件放入art/Textures/目录 - 在
fes_m_factionMenu.xml(资产声明)中添加 Texture 声明:
<Texture id="apt_fes_m_factionMenu_4" File="art\Textures\apt_fes_m_factionMenu_4.tga"
OutputFormat="A8R8G8B8" GenerateMipMaps="false" AllowAutomaticResize="false"/>
- 在 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 静态定义:
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 中新增子控件。
步骤:
- 确认组件定义在哪个 APT。顶层 screen 可能只是 import
- 在组件 sprite 内复制
placeobject,选择不冲突的 depth - 给新实例设置唯一
poname - 如果组件脚本通过数组管理实例(如
this.m_playerSlots = [slotMC_1, slotMC_2, ...]),同步扩展数组 - 如果控件需要初始化,加入
show/enable/setOnChange/setOnMouseDownFunction等绑定 - 如果控件需要刷新数据,加入对应
refresh...()分支 - 如果控件发送命令,确认游戏侧有对应 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 spritem_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 内置类
可用的通信方式:
// 调用游戏引擎函数(单向,无返回值)
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:
class fes_m_factionMenu extends Cafe2_BaseUIScreen {
function fes_m_factionMenu(screen) {
super(screen);
}
}
edit.xml 中对应的 bytecode 模式(在 initaction 中):
- 检查
_global.fes_m_factionMenu是否已定义 - 用
function指令定义构造函数 - 用
extends设置继承链 - 在
prototype上逐个设置方法(pushreg 2 → pushconst "methodName" → function... → ActionSetMember) - 调用
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. 推荐工作流
- 定位目标:用
poname、export name、character ID 定位需要修改的元素 - 画关系链:顶层实例 → imported/exported character → content sprite → shape/image → script
- 跨 APT 追踪:如果链路里出现 import,打开对应 assets APT 继续追踪
- 读反编译 AS:确认真正的运行时逻辑、数据查询和游戏命令
- 修改 edit.xml:必要时同步纹理和资产声明;
.ru通常由转换工具从 edit.xml 输出 - 转换:用外部工具重新转换为游戏用 APT 文件组
- 游戏验证:验证视觉、鼠标、键盘焦点、返回/继续流程、文本、本地化、声音、游戏侧数据刷新
- 排错:如果出现黑屏、按钮无响应或状态卡住,优先检查 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`