查看源码下载文档
CHAPTER / 00

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

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

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

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

Wn+1=F(Wn,Cn,Rn,D)

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

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

在实际代码中,Main_Loop 协调输入、地图渲染、世界逻辑、命令队列和帧推进;LogicClass::AI 推进多种对象与系统。它没有一个现代名称为 World 的统一对象,而是由 Map、Logic、Scen、各对象池、House 和全局配置共同构成状态。主循环世界逻辑入口

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

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

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

本仓库也因此不只有 CODEWIN32LIBWWFLAT32IPXVQWINVQ 和工具目录是这套系统的历史底座。全量盘点见文件索引,构建关系见第15章

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

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

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

一个典型例子是 TechnoTypeClass::Read_INI:既处理通用生命、价格和武器,又依据武器能力和移动类型计算移动区域。这既体现数据复用,也说明配置、寻路和单位能力并非完全分离。类型读取与派生状态

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

类型定义回答“这种坦克一般是什么样”,实例回答“它此刻发生了什么”。例如 2TNK 的静态原型提供类型名、炮塔和帧相关特征;启动时复制到类型池,再由 INI 覆盖共同属性。战场实例通过自己的 Class 引用其类型。类型原型示例建立类型池

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

实例的生命也不是一次 new 和一次 delete 那么简单。ObjectClass 初始处于 Limbo,Unlimbo 负责进入有效世界,Limbo 负责脱离,另外还有跨系统的 Detach。装在运输工具里的单位、生产中尚未放置的对象、真正被销毁的对象不能被简单混为同一状态。对象初始状态进入、离开及解除引用。详见第03章

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

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

MissionClass::AI 按任务调度虚函数,具体兵种实现自己的任务逻辑。基类中很多默认任务函数只是长时间等待的占位。这也是为什么搜到 Mission_Harvest 就停下会误读:必须继续找到矿车所在派生类的实际实现。任务调度器基类任务占位

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

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

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

FootClass::Active_Click_With 的格子版本中,采矿、移动和攻击有不同映射;TechnoClass::Player_Assign_Mission 再进入命令安排层。格子点击解释玩家命令入口

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

不要据这个概念图自行推断当前帧的准确执行顺序。本项目主循环有自己的历史顺序和等待期输入处理,必须以第02章的逐段分析为准。

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

坦克转向并不需要在运行时生成一个真正三维坦克模型:主体和炮塔可以选择不同方向帧,再按位置叠绘。队伍颜色可以通过调色表替换;损伤、闪烁、阴影、选择框和血条通过不同绘制路径呈现。画面中看似统一的对象,可能由多个精灵和附加元素拼出。通用 Shape 绘制入口

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

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

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

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

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

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

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

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

阅读这类历史项目,还要多问三句:这段代码真的被当前构建选中吗?这个注释和函数体一致吗?这个数值是默认值还是已经加载配置后的最终值?这些问题贯穿整套文档,并在误读附录集中总结。

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

红色警戒初代 · 源码剖析18 章正文 / 5 份附录
输入关键词,检索 24 份完整文档
全文检索 · 点击结果直达对应小节