# 00　一款《红色警戒》是怎样被制作出来的

## 1. 先把战场看成一个随时间变化的数据世界

你点下一辆坦克，坦克绕过建筑、转动炮塔、发射炮弹、摧毁敌人。屏幕上的动画很连续，但程序处理的是离散变化：对象 A 的坐标变了，目标字段改了，开火计时器归零，创建了子弹 B，敌人 C 的生命减少，再创建爆炸动画。

这个世界可以抽象为以下关系式，**它是解释模型，不是源码中的数学表达式**：

\[
W_{n+1}=F(W_n,\;C_n,\;R_n,\;D)
\]

这里 W 是当前世界状态，C 是该逻辑阶段被接受的命令，R 是需要时使用的确定性随机序列，D 是已加载的规则和场景数据。玩家看到的画面则由世界、视口、调色板、精灵和界面状态共同绘制。

这种拆分让几件事变得容易理解：两个玩家可以显示不同视口却共享同一场战争；关闭声音不应改变坦克伤害；存档需要保存世界关系而不只是截一张图；联机不能只同步鼠标坐标，还必须保证命令执行的时机和顺序。

在实际代码中，`Main_Loop` 协调输入、地图渲染、世界逻辑、命令队列和帧推进；`LogicClass::AI` 推进多种对象与系统。它没有一个现代名称为 World 的统一对象，而是由 Map、Logic、Scen、各对象池、House 和全局配置共同构成状态。[主循环](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/CONQUER.CPP#L2139-L2438)、[世界逻辑入口](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/LOGIC.CPP#L198-L375)。

## 2. 制作游戏需要六类东西共同成立

| 制作内容 | 本项目中的实现载体 | 玩家看见的结果 |
|---|---|---|
| 世界与对象模型 | Object、Techno、Foot、各兵种与对象池 | 坦克、步兵、建筑、子弹能持续存在并互相作用 |
| 规则与机制 | Rules、各类型数据、武器、伤害、生产 | 不同兵种有不同强弱、价格和能力 |
| 空间与行为 | Cell、Map、寻路、Mission、Team、House AI | 单位会移动、追击、回矿、编队进攻 |
| 内容编排 | 场景 INI、触发器、事件、动作、战役转换 | 指定时刻增援、目标完成后过关 |
| 视听与交互 | Shape、调色板、地图显示、Sidebar、音视频库 | 图像、爆炸、按钮、语音和过场 |
| 系统工程 | 资源容器、内存、网络、存档、平台库与工具 | 能加载内容、联机、保存、在目标电脑运行 |

如果仅实现“坦克能在窗口里走”，只完成了这张表的一小部分。成熟 RTS 的难度在于这些系统互相约束：建造影响占格，占格影响寻路，死亡影响引用，规则改变影响联机一致性，资源加载影响切换场景。

本仓库也因此不只有 `CODE`。`WIN32LIB`、`WWFLAT32`、`IPX`、`VQ`、`WINVQ` 和工具目录是这套系统的历史底座。全量盘点见[文件索引](../appendices/A1-all-source-files.md)，构建关系见[第15章](15-platform-libraries-tools-and-porting.md)。

## 3. “游戏引擎”在这里不是一个外部产品

今天常把引擎理解为 Unity、Unreal 或独立框架。阅读这套源码时，更适合按职责划分：提供内存、文件、像素绘制和声音的通用底层；提供地图、对象、任务、命令和资源组织的 RTS 框架；承载坦克、矿车、电力与特定超武的游戏逻辑。

这些边界在代码中并不整齐。一些面向平台的条件编译直接进入游戏文件，UI 与模拟会通过共享状态关联，特殊单位也存在按类型分支的处理。所谓“引擎”和“游戏”主要是理解职责的视角，并非仓库已经实现了严格隔离的三个模块。

一个典型例子是 `TechnoTypeClass::Read_INI`：既处理通用生命、价格和武器，又依据武器能力和移动类型计算移动区域。这既体现数据复用，也说明配置、寻路和单位能力并非完全分离。[类型读取与派生状态](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/TECHNO.CPP#L6274-L6319)。

## 4. 先有“类型”，再有“这一辆坦克”

类型定义回答“这种坦克一般是什么样”，实例回答“它此刻发生了什么”。例如 2TNK 的静态原型提供类型名、炮塔和帧相关特征；启动时复制到类型池，再由 INI 覆盖共同属性。战场实例通过自己的 Class 引用其类型。[类型原型示例](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/UDATA.CPP#L158-L187)、[建立类型池](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/INIT.CPP#L248-L278)。

这个分离节省重复数据，也让调参集中生效。更深的收益是，程序可以对“任何可战斗物体”统一询问距离、目标、开火资格或损伤，然后让具体兵种覆盖细节。这里采用的是继承、虚函数、组合字段与类型分支共同工作的方式，不是 ECS。

实例的生命也不是一次 `new` 和一次 `delete` 那么简单。`ObjectClass` 初始处于 Limbo，`Unlimbo` 负责进入有效世界，`Limbo` 负责脱离，另外还有跨系统的 Detach。装在运输工具里的单位、生产中尚未放置的对象、真正被销毁的对象不能被简单混为同一状态。[对象初始状态](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/OBJECT.CPP#L125-L155)、[进入、离开及解除引用](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/OBJECT.CPP#L1340-L1484)。详见[第03章](03-object-model-and-memory.md)。

## 5. 单位不是每一帧从头“思考人生”

单位通常已有一个任务，如移动、攻击、采矿、卸载、驻守；任务内部还可能有多个阶段。一个矿车不会每帧运行通用规划器寻找“宇宙中最优策略”，而是根据当前阶段继续寻找矿、驶向目标、采集或返回。任务函数给出的等待时间控制下一次相应决策时机。

`MissionClass::AI` 按任务调度虚函数，具体兵种实现自己的任务逻辑。基类中很多默认任务函数只是长时间等待的占位。这也是为什么搜到 `Mission_Harvest` 就停下会误读：必须继续找到矿车所在派生类的实际实现。[任务调度器](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/MISSION.CPP#L213-L316)、[基类任务占位](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/MISSION.CPP#L97-L114)。

电脑对手又在另一层决定建什么、组织什么队伍、向哪里进攻。地图触发器则可以直接让一支队伍增援或执行预先安排的动作。因此必须区分单位的局部行为、电脑阵营的决策和关卡作者的脚本编排。这三者最终都能让坦克动起来，但来源不同。详见[第09章](09-ai-teams-and-missions.md)和[第10章](10-scenarios-triggers-and-campaign.md)。

## 6. “我点击了”不等于“世界立刻照做”

点击先产生交互动作，之后被转换成游戏命令，再在相应队列阶段执行。玩家对空地点击、对敌人点击、对运输车点击，同样是鼠标动作，却会经 `What_Action`、`Active_Click_With` 等路径解释为不同任务。

在 `FootClass::Active_Click_With` 的格子版本中，采矿、移动和攻击有不同映射；`TechnoClass::Player_Assign_Mission` 再进入命令安排层。[格子点击解释](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/FOOT.CPP#L1269-L1336)、[玩家命令入口](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/TECHNO.CPP#L3244-L3283)。

把输入变成可记录的语义命令，为联机和录像提供共同表示。需要同步的是“哪个单位执行什么任务、目标是什么、何时执行”，而不是另一个玩家显示器上的某个像素。反过来，这也意味着延迟、命令帧和随机序列会影响世界一致性，详见[第13章](13-network-lockstep-and-replay.md)。

不要据这个概念图自行推断当前帧的准确执行顺序。本项目主循环有自己的历史顺序和等待期输入处理，必须以[第02章](02-runtime-and-game-loop.md)的逐段分析为准。

## 7. 画面是模拟结果的表现，但也有自己的状态

坦克转向并不需要在运行时生成一个真正三维坦克模型：主体和炮塔可以选择不同方向帧，再按位置叠绘。队伍颜色可以通过调色表替换；损伤、闪烁、阴影、选择框和血条通过不同绘制路径呈现。画面中看似统一的对象，可能由多个精灵和附加元素拼出。[通用 Shape 绘制入口](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/CONQUER.CPP#L3407-L3535)。

表现层仍有必须维护的状态：哪些区域需要重画、哪个按钮按下、视口在哪里、鼠标悬停什么对象、动画进行到哪一帧。不能把“画面由世界产生”误解成界面完全无状态，更不能把某个动画对象都视为纯装饰；有的动画与伤害或生命周期有关，需要追踪它是否进入逻辑更新。

音乐、语音和过场还涉及异步播放、资源读取、设备缓冲及回调。它们不是一条 `play()` 就能涵盖的完整系统。[第11章](11-rendering-ui-and-input.md)与[第12章](12-assets-audio-and-video.md)将从游戏层一直追到平台库。

## 8. 一条闭合的游戏因果链

用“采矿后生产坦克，坦克击毁建筑”串联整套系统：

1. 关卡数据建立地图、矿、初始建筑与玩家阵营。
2. 矿车任务根据地图与占格信息选择目标，移动模块尝试生成并执行路径。
3. 采集改变地图资源和矿车载荷；卸矿改变阵营资金与储量。
4. 玩家点击建造图标，形成生产事件；工厂推进阶段并扣钱。
5. 完成的实例从未进入战场的状态转为有效世界对象，加入占格、逻辑和绘制系统。
6. 攻击命令设置目标；单位移动、转向、检查射程与冷却后产生弹体。
7. 弹体更新并触发伤害，目标可能死亡、解除引用并生成表现。
8. 场景触发器或阵营胜负逻辑观察世界变化，决定是否完成任务。

这是跨模块因果链，并非严格逐帧调用栈。中间可能跨过数百逻辑帧，还可能因为堵路、资金不足、电力不足、目标消失而暂停、分支或失败。[第16章](16-end-to-end-gameplay-traces.md)会把它拆成可跟踪的入口、状态、出口与失败点。

## 9. 怎样判断自己已经读懂

如果你能回答一个机制的输入来自哪里、状态存在哪里、谁在何时推进它、失败后怎么办、结果影响哪些其他系统，就已经比只知道类名走得更深入。

阅读这类历史项目，还要多问三句：这段代码真的被当前构建选中吗？这个注释和函数体一致吗？这个数值是默认值还是已经加载配置后的最终值？这些问题贯穿整套文档，并在[误读附录](../appendices/A4-code-reading-pitfalls.md)集中总结。

本章给出了制作原理的整体模型；它没有宣称一个抽象图就能替代全部实现。后续各章将把每个概念落到可查证的源码位置。
