从零到 Handmade Hero
这份路线在做什么
Handmade Hero 是 Casey Muratori 历时两年,从空文件夹到一个完整可玩游戏,每行代码都在视频里讲清楚的项目。原作是 C/C++ + Win32 + DirectSound + OpenGL,但你讨厌平台特定 API,所以这份路线把所有平台相关代码翻译成 Rust 的跨平台等价物——winit 管窗口、softbuffer 显示后缓冲、cpal 输出音频、gilrs 读手柄、libloading 做热重载、glutin+glow 在 Day 235 后接管 OpenGL。
学习目标不是「记住 winit 怎么用」,而是吃透每一行代码背后的原理——为什么 Casey 要把游戏代码做成 cdylib、为什么实体系统用索引不用指针、为什么 SIMD 必须 aligned、为什么光照要在 linear 空间计算、为什么 Casey 最后把 Minkowski 碰撞改成体素化方案。所有可以「用库」的地方(PNG、WAV、字体光栅、声音混音、内存分配器)Casey 都是从零写的,你也得从零写。
这是约 2.3 年的工程(每周 10–15 小时)。每一集配一个 checkbox,看完一集打勾,进度自动保存;可以用三个存档槽(左侧栏)把进度外化到同目录的 JSON 文件,就像游戏的存档/读档。每个 Phase 里有保姆级的中文讲解,每集配中文翻译 + 难度 + 类型 + 「本集你将学到」。下面还有 37 张概念卡,把 HH 用到的所有黑话(sRGB、Minkowski、depth peeling、SIMD...)都讲清楚。
🧠 递归式心智模型:从总图到细节
这份路线不是只让你“看懂代码”,而是让你不断建立可在脑中运行的系统模型。每一集都先问四个层次的问题:我现在处在系统的哪一层?这一层解决什么问题?这一层背后的原理是什么?我能不能不看代码就复述它怎么工作?
总图层
先知道整套系统的边界:输入从哪里来,状态放在哪,输出到哪里去,平台层和游戏层怎么分工。
- 关注“数据流”而不是“函数名”
- 先记住模块之间的关系
- 先回答“它为什么存在”
流程层
把一帧的执行顺序画出来:初始化 → 读取输入 → 更新游戏状态 → 生成渲染命令 → 显示/播放/同步。
- 关注“先后顺序”
- 把主循环想成一条流水线
- 问自己“下一步为什么必须先做这个”
原理层
理解视频背后的理论:为什么要双缓冲、为什么要热重载、为什么要用索引而不是指针、为什么要做线性空间光照。
- 每次只抓一个核心原理
- 先理解“问题”,再理解“方案”
- 补上视频默认你已经会的背景知识
实现层
最后才进入代码细节:具体 API、结构体字段、生命周期、unsafe 边界、错误处理、调试方法。
- 把代码当成“原理的落地”
- 每段实现都写一句自己的解释
- 能脱离源码复述,才算真的懂
🧭 这份路线的使用方式
看任何一集之前,先用一句话回答:这一集属于哪个层级、它在整个系统里补上了什么缺口。看完后,再用自己的话把“总图 → 流程 → 原理 → 代码”复述一遍。复述不出来的地方,就回到更高层补课,而不是直接死磕细节。
🛠️ 起步指南(保姆级)
在开始 Day 001 之前,先按这 8 步把环境搭好。每一步都给了可以复制粘贴的命令。如果你是 Linux/Rust 新手,不要跳步——后面跟 HH 的时候出问题,90% 都是基础环境问题。
🔧 技术栈对照表
原作的 Win32/C++ 翻译成下面的 Rust 跨平台方案。所有「原作手写」的部分(PNG/WAV/字体/mixer/分配器/软渲染器/debug 系统)继续手写——这才是 Handmade Hero 的核心价值。
| 原作 (Win32/C++) | Rust 跨平台等价 | 用在哪 |
|---|---|---|
| CreateWindowEx + message loop | winit | 窗口与事件循环 |
| StretchDIBits(后缓冲显示) | softbuffer | Day 1–234 软渲染阶段 |
| wglCreateContext + wgl 扩展 | glutin + glow | Day 235+ OpenGL 阶段 |
| QueryPerformanceCounter | std::time::Instant | 帧时间 |
| __rdtsc | std::arch::x86_64::_rdtsc | CPU 周期计数 |
| DirectSound | cpal(原始流) | 音频输出,mixer 自己写 |
| XInput | gilrs | 手柄输入 |
| CRITICAL_SECTION / CreateThread | std::sync + std::thread | 线程与同步 |
| LoadLibrary + 动态重载 | libloading | Day 21–23 热重载 |
| Win32 File API | std::fs + std::io | 文件 I/O |
| 手写 PNG/WAV/字体光栅/声音 mixer/分配器 | 全部手写 | 跟 Casey 一致 |
明确不用: SDL2(藏太多细节)、minifb(切换 OpenGL 成本高)、rodio(藏 mixer)、image/hound/rusttype(违背「自己写」原则)、wgpu(太现代,与 HH 教学路线不符)。
💡 核心概念卡(37 张)
HH 全剧反复用到的核心概念。点击卡片看详细解释 + Casey 哪几集用到 + 深入学习资源。建议在跟到对应 Phase 前先扫一遍该 Phase 用到的卡。
⏱️ 每集工作流(单集 1.5–3 小时)
- 看视频 — 用 guide.handmadehero.org 看 Casey 自己做的逐行注释版。Q&A 和 Bug-fix 集可以 1.5–2x 速听。
- 读源码 diff — github.com/HandmadeHero/cpp 的 commit,或本地
git log -p --follow。 - 笔记:在项目的
notes/day-NNN.md记录:Casey 这集讲了什么、哪些是 Win32-ism 翻译成什么 Rust 等价物、涉及的 CS 概念。也可以在每集下方的「笔记」框里写。 - 实现:严格跟着 Casey 的代码结构,不要发挥。Rust 学习者最大的诱惑是「用更 Rusty 的方式重写」——这会让你和 Casey 的代码偏离,后面调试就麻烦了。
- 验证:运行后看画面是否与 Casey 当天的截图一致。声音、输入、性能都要对得上。
- 提交:
git commit -m "Day NNN: <title>"。强制自己每天一个 commit,长期下来就是完整学习记录。 - 每周日复盘:在
journal.md写这周学了什么、卡在哪里、下周重点。 - 回来打勾:回到这份 HTML,把对应 Day 的 checkbox 打勾,进度自动保存。建议每集打勾时顺手存档到 Slot 1。
🎯 关键里程碑(庆祝点)
- M1 (Day 25): 平台层完成,屏幕上有会动的方框,热重载生效。
- M2 (Day 70): 完整可玩 2D 关卡,有怪物、攻击、投射物。
- M3 (Day 121): 🔥🔥🔥 SIMD 软渲染 tiled marathon 完成。
- M4 (Day 175): 字体能渲染,内存分配器完工。
- M5 (Day 234): Debug 系统完成,有自己的 profiler。
- M6 (Day 260): OpenGL 迁移完成。
- M7 (Day 435): 光照系统完工(此时已是 Handmade Hero 高手)。
- M8 (Day 575): 光探针完工,有 in-game 编辑器、世界生成器。
- M9 (Day 667): 🎉 全剧终——体素碰撞、k-d tree raycaster、octahedral 光照、实体存储简化收官。
⚠️ 风险与应对
| 风险 | 应对 |
|---|---|
| 卡在某个 Day 几天没进展 | 用 notes/day-NNN-stuck.md 记录卡点,跳过去做下一集,周末回头 |
| 跟着跟着忘了前面的概念 | 每个 Phase 末尾花 1 周复习 + 整理 notes/index.md |
| Day 235 切 OpenGL 时炸了 | 这周专门停下来,先把 glutin+glow 单独跑通三角形,再回来 |
| Day 112+ SIMD 学不动 | 提前补 CS:《Computer Systems: A Programmer's Perspective》第 5 章 |
| Day 436+ PNG 解码太难 | 官方 PNG spec (w3.org/TR/png) 一节一节对照,Casey 也是这么讲的 |
| Day 590+ k-d tree/grid raycaster 转不过弯 | 把 Handmade Ray 系列(ray00–ray04,5 集)作为前置热身 |
| Day 638+ 体素碰撞重构工程量太大 | Casey 自己也花了 8 集才搞定,允许自己慢 |
| 半途而废 | 找学习伙伴或社区(Handmade Network 论坛、r/HandmadeHero、B 站跟做群),每周汇报进度 |
🧠 游戏引擎的全局心智模型
在看第一行代码之前,先在脑子里建立这个可运行的模型。能把下面三层说清楚,整个 HH 就不会迷失方向。
1. 读输入 ← 键盘 / 手柄 / 鼠标状态
2. 更新状态 ← 物理 / AI / 碰撞 / 游戏逻辑
3. 渲染 ← 状态 → 一帧像素(写 framebuffer)
4. 播音频 ← 状态 → 音频样本(写 audio buffer)
// 回到 1
• 帧缓冲 → 屏幕
• 音频回调 → 声卡
• 输入设备轮询
• 热重载游戏 .so
• 内存分配(大块)
• 碰撞检测 / 物理
• AI / 游戏逻辑
• 生成渲染命令
• 生成音频样本
• 完全不知道自己在什么 OS 上
GameInput 结构体 →
游戏更新函数 →
Push Buffer(渲染命令列表) →
软渲染器/OpenGL →
像素数组 →
buffer.present() →
屏幕
「为什么热重载(Day 21-23)不需要重启程序?游戏状态存在哪里?」
——你就已经建立了足够的全局心智模型,可以开始 Phase 1 了。
Intro to C on Windows(穿插)
10 集 · 可选这是 Casey 在开始 Handmade Hero 之前录的 C 语言入门,主要面向从 C#/Java/Python 转过来的人。既然你用 Rust,这 10 集主要价值是对照 C 与 Rust 的内存模型——指针 vs 引用、malloc/copy vs ownership、define vs const/macro。可以 1.5x 速听,不写代码。
Handmade Ray(穿插,Phase 8 前热身)
5 集 · 强烈推荐这是 Casey 在主项目之外的支线,5 集讲完一个简单的 raycaster。重点是 SIMD raycast 和多线程——Phase 8(Day 590+)k-d tree/grid raycaster 之前的最佳热身材料。建议在跟到 Day 580 左右时停下来,花一周把这 5 集做了。
Handmade Chat(穿插,深度话题)
17 集 · 可选Casey 与嘉宾深谈特定话题的系列:cache、UB、CRTP、imposter syndrome、IK、shader 分析……不写代码,纯讨论。可以在等编译、跑步机、通勤路上听。