From 83477a7f6459b608d300fd7881991090d81d8d3b Mon Sep 17 00:00:00 2001 From: cc Date: Tue, 23 Jun 2026 20:32:35 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E5=88=9D=E5=A7=8B=E5=8C=96=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E9=AA=A8=E6=9E=B6=E4=B8=8E=E5=AD=A6=E4=B9=A0=E5=BC=80?= =?UTF-8?q?=E5=8F=91=E8=AE=A1=E5=88=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 搭建 Cargo workspace:engine 库 + sandbox 可执行示例,构建/运行通过 - 加入分阶段学习开发计划(Markdown / CSV / xlsx) - 配置 .gitignore、README、MIT 许可信息 Co-Authored-By: Claude Opus 4.8 --- .gitignore | 24 +++ Cargo.lock | 14 ++ Cargo.toml | 18 ++ README.md | 53 ++++++ crates/engine/Cargo.toml | 17 ++ crates/engine/src/lib.rs | 40 +++++ examples/sandbox/Cargo.toml | 11 ++ examples/sandbox/src/main.rs | 8 + 引擎学习开发计划.csv | 37 ++++ 引擎学习开发计划.md | 306 ++++++++++++++++++++++++++++++++++ 引擎学习开发计划.xlsx | Bin 0 -> 15159 bytes 11 files changed, 528 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 crates/engine/Cargo.toml create mode 100644 crates/engine/src/lib.rs create mode 100644 examples/sandbox/Cargo.toml create mode 100644 examples/sandbox/src/main.rs create mode 100644 引擎学习开发计划.csv create mode 100644 引擎学习开发计划.md create mode 100644 引擎学习开发计划.xlsx diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..931cbbf --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Rust / Cargo 构建产物 +/target/ +**/target/ + +# Cargo.lock:库不提交,但本项目含可执行程序,建议提交以锁定依赖版本(保留) +# 若日后纯做库可改为忽略 + +# 编辑器 / IDE +.vscode/ +.idea/ +*.iml + +# 操作系统 +.DS_Store +Thumbs.db +desktop.ini + +# Office 临时/锁文件(Excel/Word 打开文档时生成) +~$* + +# 调试与临时文件 +*.pdb +*.log +*~ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..b3f548f --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,14 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "engine" +version = "0.0.1" + +[[package]] +name = "sandbox" +version = "0.0.1" +dependencies = [ + "engine", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..3bad875 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,18 @@ +# Cargo 工作空间(workspace) —— 把"引擎库"和"测试用沙盒"放在一起统一管理 +[workspace] +resolver = "2" +members = [ + "crates/engine", # 引擎本体(库 crate),你要造的东西在这里 + "examples/sandbox", # 沙盒(可执行程序),用来调用并测试引擎 +] + +# 所有成员共享的包级元信息,避免重复填写 +[workspace.package] +edition = "2021" +license = "MIT" +authors = ["cc"] +repository = "ssh://gitea@8.130.143.54:2222/cc/2DGameEngineByRust.git" + +# 在这里统一管理依赖版本,成员 crate 用 `xxx.workspace = true` 引用 +# 现阶段保持空白,按学习计划逐阶段添加(winit / wgpu / glam ...) +[workspace.dependencies] diff --git a/README.md b/README.md new file mode 100644 index 0000000..cd3c021 --- /dev/null +++ b/README.md @@ -0,0 +1,53 @@ +# 2DGameEngineByRust + +用 Rust 从零手写的 2D 游戏引擎 —— 一个零基础学习者的引擎养成项目。 + +## 这是什么 + +我在零编程/零引擎经验的基础上,通过亲手造一个 2D 引擎来系统巩固游戏引擎知识, +之后会用它做一款小游戏。完整路线见: + +- 📖 [引擎学习开发计划.md](引擎学习开发计划.md) —— 详细的分阶段学习与开发计划 +- 📊 [引擎学习开发计划.csv](引擎学习开发计划.csv) —— 任务清单(可当进度看板) + +## 项目结构 + +``` +. +├── Cargo.toml # 工作空间(workspace)配置 +├── crates/ +│ └── engine/ # 引擎本体(库 crate)—— 要造的东西在这里 +├── examples/ +│ └── sandbox/ # 沙盒(可执行程序)—— 调用并测试引擎 +├── 引擎学习开发计划.md +└── 引擎学习开发计划.csv +``` + +## 快速开始 + +```bash +# 构建全部 +cargo build + +# 运行沙盒(会打印引擎问候语,验证骨架可用) +cargo run -p sandbox + +# 跑测试 +cargo test +``` + +## 进度 + +- [x] 阶段 0 之前:项目骨架搭建完成 +- [ ] 阶段 0:地基(Rust 语言 / 数学 / 图形学认知) +- [ ] 阶段 1:工具链与环境 +- [ ] 阶段 2:窗口与渲染入门 +- [ ] 阶段 3:自己的 2D 渲染器 +- [ ] 阶段 4:引擎核心架构 +- [ ] 阶段 5:引擎子系统 +- [ ] 阶段 6:编辑器与工具链 +- [ ] 阶段 7:用引擎做一款游戏 + +## 许可证 + +MIT diff --git a/crates/engine/Cargo.toml b/crates/engine/Cargo.toml new file mode 100644 index 0000000..4833170 --- /dev/null +++ b/crates/engine/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "engine" +version = "0.0.1" +description = "一个从零手写的 2D 游戏引擎(学习用)" +edition.workspace = true +license.workspace = true +authors.workspace = true +repository.workspace = true + +# 这是一个库 crate:引擎的所有功能都在这里实现,供 sandbox 或将来的游戏调用 +[lib] + +[dependencies] +# 现阶段无依赖。按 引擎学习开发计划 逐步添加,例如: +# winit = "0.30" # 阶段 2.B 窗口 +# wgpu = "23" # 阶段 2.B GPU 渲染 +# glam = "0.29" # 阶段 4.2 数学库 diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs new file mode 100644 index 0000000..fb1e2d8 --- /dev/null +++ b/crates/engine/src/lib.rs @@ -0,0 +1,40 @@ +//! # engine —— 一个从零手写的 2D 游戏引擎 +//! +//! 这是引擎的入口。现在它几乎是空的,这很正常—— +//! 你会按照《引擎学习开发计划》一个阶段一个阶段地把它填满。 +//! +//! 规划中的模块(随学习进度逐步取消注释并实现): +//! - `app` 应用与游戏主循环 (计划 阶段 4.1) +//! - `render` 2D 渲染器 (计划 阶段 3) +//! - `math` 数学类型封装 (计划 阶段 4.2) +//! - `ecs` 实体-组件-系统 (计划 阶段 4.5) +//! - `input` 输入系统 (计划 阶段 5.1) +//! - `audio` 音频系统 (计划 阶段 5.2) +//! - `physics` 2D 物理与碰撞 (计划 阶段 5.3) +//! +//! 例如将来会这样写: +//! ```ignore +//! pub mod app; +//! pub mod render; +//! pub mod math; +//! ``` + +/// 返回引擎版本号。这是第一个能跑通的功能,用来验证项目骨架可用。 +pub fn version() -> &'static str { + env!("CARGO_PKG_VERSION") +} + +/// 占位的问候函数,sandbox 会调用它,证明"库 → 可执行程序"的调用链通了。 +pub fn greeting() -> String { + format!("2D 引擎已就绪 (v{}),开始你的阶段 0 吧!", version()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn version_not_empty() { + assert!(!version().is_empty()); + } +} diff --git a/examples/sandbox/Cargo.toml b/examples/sandbox/Cargo.toml new file mode 100644 index 0000000..0f59ed1 --- /dev/null +++ b/examples/sandbox/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "sandbox" +version = "0.0.1" +description = "测试引擎用的沙盒程序" +edition.workspace = true +license.workspace = true +authors.workspace = true + +# 这是一个可执行程序:它依赖 engine 库,用来试验和验证引擎功能 +[dependencies] +engine = { path = "../../crates/engine" } diff --git a/examples/sandbox/src/main.rs b/examples/sandbox/src/main.rs new file mode 100644 index 0000000..dcd633a --- /dev/null +++ b/examples/sandbox/src/main.rs @@ -0,0 +1,8 @@ +//! 沙盒程序:调用 engine 库,验证项目骨架是否能正常构建运行。 +//! +//! 随着引擎长大,这里会逐步变成"创建窗口 → 启动游戏循环 → 渲染精灵"。 +//! 现在它只做一件事:打印引擎的问候语。 + +fn main() { + println!("{}", engine::greeting()); +} diff --git a/引擎学习开发计划.csv b/引擎学习开发计划.csv new file mode 100644 index 0000000..1298986 --- /dev/null +++ b/引擎学习开发计划.csv @@ -0,0 +1,37 @@ +阶段,序号,任务/知识点,类型,是否有教程,推荐教程(中文优先),链接,产出/备注 +阶段0 地基,0.1,Rust语言入门(所有权/借用/trait/泛型/错误处理),语言,✅有中文教程,Rust程序设计语言中文版 / Rust语言圣经,https://kaisery.github.io/trpl-zh-cn/ ; https://course.rs/,能独立写带结构体枚举trait的小程序 +阶段0 地基,0.2,数据结构与基本算法,理论,✅/🔧,Rust语言圣经"手把手实现链表"+通用数据结构课,https://course.rs/,理解ECS用连续数组为何更快 +阶段0 地基,0.3,游戏数学:线性代数(向量/矩阵/变换/正交投影),理论,✅有中文教程,GAMES101前几讲(线代复习+变换),https://www.bilibili.com/video/BV1X7411F744/,能手算旋转+平移的矩阵 +阶段0 地基,0.4,计算机图形学基础(光栅化/帧缓冲/着色器/管线),理论,✅有中文教程,GAMES101(重点光栅化与变换),https://www.bilibili.com/video/BV1X7411F744/,能说清图片如何显示到屏幕 +阶段0 地基,0.5,引擎宏观认知+游戏设计模式,理论,🔧有教程需改写,游戏编程模式中文版 + The Cherno第1集,https://gpp.tkchu.me/ ; https://www.bilibili.com/video/BV14v411h7Hu/,画出引擎模块草图 +阶段0 地基,0.6,Git版本控制,工具,✅有中文教程,廖雪峰Git教程,https://liaoxuefeng.com/books/git/introduction/index.html,项目托管到GitHub +阶段1 工具链,1.1,安装Rust工具链(rustup/cargo),工具,✅有中文教程,TRPL中文版入门指南,https://kaisery.github.io/trpl-zh-cn/ch01-00-getting-started.html,cargo run跑通HelloWorld +阶段1 工具链,1.2,配置VS Code + rust-analyzer + 调试器,工具,✅有中文教程,B站搜"VS Code Rust 环境配置",https://rust-analyzer.github.io/,代码有智能提示和实时报错 +阶段1 工具链,1.3,理解crate生态(crates.io/docs.rs/关键库定位),工具,✅有中文教程,crates.io / docs.rs,https://crates.io ; https://docs.rs,能自查一个库怎么用 +阶段2 渲染入门,2.A,用macroquad做小游戏建立手感(贪吃蛇/打砖块),代码,🔧有教程需改写,Macroquad官方书(英文,代码极短),https://mq.agical.se/ ; https://github.com/not-fl3/macroquad,做出一个能玩的贪吃蛇 +阶段2 渲染入门,2.B,winit开窗口 + wgpu初始化 + 第一个三角形,代码,✅有中文教程,学习wgpu中文版,https://jinleili.github.io/learn-wgpu-zh/,窗口里出现彩色三角形 +阶段3 2D渲染器,3.1,渲染管线/WGSL着色器/顶点索引缓冲,代码,✅有中文教程,学习wgpu中文版"管线/缓冲"章,https://jinleili.github.io/learn-wgpu-zh/,理解着色器与缓冲 +阶段3 2D渲染器,3.2,纹理加载(image库)与精灵Sprite渲染,代码,✅有中文教程,学习wgpu中文版"纹理与绑定组"章,https://jinleili.github.io/learn-wgpu-zh/,屏幕显示一张图片 +阶段3 2D渲染器,3.3,2D正交摄像机与世界/屏幕坐标转换,代码,✅有中文教程,学习wgpu中文版"Uniform与摄像机"章(简化为正交),https://jinleili.github.io/learn-wgpu-zh/,能移动缩放视角 +阶段3 2D渲染器,3.4,批处理渲染Batch Rendering(2D性能关键),代码,🔧有教程需改写,The Cherno "2D Batch Rendering"系列(C++),https://www.bilibili.com/video/BV14v411h7Hu/,流畅画上千精灵 +阶段3 2D渲染器,3.5,文本渲染(字体图集/SDF),代码,🔧有教程需改写,glyphon/wgpu_text库示例 + 位图字体原理,https://docs.rs/glyphon/,屏幕显示分数与调试信息 +阶段4 核心架构,4.1,游戏主循环(固定时间步长),代码,✅有中文教程,游戏编程模式"游戏循环"章,https://gpp.tkchu.me/game-loop.html,物理在不同帧率表现一致 +阶段4 核心架构,4.2,集成数学库glam,代码,✅有中文教程,glam文档(概念已掌握),https://docs.rs/glam/,引擎统一Vec2/Mat3类型 +阶段4 核心架构,4.3,日志系统(log/tracing分级日志),代码,✅有中文教程,log/env_logger库README + Cherno Logging集,https://docs.rs/log/,能打印分级调试信息 +阶段4 核心架构,4.4,事件系统(观察者/事件分发),代码,🔧有教程需改写,The Cherno事件系统系列 + 游戏编程模式观察者章,https://gpp.tkchu.me/observer.html,各系统能订阅处理事件 +阶段4 核心架构,4.5,ECS实体组件系统(先用hecs/bevy_ecs再考虑自造),架构,✅/⚠️,Bevy中文教程/Cheatbook + 游戏编程模式组件章,https://yiviv.com/bevy-cheatbook/ ; https://bevyengine-cn.github.io/learn/book_cn/getting-started/ecs/,能创建实体挂组件写系统 +阶段4 核心架构,4.6,资源管理器(纹理/音频/字体缓存+句柄),架构,⚠️无现成教程,参考Bevy Assets设计自行实现,https://bevy.org/,assets.load接口 +阶段5 子系统,5.1,输入系统(键鼠/手柄封装+按键映射),代码,🔧有教程需改写,winit输入文档 + gilrs手柄库,https://docs.rs/gilrs/,input.is_pressed接口 +阶段5 子系统,5.2,音频系统(音效/BGM/音量,kira或rodio),代码,✅/🔧,kira/rodio库示例 + B站"Rust音频",https://docs.rs/kira/,游戏能放音效和BGM +阶段5 子系统,5.3,2D物理与碰撞(AABB/圆/响应,或集成rapier2d),代码,🔧/⚠️,B站"AABB/SAT碰撞" + Randy Gaul物理教程 + rapier2d,https://rapier.rs/,角色能站平台被墙挡 +阶段5 子系统,5.4,精灵动画系统(序列帧/图集/动画状态机),代码,⚠️无现成教程,状态机参考游戏编程模式"状态"章自行实现,https://gpp.tkchu.me/state.html,角色播放走跳待机动画 +阶段5 子系统,5.5,粒子系统(发射器/生命周期/批量渲染),代码,🔧有教程需改写,The Cherno "Particle System"集(C++),https://www.bilibili.com/video/BV14v411h7Hu/,爆炸火焰等特效 +阶段5 子系统,5.6,场景管理与序列化(serde+ron/json),代码,✅/⚠️,B站/掘金"Rust serde" + 自行设计场景,https://serde.rs/,关卡能存文件再读回 +阶段5 子系统,5.7,游戏内UI系统(集成egui),代码,✅有中文教程,egui官方示例 + B站"Rust egui教程",https://github.com/emilk/egui,主菜单/暂停界面/HUD +阶段6 编辑器,6.1,集成egui编辑器面板(实体列表/属性Inspector),工具,✅有中文教程,egui文档 + The Cherno Editor系列建立设计观,https://github.com/emilk/egui,编辑器里选中实体改属性 +阶段6 编辑器,6.2,资源热重载(notify监听文件变化),工具,⚠️无现成教程,notify库README自行实现,https://docs.rs/notify/,改图不用重启程序 +阶段6 编辑器,6.3,关卡/地图编辑(自做或对接Tiled),工具,🔧有教程需改写,Tiled官方(有中文) + tiled rust库,https://www.mapeditor.org/ ; https://docs.rs/tiled/,用编辑器拼一张关卡 +阶段7 做游戏,7.1,选题与游戏设计(小而完整),设计,✅有中文教程,indienova独立游戏社区 + B站游戏设计入门,https://indienova.com/,一页纸设计文档 +阶段7 做游戏,7.2,用引擎实现完整游戏循环(菜单到结算到存档),代码,⚠️无现成教程,串起所有系统并反向补全引擎,,从头玩到尾的完整小游戏 +阶段7 做游戏,7.3,打磨与优化(性能/手感juice),代码,🔧有教程需改写,GDC "Juice it or lose it"(B站搬运),https://www.bilibili.com/,手感舒服不卡顿 +阶段7 做游戏,7.4,跨平台打包与发布(Win/Mac/Linux/可选WASM),工具,✅/🔧,cargo build --release + 学习wgpu中文版Web章,https://jinleili.github.io/learn-wgpu-zh/,发给朋友能玩/发布itch.io diff --git a/引擎学习开发计划.md b/引擎学习开发计划.md new file mode 100644 index 0000000..41450e5 --- /dev/null +++ b/引擎学习开发计划.md @@ -0,0 +1,306 @@ +# 用 Rust 从零构建 2D 游戏引擎 —— 完整学习与开发计划 + +> 适用对象:**零编程经验、零引擎经验**的初学者 +> 目标:① 系统巩固游戏引擎知识 → ② 亲手造一个能用的 2D 引擎 → ③ 用它做一款小游戏 +> 时间:不设硬性期限,以"真正学会"为准 + +--- + +## 一、先读这段(最重要的认知) + +1. **没有"一条龙"中文教程。** 没有任何单一教程能带你"从零用 Rust 写完整 2D 引擎"。这件事的本质是**拼装**:Rust 语言、数学、图形学、各个引擎子系统,分别有各自的好教程,你要把它们组装起来。本计划就是这张"组装说明书"。 + +2. **引擎理论是跨语言的。** 著名的 **The Cherno 游戏引擎系列**(B站有中文字幕/配音搬运)虽然用 C++,但它讲的**架构思想**(事件系统、渲染抽象层、ECS、编辑器)是通用的。看它学"为什么这么设计",回到 Rust 自己实现。 + +3. **不要一上来就造引擎。** 正确顺序是:先学会 Rust → 用现成简单库(macroquad)做几个小游戏,理解"游戏循环/精灵/输入"是什么感觉 → 再学 wgpu 自己写渲染器 → 再逐个搭建引擎系统。**先会用,再会造。** + +4. **教程标记含义:** + - ✅ **有直接中文教程** —— 跟着做即可 + - 🔧 **有教程需改写** —— 教程是英文或别的语言(如 C++),思想通用,需自己翻译成 Rust + - ⚠️ **无现成教程** —— 需查文档+参考开源引擎源码自己实现(这是最能巩固知识的环节) + +--- + +## 二、阶段总览(路线图) + +| 阶段 | 名称 | 核心目标 | 大致占比 | +|------|------|----------|----------| +| 阶段 0 | 地基:语言 + 数学 + 图形学认知 | 能读会写 Rust,懂向量矩阵,懂图形学基本概念 | 最大头 | +| 阶段 1 | 工具链与环境 | 搭好开发环境,理解 Cargo 生态 | 小 | +| 阶段 2 | 窗口与渲染入门 | 开窗口、画出第一个三角形/精灵 | 中 | +| 阶段 3 | 自己的 2D 渲染器 | 精灵、纹理、批处理、摄像机、文字 | 大 | +| 阶段 4 | 引擎核心架构 | 游戏循环、ECS、事件、资源、日志 | 大 | +| 阶段 5 | 引擎子系统 | 输入、音频、物理/碰撞、动画、粒子、场景 | 大 | +| 阶段 6 | 编辑器与工具链 | egui 编辑器、序列化、热重载 | 中 | +| 阶段 7 | 用引擎做一款游戏 | 完整小游戏 + 跨平台打包发布 | 中 | + +--- + +## 阶段 0 —— 地基(语言 / 数学 / 图形学认知) + +> 这是零基础玩家最容易想跳过、但绝对不能跳过的部分。地基不牢,后面寸步难行。 + +### 0.1 编程与 Rust 语言入门 ✅ +- **任务**:掌握变量、控制流、函数、所有权(ownership)、借用、生命周期、结构体、枚举、trait、泛型、错误处理、模块、Cargo。 +- **中文教程**: + - 《Rust 程序设计语言》简体中文版(官方书翻译,权威):https://kaisery.github.io/trpl-zh-cn/ + - 《Rust 语言圣经》(讲解最细、最贴近实战):https://course.rs/ ,配套练习:https://practice.course.rs/ +- **产出**:能独立写出带结构体/枚举/trait 的小程序(如命令行计算器、待办清单)。 + +### 0.2 数据结构与基本算法 ✅/🔧 +- **任务**:数组/动态数组、哈希表、栈、队列、树;时间复杂度概念。引擎里到处是它们。 +- **资源**:《Rust 语言圣经》中"手把手实现链表"章节;其余概念可看任意中文数据结构课程(B站搜"数据结构 基础"),思想通用。 +- **产出**:理解"为什么 ECS 用连续数组存组件会更快"。 + +### 0.3 游戏数学:线性代数 ✅ +- **任务**:向量(加减、点乘、叉乘、归一化)、矩阵(变换、平移/旋转/缩放)、坐标系、正交投影。**2D 引擎几乎只需要这些。** +- **中文教程**: + - **GAMES101 现代计算机图形学入门(闫令琪)** 前几讲专门复习线性代数与变换,强烈推荐:https://www.bilibili.com/video/BV1X7411F744/ + - 配套笔记很多(知乎搜 "GAMES101 笔记")。 +- **产出**:能手算"把一个点旋转 30° 再平移"对应的矩阵。 + +### 0.4 计算机图形学基础认知 ✅ +- **任务**:理解光栅化、像素、帧缓冲、颜色、什么是 GPU/着色器、渲染管线大致流程。**只需 2D 相关部分,光线追踪/动画那几讲可略读。** +- **中文教程**:GAMES101(同上),重点看"光栅化""变换"部分。 +- **产出**:能说清"一张图片是怎么显示到屏幕上的"。 + +### 0.5 游戏引擎宏观认知 + 设计模式 🔧 +- **任务**:建立"引擎由哪些系统组成"的整体地图;学常见游戏编程模式(游戏循环、状态机、观察者、对象池、组件模式等)。 +- **资源**: + - **《游戏编程模式》中文版**(免费在线,必读):https://gpp.tkchu.me/ 或镜像 https://github.com/tkchu/Game-Programming-Patterns-CN + - **The Cherno 游戏引擎系列**(B站中文字幕)第 1 集"系列介绍"先建立全局观:https://www.bilibili.com/video/BV14v411h7Hu/ + - (进阶参考书)《游戏引擎架构》(Jason Gregory) 有中文纸质版,可作长期工具书,不必现在读完。 +- **产出**:能画出一张"我的引擎要有哪些模块"的草图。 + +### 0.6 Git 版本控制 ✅ +- **任务**:init / add / commit / branch / merge / 远程仓库,会用 GitHub。 +- **中文教程**:廖雪峰《Git 教程》:https://liaoxuefeng.com/books/git/introduction/index.html +- **产出**:把你的引擎项目托管到 GitHub,养成提交习惯。 + +--- + +## 阶段 1 —— 工具链与开发环境 + +### 1.1 安装 Rust 工具链 ✅ +- **任务**:用 `rustup` 安装 Rust,理解 `cargo`(构建/依赖/运行/测试一体)。 +- **教程**:TRPL 中文版"入门指南":https://kaisery.github.io/trpl-zh-cn/ch01-00-getting-started.html +- **产出**:`cargo new`、`cargo run` 跑通 Hello World。 + +### 1.2 编辑器/IDE 配置 ✅ +- **任务**:VS Code + `rust-analyzer` 插件(自动补全、报错、跳转);会用调试器。 +- **教程**:B站搜 "VS Code Rust 开发环境配置",或 rust-analyzer 官网。 +- **产出**:写代码有智能提示和实时报错。 + +### 1.3 理解 crate 生态 ✅ +- **任务**:会查 https://crates.io 和 https://docs.rs ;理解后面要用的关键库定位: + - `winit`(窗口)、`wgpu`(图形)、`glam`(数学)、`image`(图片加载)、`rodio`/`kira`(音频)、`egui`(GUI)、`serde`(序列化)、`hecs`/`bevy_ecs`(ECS,可选)。 +- **产出**:能自己在 docs.rs 查一个库怎么用。 + +--- + +## 阶段 2 —— 窗口与渲染入门(先会用,建立手感) + +### 2.A 用现成简单库做手感(强烈建议先做)✅/🔧 +- **任务**:用 **macroquad**(极简 2D 库)做 1~2 个小游戏(贪吃蛇、打砖块),亲身体会"游戏循环、画精灵、读输入、播音效"。**这一步让你知道引擎最终要给用户提供什么样的体验。** +- **教程**: + - 官方书《Game Development in Rust with Macroquad》(英文,但代码极短易懂):https://mq.agical.se/ + - macroquad 仓库示例:https://github.com/not-fl3/macroquad + - 中文较少,B站搜 "macroquad" / "Rust 游戏" 有零星视频。 +- **产出**:一个能玩的贪吃蛇。**做完你会对"引擎该有什么"豁然开朗。** + +### 2.B 开始造引擎:窗口 + GPU 初始化 ✅ +- **任务**:用 `winit` 创建窗口、处理窗口事件(关闭、缩放);用 `wgpu` 初始化 GPU、画出第一个三角形。 +- **中文教程**: + - **《学习 wgpu》中文版**(核心教程,从开窗到三角形手把手):https://jinleili.github.io/learn-wgpu-zh/ + - 英文原版(更新更快):https://sotrh.github.io/learn-wgpu/ +- **产出**:自己的窗口里出现一个彩色三角形。 + +--- + +## 阶段 3 —— 构建自己的 2D 渲染器 + +> 跟着《学习 wgpu》中文版逐章推进,同时把通用代码抽象成"你引擎的渲染模块"。 + +### 3.1 渲染管线 / 着色器 / 顶点缓冲 ✅ +- 教程:《学习 wgpu》中文版 "管线 / 缓冲区 / 纹理" 各章。 +- 产出:理解 WGSL 着色器、顶点/索引缓冲。 + +### 3.2 纹理加载与精灵(Sprite)渲染 ✅ +- 任务:用 `image` 库加载 PNG,渲染成带贴图的四边形(Quad)。 +- 教程:《学习 wgpu》中文版 "纹理与绑定组" 章节。 +- 产出:屏幕上显示一张图片。 + +### 3.3 摄像机与正交投影 ✅ +- 任务:实现 2D 正交摄像机(平移、缩放),世界坐标↔屏幕坐标。 +- 教程:《学习 wgpu》中文版 "Uniform 缓冲与摄像机" 章节(其相机虽是3D,2D 把它简化为正交即可)。 +- 数学依赖:阶段 0.3 的投影矩阵。 +- 产出:能移动/缩放视角看场景。 + +### 3.4 批处理渲染(Batch Rendering)⚠️/🔧 +- 任务:把成百上千个精灵合并成少量 draw call,这是 2D 引擎性能关键。 +- 资源:**The Cherno** 有专门的 "2D Batch Rendering" 系列(C++,思想直接照搬到 wgpu):B站搜 "Cherno 批处理 / Batch"。 +- 产出:能流畅画上千个精灵。 + +### 3.5 文本渲染 🔧 +- 任务:渲染文字(字体图集/SDF)。可用 `glyphon` / `wgpu_text` 等库,或自己做位图字体。 +- 资源:相关库的英文 README + 示例;位图字体原理 B站可搜。 +- 产出:屏幕上显示分数、调试信息。 + +--- + +## 阶段 4 —— 引擎核心架构 + +### 4.1 游戏主循环(固定时间步长)🔧 +- 任务:实现 `update`/`render` 分离、固定时间步(fixed timestep)让物理稳定、帧率无关。 +- 资源:**《游戏编程模式》"游戏循环"一章**(中文,必读):https://gpp.tkchu.me/game-loop.html +- 产出:物理在不同帧率下表现一致。 + +### 4.2 数学库集成 ✅ +- 任务:引入 `glam`(向量/矩阵/四元数高性能库),统一引擎数学类型。 +- 资源:glam docs.rs;概念已在阶段 0.3 掌握。 +- 产出:引擎里有统一的 `Vec2 / Mat3` 等类型。 + +### 4.3 日志系统 ✅ +- 任务:用 `log` + `env_logger` 或 `tracing` 做分级日志。 +- 资源:库 README;The Cherno 也有 "Logging" 一集讲设计思路。 +- 产出:能打印分级调试信息。 + +### 4.4 事件系统 🔧 +- 任务:窗口/输入/自定义事件的分发机制(观察者模式)。 +- 资源:**The Cherno "Event System" 系列**(C++,思想通用);《游戏编程模式》观察者/事件队列章节(中文)。 +- 产出:输入和窗口事件能被各系统订阅处理。 + +### 4.5 ECS(实体-组件-系统)架构 ✅/⚠️ +- 任务:理解并使用 ECS 组织游戏对象。**两条路线选一**: + - **(推荐先用现成)** 集成 `hecs` 或 `bevy_ecs`,先学会用。 + - **(进阶自造)** 自己实现一个简易 ECS,彻底搞懂原理(最巩固知识,但难)。 +- 中文资源: + - Bevy 中文教程理解 ECS 思想:https://bevy-cheatbook.github.io 的中文版 https://yiviv.com/bevy-cheatbook/ ;ECS 入门:https://bevyengine-cn.github.io/learn/book_cn/getting-started/ecs/ + - 《游戏编程模式》"组件模式"一章(中文)。 +- 产出:能用 ECS 创建实体、挂组件、写系统遍历。 + +### 4.6 资源管理(Asset Manager)⚠️ +- 任务:统一加载/缓存纹理、音频、字体,用句柄(handle)引用,避免重复加载。 +- 资源:参考 Bevy 的 Assets 设计(看文档/源码);无直接中文教程,需自己设计。 +- 产出:`assets.load::("player.png")` 这样的接口。 + +--- + +## 阶段 5 —— 引擎子系统 + +### 5.1 输入系统 🔧 +- 任务:封装键盘、鼠标、手柄(`gilrs`)状态,提供"本帧是否按下/持续按住"查询和按键映射(Action)。 +- 资源:winit 输入事件文档;手柄用 `gilrs` README。 +- 产出:`input.is_pressed(Key::Space)` 接口。 + +### 5.2 音频系统 ✅/🔧 +- 任务:播放音效与背景音乐、音量控制。用 `kira`(游戏向,推荐)或 `rodio`。 +- 资源:库 README/示例;B站可搜 "Rust 音频 rodio"。 +- 产出:游戏里能放音效和 BGM。 + +### 5.3 2D 物理与碰撞检测 🔧⚠️ +- 任务:AABB 碰撞检测、圆形碰撞、碰撞响应;**两条路线**: + - 入门:自己实现 AABB/圆碰撞(最巩固知识)。 + - 进阶:集成 `rapier2d`(成熟 2D 物理引擎)。 +- 资源: + - 碰撞检测原理 B站搜 "2D 碰撞检测 AABB / 分离轴定理 SAT"。 + - 自写物理引擎经典系列(英文):Randy Gaul 的 2D 物理教程,可配合翻译阅读。 + - `rapier2d` 官方文档与中文社区文章。 +- 产出:角色能站在平台上、被墙挡住。 + +### 5.4 精灵动画系统 ⚠️ +- 任务:序列帧动画、精灵图集(atlas)切割、动画状态机。 +- 资源:无完整中文教程;状态机参考《游戏编程模式》"状态"一章(中文)。 +- 产出:角色能播放走/跳/待机动画。 + +### 5.5 粒子系统 🔧 +- 任务:发射器、粒子生命周期、批量渲染粒子(复用 3.4 批处理)。 +- 资源:The Cherno 有 "Particle System" 一集(C++,思想通用)。 +- 产出:爆炸/火焰等特效。 + +### 5.6 场景管理与序列化 ✅/⚠️ +- 任务:场景(Scene)概念、实体的保存/加载,用 `serde` + `ron`/`json` 序列化。 +- 资源:`serde` 中文文章较多(B站/掘金搜 "Rust serde");场景设计需自己规划。 +- 产出:能把一个关卡存成文件再读回来。 + +### 5.7 游戏内 UI 系统 ✅ +- 任务:按钮、文本、血条等。推荐集成 **egui**(即时模式 GUI,对接 wgpu 成熟)。 +- 资源:egui 官方示例 + B站搜 "Rust egui 教程";在线 demo 直观。 +- 产出:主菜单、暂停界面、HUD。 + +--- + +## 阶段 6 —— 编辑器与工具链 + +> 引擎区别于"一堆库"的关键,就是有可视化工具。这一阶段会让你的引擎"专业感"质变。 + +### 6.1 集成 egui 编辑器面板 ✅ +- 任务:用 egui 做实体列表、属性面板(Inspector)、场景视图。 +- 资源:egui 文档 + The Cherno "Editor" 系列建立设计观(C++)。 +- 产出:能在编辑器里选中实体、改它的位置/属性。 + +### 6.2 资源热重载(Hot Reload)⚠️ +- 任务:监听文件变化(`notify` 库),改了图片/配置自动生效。 +- 资源:`notify` 库 README;无直接中文教程,自行实现。 +- 产出:改图不用重启程序。 + +### 6.3 关卡/地图编辑 ⚠️/🔧 +- 任务:自做简易关卡编辑器,或对接 **Tiled** 地图编辑器(`tiled` 库读取 `.tmx`)。 +- 资源:Tiled 官方(有中文界面);`tiled` rust 库 README。 +- 产出:能用编辑器拼一张关卡地图。 + +--- + +## 阶段 7 —— 用你的引擎做一款游戏 + +### 7.1 选题与游戏设计 ✅ +- 任务:选一个**小而完整**的题材(平台跳跃 / 俯视角射击 / 塔防)。写一页纸设计文档。 +- 资源:游戏设计入门看 B站/indienova 独立游戏社区文章。 +- 产出:一份明确范围的设计文档(切忌做大)。 + +### 7.2 用引擎实现完整游戏循环 ⚠️ +- 任务:把前面所有系统串起来:菜单→关卡→玩法→结算→存档。在做的过程中反过来补全引擎缺失功能。 +- 产出:一个从头玩到尾的完整小游戏。 + +### 7.3 打磨与优化 🔧 +- 任务:性能分析、内存优化、手感调优(juice:震屏、音效反馈)。 +- 资源:The Cherno / GDC 演讲 "Juice it or lose it"(B站有搬运)。 +- 产出:手感舒服、不卡顿。 + +### 7.4 跨平台打包与发布 ✅/🔧 +- 任务:Windows/Linux/Mac 打包,资源随程序分发;可选 WebAssembly 上网页(`wasm`)。 +- 资源:`cargo build --release`;wgpu/winit 对 WASM 支持的文档(《学习 wgpu》中文版有 Web 章节)。 +- 产出:把游戏发给朋友能直接玩,或发布到 itch.io。 + +--- + +## 三、贯穿全程的好习惯 + +- **每个阶段都写小 demo 验证**,别憋大招。能跑起来的丑东西,胜过完美的设想。 +- **多读开源引擎源码**当字典:`Bevy`(最大最现代)、`ggez`(小而经典 2D)、`macroquad`(极简)。遇到不会的就去翻它们怎么写。 +- **理论卡住时回到 GAMES101 和《游戏编程模式》**,它们能解答"为什么"。 +- **The Cherno 系列**用来学架构思想(C++→Rust 转换是很好的锻炼)。 +- 坚持用 Git 提交,给自己留回溯点。 + +--- + +## 四、核心中文资源清单(收藏夹) + +| 用途 | 资源 | 链接 | +|------|------|------| +| Rust 官方书中文 | Rust 程序设计语言 简体中文版 | https://kaisery.github.io/trpl-zh-cn/ | +| Rust 实战教程 | Rust 语言圣经 | https://course.rs/ | +| Rust 在线练习 | Rust 语言圣经练习 | https://practice.course.rs/ | +| 图形学/数学 | GAMES101(闫令琪,B站) | https://www.bilibili.com/video/BV1X7411F744/ | +| 引擎架构思想 | The Cherno 游戏引擎系列(B站中字) | https://www.bilibili.com/video/BV14v411h7Hu/ | +| 设计模式 | 游戏编程模式 中文版 | https://gpp.tkchu.me/ | +| GPU/渲染 | 学习 wgpu 中文版 | https://jinleili.github.io/learn-wgpu-zh/ | +| 2D 入门库 | Macroquad(书+示例,英文) | https://mq.agical.se/ | +| ECS 思想 | Bevy Cheatbook 中文 | https://yiviv.com/bevy-cheatbook/ | +| Git | 廖雪峰 Git 教程 | https://liaoxuefeng.com/books/git/introduction/index.html | + +> 详细逐条任务见同目录下的 **引擎学习开发计划.csv**(可导入表格软件当进度看板用)。 + +--- + +*祝你造引擎愉快。记住:第一个引擎一定会很烂,这很正常——它的价值不是"好用",而是让你真正理解了引擎是怎么回事。* diff --git a/引擎学习开发计划.xlsx b/引擎学习开发计划.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..b0cb689dc1bc092ac524b6aac8155fb9b813e4ac GIT binary patch literal 15159 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