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

68 KiB
Raw Permalink Blame History

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(自定义的操作码如 EAPushOnesetstrvar 等),部分标准 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_gameSetupfe_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×768
  • unknown:未知属性,始终为 "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 ID
  • name:导出的名称。注意 __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

movieclipsprite 本质上都是 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 的 shapebuttonrecord 使用的 character)或放置时的矩阵,而不是只改可见图形
  • 扩大可点击区域要改 hit shape 或 hit button 的 matrix不是改可见 shape
  • content sprite 中 depth 5 上的 buttonponame="hitRegion")使用了很大的缩放矩阵(如 rotm00="2.820221" rotm11="11.699921"),这是故意放大 hit 区域的做法
  • 如果 content sprite 中没有 hitRegion 实例,通用组件脚本将无法绑定鼠标事件,按钮将不可点击

3.3 shape(矢量图形)

Shape 是矢量图形,通过 rurender 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 绘制一条线段

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 + txtex_y = m10 × screen_x + m11 × screen_y + ty。 当矩阵为单位矩阵(1:0:0:1)时简化为 tex = screen + txt 命令中的坐标为屏幕坐标。要采样纹理中的特定区域,需满足 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 中的 OutputFormatGenerateMipMapsAllowAutomaticResize 是否符合原样本
  • 新增纹理时,需要在资产声明 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> 的 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 中:

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:当前样本出现 02,可按左对齐/居中等理解,改前最好对照实际显示
  • 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 轴缩放 srotm00="s"Y 轴缩放 srotm11="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_widthm_tabIndexm_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>

结构分析

  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 32Cafe2_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

这些路径来自多层 placeobjectponame 嵌套。定位控件时从脚本路径反推:

  1. Cafe2_BaseUIScreen.m_screen 是顶层 screen movieclip
  2. 第一级属性(如 gameSettings)是顶层 placeobjectponame
  3. 后续属性(如 mapPanelmapList)来自被导入 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 压入 0EA 特有快捷指令)
EAPushOne 压入 1EA 特有快捷指令)
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(事件控制器参数)
  • reg1this
  • reg2_global
  • reg3super 或第一个参数
  • 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 = 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):

<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 9alliesFaction 用 depth 11japaneseFaction 用 depth 13,所以新按钮可以用 depth 14(或其他未使用的奇数 depth)
  • txX 坐标。参考: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 方法定义(在 japaneseFactionButtonClickedActionSetMember 之后):

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 属性:

<!-- 原:tx="344.000000" ty="692.799988" -->
<!-- 改为 -->
tx="400.000000" ty="700.000000"

改变大小

修改 placeobject 的 rotm00X 缩放)和/或 rotm11Y 缩放):

<!-- 原: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 有较好理解。建议:

  1. 先用反编译工具理解现有逻辑(参考 decompiled-as-references/ 中的 .as 文件)
  2. 找出需要修改的 bytecode 段
  3. 按照 bytecode 模式进行修改

如果只是添加新方法(如上文添加 fourthFactionButtonClicked),可以复制现有类似方法的 bytecode 并修改关键字符串。


6.4 导入和导出角色

添加新 import

假设你需要使用另一个 APT 中定义的角色:

  1. <imports> 中添加:
<import name="MySymbol" movie="path\to\sourceApt" character="40"/>
  1. 在角色定义区域添加对应的 <empty>
<empty id="40"/>
  1. 然后就可以在 placeobject 中通过 character="40" 引用此角色

添加新 export

如果其他 APT 需要引用你的角色:

<exports>
    <export character="7" name="factionButtonHitRegion"/>
</exports>

6.5 添加或修改 shape / 图形

只需修改 edit.xml 中的 <ru> 内容。 外部 .ru 文件由工具自动生成,不需要手动编辑。

修改现有 shape

直接编辑 <ru> 标签内的 ru 命令。

创建新 shape

  1. 选择下一个可用的 character ID
  2. 创建新的 <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>
  1. fes_m_factionMenu.xml(资产声明文件)中添加对应的 geometry 声明:
<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 声明:
<Texture id="apt_fes_m_factionMenu_4" File="art\Textures\apt_fes_m_factionMenu_4.tga"
         OutputFormat="A8R8G8B8" GenerateMipMaps="false" AllowAutomaticResize="false"/>
  1. 在 edit.xml 中创建 <image> 引用新纹理,或在 ru 命令中直接引用纹理路径

6.7 修改可用性、显示隐藏和初始状态

APT 组件状态可能由多处控制,修改一处可能被其他地方覆盖:

  • construct 参数m_bVisiblem_bEnabledm_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_contentSymbolm_entrySymbol
  • 初始化时是否调用 init() 或绑定 scrollbar
  • 数据是否来自 loadVariables("QueryGameEngine?...")
  • 选中变化是否绑定了 setOnChange(...) 回调
  • 回调是否向游戏侧发送 FSCommand

例如 fe_m_assetsGameSetupplayerSlotMC 内部放置了 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 是否包含组件脚本要求的子实例名(如 hitRegioncheckBoxMClabelMC
  • 帧标签是否与组件脚本的 gotoAndPlay 字符串一致
  • 文本是否由 AS2 覆盖(而非静态 ettext
  • 点击逻辑是否还依赖全局对象、枚举、消息码或 FSCommand
  • 修改贴图/shape 时,edit.xml 内嵌 <ru>.tga、资产声明 XML 是否同步
  • 如果目标来自另一个 APT 的 import,是否去那个 assets APT 检查真正的 sprite、子实例和导出名
  • 是否存在 HasColorTransformHasRatio 等额外状态字段
  • 是否有 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 属性不变

  • movieclipunknown="33"placeobjectunknown="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 旋转与斜切

  • 旋转变换涉及 rotm01rotm10(非对角线元素),不要把它们误删成 0
  • 仅改 rotm00rotm11 做的是缩放,不会旋转对象
  • 如果原始 placeobject 有非零的 rotm01/rotm10,复制到新实例时不要遗漏

8.12 API 大小写不一致

  • 反编译代码和 edit.xml 中同时存在 getURLgetUrl 两种写法
  • 同样的还有 gotoAndPlay / gotoAndPlay
  • 这是历史遗留的大小写不一致,不要在未确认工具链行为前自动"修正"它们
  • 修改脚本时保持与原始代码一致的写法

8.13 图集与 shape 边界

  • shapetop/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 内置类

可用的通信方式:

// 调用游戏引擎函数(单向,无返回值)
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 中):

  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 NN 是寄存器号)
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.0000460.0000920.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_factionMenusymbols.csv 中有大量重复的 7;factionButtonHitRegion 条目
  • 这可能是反编译工具对每一帧的 export 声明都输出了一行
  • 不影响实际功能

11. 常见排错线索

11.1 控件看得见但点不了

  • 检查 hit region 是否存在:content sprite 中 depth 5 的 buttonponame="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` |

## 附录 Cbytecode 指令速查(按功能分类)

**数据压入**`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`