阅读实验、调试路线与制作自己的 RTS
1. 学习目标:能解释、能预测、能验证
源码阅读最容易产生的错觉是“所有文件名都眼熟,因此已经理解”。更有用的标准是:给出一个变化,你能预测哪些字段、函数和系统受影响,并指出需要什么证据证明预测。
以下实验分为两类。前五个仅靠仓库和文本工具即可完成;后三个需要你后续自行建立可运行版本,本文只给实验设计,没有声称已经执行。它们不依赖你先把全部历史工程修好。
2. 实验一:给一个坦克制作数据身份证
任务:选择 2TNK,列出它的枚举、静态原型、类型池建立点、INI 读取点、主要运行期类。
先查 UDATA.CPP 的原型,再查 UnitTypeClass::Init_Heap 和 Read_INI,最后沿继承找到 TechnoTypeClass::Read_INI。不要把静态原型中的每个布尔参数凭顺序猜成游戏属性;对照构造声明核验。原型、类型池初始化、共同数据读取。
合格产出:一张表中区分“代码固定、INI 可覆盖、实例独有”三类字段。若缺少实际 RULES.INI,武器、价格和生命的最终值应写“待资源确认”,不能根据记忆填入并称为源码结果。
检验题:只添加 [MYTANK],为什么不一定增加一个能生产的新兵种?答案应涉及类型注册、名称解析、枚举边界、资源以及生产资格,而不只是“因为没有图片”。
3. 实验二:手算规则转换,不混淆单位
任务:用第05章教学输入,解释 Range=5、Speed=50、Strength=420 分别变成什么。
距离经 Get_Lepton 以格为输入,转换成内部坐标量;车辆速度经过 Techno 的缩放;生命是普通整数,但后续仍有类型、难度和实例状态影响。距离转换、速度缩放。
预期结果:按一格256的坐标量,Range 得1280;Speed 50缩放为128。这两项都不是墙上时钟计时的“米/秒”。数值进入不同消费者时还会受到地形、任务、计时和取整影响。
扩展题:分别比较 Speed=99、100、101、-1 的裁剪与取整;再比较武器速度读取和车辆速度读取是否使用同一函数。目的不是证明“写负数能作弊”,而是学会追踪每个字段自己的转换契约。
4. 实验三:画出一条有障碍的寻路过程
任务:在纸上画一块小网格,加入一个 L 形障碍,标出起点和目标。对照第04章,跟踪直线尝试、碰障后的两侧边缘搜索、路径选择和平滑。
合格产出:标明每一步检查的是静态地形、移动类别、区域还是动态占用。说明为什么到达相同目标的不同单位可能得到不同结果,以及为什么已有路径仍会因另一辆车挡路而失效。
不能直接画一张标准 A* 的 open/closed 表然后称为本项目算法。先检查 FINDPATH.CPP 的实际状态和循环,再使用自己的算法术语。寻路实现文件。
扩展题:你会怎样构造一个让局部绕障产生不理想结果的地图?预测应明确是无法到达、路径过长还是容易拥堵,三种现象不能混称“寻路错误”。
5. 实验四:从“点了却不动”定位问题层级
按顺序寻找以下源码入口:FootClass::Active_Click_With、TechnoClass::Player_Assign_Mission、Queue_Mission、EventClass::Execute、任务分派、寻路和移动提交。
可使用这些只读检索,路径大小写按仓库保留:
rg -n 'Player_Assign_Mission|Queue_Mission' CODE/TECHNO.CPP CODE/QUEUE.CPP
rg -n 'case MEGAMISSION|Assign_Destination' CODE/EVENT.CPP
rg -n 'MissionClass::AI|Commence|Assign_Mission' CODE/MISSION.CPP合格产出:给三个不同故障各安排一个最早可辨别的观察点:没有入队、事件执行时对象失效、路径被障碍阻断。避免在还没确认任务写入之前就改寻路代码。入队函数、执行时检查。
6. 实验五:核查一条注释,找出真正契约
选一个看起来含义很明确的函数,例如生产扣款、伤害衰减、连接包顺序或智能指针。分开抄下“注释承诺”和“函数体实现”,再查调用者是否依赖该承诺。
示范是 FactoryClass::AI:阶段先推进,钱不够时退回;余额归零与暂停在完成条件里处理。真正契约要从分支中读出来,而不是只把它概括成“更新生产”。阶段和资金代码。
合格产出:对疑点写出条件、字面结果和待验证部分。不要把所有不符合现代习惯的代码立即叫作 bug,也不要因为注释完整就忽略代码相反的证据。第A4附录提供更多可练习对象。
7. 实验六:建立最小可运行观察场景(待环境复原)
在具备合法游戏资源和可调试构建后,先做只有一名玩家、几辆普通坦克、一个障碍和一个靶建筑的场景。固定规则、初始随机状态和命令序列。
建议记录帧号、对象稳定标识、任务、坐标、NavCom、TarCom、开火计时、生命和关键创建/销毁事件。一次只改一个参数,如炮弹伤害或射程,并比较轨迹。不要同时更换编译器、修改寻路、调整平衡和提高帧率,否则差异无法归因。
通过标准:不仅最后胜负相同,关键状态轨迹也能解释。若首个不同点在对象创建顺序,后面伤害与随机结果可能只是其连锁后果。
8. 实验七:确定性与存档恢复(待环境复原)
对同一初始状态和命令流运行两次,记录同步相关状态。先确认同一构建能复现,再比较迁移构建。若不同,定位第一处状态分歧,而不是只对最终截图。
存档试验则在某帧保存,继续运行一段;另一路加载后输入相同后续命令,比较对象关系、队伍、工厂、任务和随机序列。类型、虚表、池槽与指针编码均可能影响恢复,详见第14章。
特别注意:新增日志应只读状态。这个项目中把随机对象转换成整数会取下一个随机值;为了打印而触发这种转换,可能改变你要观察的程序。随机对象整数转换。
通过标准:明确比较了什么状态、忽略了哪些本地表现数据、从哪个帧开始一致,不能仅以“没崩溃”宣布兼容。
9. 实验八:替换一个平台模块(待环境复原)
如果目的是学习移植,选一个边界明确的模块,如文件读取、像素输出或定时器。先保留游戏层接口,再用现代实现替换底层,建立可比较的输入输出。
像素输出可比较索引色缓冲、调色板与最终截图;定时器可比较不同游戏速度下的逻辑推进和等待行为;音频可比较采样格式、播放结束通知和资源生命周期。它们的通过条件不同,不能以“窗口弹出”代替渲染正确,或以“能听到声音”代替播放队列正确。
修改整数宽度、结构布局和对象遍历顺序属于更深的兼容性工作。先证明旧行为,再决定哪些行为有意改变。迁移路线和构建缺口详见第15章。
10. 今天制作一个小 RTS,哪些思想值得留下
| 设计问题 | 可以借鉴的思想 | 今天可明确化的部分 |
|---|---|---|
| 大量同类单位 | 类型数据与实例分开 | 类型 schema、稳定 ID、引用检查 |
| 多种操作来源 | 将输入表达为语义命令 | 命令版本、验证、可记录执行顺序 |
| 持续任务 | 显式状态与定时推进 | 状态图、调试可视化、可测的转移条件 |
| 世界空间 | 占用、地形、移动能力分开 | 清晰的空间查询契约与动态障碍策略 |
| 内容制作 | 规则与场景覆盖 | 覆盖顺序、单位、错误提示和编辑器约束 |
| 运行一致性 | 固定规则、顺序与随机序列 | 确定性回归、版本化数据和状态摘要 |
| 对象生命周期 | 入场、离场、销毁显式处理 | 带代次的句柄、所有权和订阅清理 |
| 平台迁移 | 封装文件、显示、计时、音频 | 平台边界与可验证后端 |
这些是工程建议,不是声称原项目已经实现所有现代保障。也不必把所有历史结构原封不动搬进新游戏。深继承、全局变量、固定容量池、汇编优化都与当时约束有关;是否适合今天,要看项目规模、目标平台和兼容性要求。
11. 建议的自制 RTS 里程碑
- 可观察世界:网格、单位、选中与移动,能显示坐标、任务和路径。
- 完整战斗闭环:目标、射程、冷却、弹体、伤害、销毁及引用清理。
- 经济闭环:资源、采集、生产、扣款、交付失败处理。
- 内容与局部自主行为:类型配置、任务状态机、少量关卡触发器。
- 可复现性:命令记录、稳定更新顺序、保存恢复;若要联机,再建立同步层。
- 表现与工具完善:动画、声音、侧栏、资源制作和性能分析。
每一步应让前一步继续可观察、可解释。没有必要一开始复刻全部兵种、剧情影片或旧联网服务;但若目的是兼容原版,则必须把兼容范围明确写成独立目标,不能把自制近似玩法称为已经移植原引擎。
12. 本资料的验证产物
提供的 index_source.py 会重新统计跟踪文件、行数和 SHA-256;audit_guide.py 会核对源文件链接、提交和行号范围。它们帮助复核这套分析与哪个源码快照对应。
已经完成的是源码获取、主要子系统静态研读、文档交叉核验和结构检查。这里设计的游戏运行、网络联机、存档往返与移植实验尚未执行;相关原因和剩余问题见覆盖附录。