From 5beb3d294034247298a290d708267b6654cb61b1 Mon Sep 17 00:00:00 2001 From: Mai Date: Sat, 4 Jul 2026 08:07:08 +0800 Subject: [PATCH] chore: init repo, add .gitignore and tech stack analysis --- .gitignore | 16 +- knowledge/tech-stack-analysis.md | 316 +++++++++++++++++++++++++++++++ 2 files changed, 325 insertions(+), 7 deletions(-) create mode 100644 knowledge/tech-stack-analysis.md diff --git a/.gitignore b/.gitignore index 3e65283..c598864 100755 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,10 @@ -node_modules -package.json -package-lock.json -autosave/ -build/ +# Build artifacts +build/* +!build/dummy.txt + +# Compiled map packer pack_map -\#* -*~ + +# OS +.DS_Store +Thumbs.db diff --git a/knowledge/tech-stack-analysis.md b/knowledge/tech-stack-analysis.md new file mode 100644 index 0000000..2614da9 --- /dev/null +++ b/knowledge/tech-stack-analysis.md @@ -0,0 +1,316 @@ +# Q1K3 技术栈分析文档 + +> 作者:phoboslab(Dominic Szablewski) +> 竞赛:2021 年 JS13K(13KB 游戏编程大赛) +> 仓库:https://github.com/js13kGames/q1k3 + +--- + +## 一、项目概述 + +Q1K3 是 JS13K 2021 的参赛作品,一款致敬《Quake》的第一人称射击游戏。包含完整的 3D 渲染引擎、敌人 AI 系统、武器系统、动态光照、音效音乐,**全部压缩在 13KB 以内**。项目由多人格大神 [phoboslab](https://phoboslab.org) 独立完成,他也是 [IMP](https://phoboslab.org/log/2023/08/imp)(另一款 JS13K 3D 游戏)的作者。 + +--- + +## 二、核心技术栈总览 + +| 层面 | 技术 | 用途 | +|------|------|------| +| **渲染** | WebGL 1.0(原生) | 3D 硬件加速渲染 | +| **语言** | Vanilla JavaScript(ES5/ES6) | 所有游戏逻辑 | +| **音频** | Sonant-X(定制版) | 程序化生成音效 + 音乐 | +| **纹理** | Tiny Texture Tumbler(TTT) | Canvas 程序化生成纹理 | +| **地图** | TrenchBroom + 自定义 C 编译器 | Quake 风格地图编辑与打包 | +| **模型** | Wavefront OBJ + 自定义 PHP 打包器 | 3D 模型处理 | +| **压缩** | UglifyJS3 + Roadroller + advzip | JS 代码 + ZIP 极致压缩 | +| **构建** | Bash + PHP + GCC | 全自动构建管线 | + +--- + +## 三、渲染引擎(renderer.js) + +### 3.1 核心架构 + +纯 WebGL 1.0 实现,**无任何第三方库**(Three.js、PixiJS 等)。作者手写了 vertex 和 fragment shader,实现了一套精简的光栅化管线。 + +### 3.2 渲染策略 + +- **延迟提交渲染**:所有 draw call 先收集到 `r_draw_calls` 数组中,`r_end_frame()` 时统一提交。这样灯光 buffer 只需上传一次,所有几何体共享。 +- **动态光照**:支持最多 32 个动态光源(`R_MAX_LIGHT_V3 = 64`,每个光源占 2 个 vec3:位置 + 颜色/强度)。在 fragment shader 中逐像素计算光照,光照衰减按距离平方反比(`1/distance²`)。 +- **视口投影**:使用固定 90° FOV,投影矩阵简化为一元矩阵(`atan(90/2) = 1`,矩阵 `[0]` 和 `[5]` 恰为 1)。 +- **颜色量化**:最终输出颜色压缩到 16 级(`floor(val * 16 + 0.5) / 16`),模拟 Quake 的低色深视觉风格。Gamma 校正通过 `pow(vl, 0.75)` 实现。 + +### 3.3 Shader 代码 + +**Vertex Shader**(内嵌在 JS 字符串中): +- 支持两个顶点缓冲区混合(`mix(p, p2, f)`),实现模型动画的关键帧插值。 +- 模型旋转矩阵使用 yaw/pitch 分离(`mat4 ry(r)` / `mat4 rz(r)`)。 +- 投影矩阵、相机旋转、模型位移全部在 VS 中完成,一行 `gl_Position` 搞定。 + +**Fragment Shader**: +- 从纹理采样获取基础颜色。 +- 循环计算所有光源:法线方向点积 + 距离衰减 + 颜色强度。 +- gamma 校正 + 颜色量化。 + +### 3.4 几何体构建 + +代码中直接构建 3D 几何体: +- `r_push_quad()`:用 4 个顶点 + UV坐标 + 法线构建三角形对(6 个顶点 = 2 个三角形)。 +- `r_push_block()`:构建完整 6 面体(12 个三角形),按面设置纹理映射。 +- 顶点格式紧凑:每顶点 8 个 float = `[x, y, z, u, v, nx, ny, nz]`(位置 3 + UV 2 + 法线 3)。 +- 所有顶点存入一个 `Float32Array`(固定 64K 顶点上限),一次性提交到 GPU。 + +### 3.5 WebGL 编码技巧 + +通过 `pack_js.php` 将所有 `gl.xxx` 方法名和枚举常量替换为最短缩写(如 `gl.buD` 代替 `gl.bufferData`,`34962` 代替 `gl.ARRAY_BUFFER`)。这是 13KB 极限压缩下极致的尺寸优化手段。 + +--- + +## 四、音频系统(audio.js) + +### 4.1 来源 + +基于 [Sonant-X](https://github.com/nicolas-van/sonant-x)(zlib 许可证)的深度定制版。作者在 2018、2019、2021 年三次重写,改为使用 Float32 buffer 直接合成音频,并改用查找表代替函数调用实现振荡器(~10x 性能提升)。 + +### 4.2 技术特点 + +- **纯程序化合成**:无任何音频文件,全部在运行时用数学函数生成波形。 +- **自定义参数编码**:声音和歌曲定义从 JS 对象改为数字数组,大幅压缩代码体积。 +- **3D 空间音频**:支持立体声分离和距离衰减(基于实体与相机的相对位置)。 +- **流式播放**:音乐通过 `audio_create_song()` 创建,与音效共用同一个音频上下文。 + +--- + +## 五、纹理系统(textures.js + ttt.js) + +### 5.1 Tiny Texture Tumbler(TTT) + +由 phoboslab 开发的**程序化纹理生成库**。所有纹理没有 PNG/JPG 资产文件,全部通过 Canvas 2D API 在运行时生成。 + +### 5.2 编码方式 + +纹理定义编码为紧凑数字数组,例如: + +``` +[64,64,0,2,3,1.4,2,17176,1.3] +``` + +代表:宽 64x64,分形噪声种子、颜色混合模式、缩放因子等参数。 + +### 5.3 纹理种类 + +30 种不同纹理,涵盖: +- 砖墙纹理 +- 金属面板 +- 血迹/污渍 +- 地板石纹 +- 特殊图案(logo、符号等) +- 粒子效果用纹理 + +--- + +## 六、地图系统(map.js + pack_map.c) + +### 6.1 设计工具 + +地图使用 [TrenchBroom](https://trenchbroom.github.io/)(Quake 引擎地图编辑器)编辑,导出为 `.map` 格式(Quake 标准 Brush 格式)。 + +### 6.2 编译管线 + +1. **原始格式**:`.map` 文件包含用平面方程定义的 Brush 体(每 6 个平面 = 1 个立方体 Brush)。 +2. **C 编译器**:`pack_map.c` 用标准 C99 编译,将 `.map` 文件解析为紧凑二进制 `.plb` 格式。 +3. **合并**:所有地图数据拼接到一个二进制文件 `build/l`。 + +### 6.3 运行时格式 + +```c +struct { + u16 blocks_size; // 区块数据大小 + block_t blocks[]; // 区块数组(纹理切换指令穿插其中) + u16 num_entities; // 实体数量 + entity_t entities[]; // 实体数据 +} map_data; +``` + +每个 `block_t`:`u8 x, y, z, sx, sy, sz`(位置 + 尺寸,共 6 字节)。 + +### 6.4 碰撞检测 + +使用 **128³ Bitmap** 作为碰撞贴图: +- `u8 cm[128*128*128 >> 3]` = 256KB 的位图内存。 +- 每 bit 表示一个 voxel 是否被占据。 +- 支持快速遍历射线/球体碰撞检测。 + +--- + +## 七、模型系统(model.js + pack_model.php) + +### 7.1 设计流程 + +1. 在 Blender 或其他 3D 软件中建 OBJ 模型。 +2. `pack_model.php` 将 OBJ 文件转换为自定义紧凑二进制格式 `.rmf`(Retarded Model Format)。 +3. 运行时 `model_load_container` 用 `Uint8Array` 解析二进制数据。 + +### 7.2 RMF 格式 + +```c +struct { + u8 num_frames; // 动画帧数 + u8 num_verts; // 每帧顶点数 + u8 num_indices; // 索引数 + vert_t verts[num_frames * num_verts]; // 顶点数据 + index_t indices[num_indices]; // 三角索引 +} rmf_data; +``` + +### 7.3 关键实现细节 + +- **顶点只含位置**(3 bytes: x, y, z),UV 和法线通过 OBJ 的纹理坐标/法线计算。 +- **动画通过帧间线性插值**:`r_draw()` 传入两个帧的 offset 和混合因子 `mix`,shader 中 `mix(p, p2, f)` 完成插值。 +- **共享几何体**:多种模型共用相同几何体模板,仅缩放和纹理不同。 +- **模型容器**:所有模型拼进一个文件 `build/m`,按顺序读取。 + +--- + +## 八、实体系统(entity.js + 各类派生) + +### 8.1 架构 + +基于类的继承体系: + +``` +entity_t(基类) +├── entity_player_t ← 玩家 +├── entity_enemy_t(基类) +│ ├── entity_enemy_grunt_t +│ ├── entity_enemy_enforcer_t +│ ├── entity_enemy_ogre_t +│ ├── entity_enemy_zombie_t +│ └── entity_enemy_hound_t +├── entity_projectile_*(弹丸) +├── entity_pickup_*(拾取物) +├── entity_door_t +├── entity_barrel_t +├── entity_light_t +├── entity_torch_t +├── entity_particle_t(粒子) +└── entity_trigger_level_t(关卡触发) +``` + +### 8.2 物理系统 + +内置在 `entity_t._update_physics()` 中: +- 重力加速度(`a.y = -1200 * gravity`) +- 速度积分与摩擦 +- AABB 碰撞检测(基于碰撞贴图) +- 台阶高度处理(`_step_height`) +- 弹跳系数(`_bounciness`) + +### 8.3 敌人 AI + +有限状态机实现,每种敌人共用 `entity_enemy_t` 基类的状态机: +- IDLE → PATROL → FOLLOW → ATTACK → EVADE +- 视线检测(Raycasting 检查障碍物) +- 无寻路算法,但能通过视线检测合理跟随玩家 +- 5 种敌人类型各有不同参数(速度、攻击距离、攻击方式、血量等) + +--- + +## 九、武器系统(weapons.js) + +基于类的武器系统,共 3 种武器: + +| 武器 | 弹药 | 特点 | +|------|------|------| +| 霰弹枪 | ∞(无限) | 基础武器,发射多发弹丸 | +| 钉枪 | 有消耗 | 高速连续射击 | +| 榴弹发射器 | 有消耗 | 弹道弧线 + 爆炸范围伤害 | + +`weapon_t` 基类处理发射逻辑,子类重写 `_init()` 设置不同参数。 + +--- + +## 十、输入系统(input.js) + +- **键盘**:使用 `ev.code`(WASD / 方向键),键盘布局无关。 +- **鼠标**:Pointer Lock API,通过 `movementX` / `movementY` 实现视角控制。 +- **键位映射**:通过字符串第 N 个字符的映射技巧减少代码体积(`W` 对应 KeyW 的 `[1]`,`p` 对应 ArrowUp 的 `[6]`)。 + +--- + +## 十一、构建管线(build.sh) + +全自动构建流程,从源文件到最终 13KB ZIP: + +``` +1. gcc → pack_map.c → 编译地图 +2. php → pack_model.php → 打包模型 +3. cat → 所有 JS 源拼接为一个文件 +4. php → pack_js.php → WebGL 调用压缩 + DEBUG 去除 +5. npx uglify-js → 代码混淆压缩(toplevel + mangle-props) +6. npx roadroller → 进一步压缩(基于概率模型的压缩) +7. sed → 嵌入 HTML 模板 +8. zip → 创建最终 ZIP 包 +9. advzip → 再压缩优化 +``` + +### 关键压缩步骤 + +- **pack_js.php**:将 `gl.ARRAY_BUFFER` 替换为 `34962`,`gl.bufferData` 替换为 `gl.buD`,节省大量字符。 +- **Roadroller**:JS13K 专用的概率模型压缩器,比常规 gzip/zip 压缩率更高。 +- **advzip**:AdvanceCOMP 工具,在 ZIP 级别进一步优化。 + +--- + +## 十二、代码风格与体积优化技巧 + +### 12.1 命名约定 + +- 所有变量名极短(1-3 字符),如 `r_num_verts`、`f`、`c` +- 私有方法用 `_` 前缀标记(mangle-props 时可安全缩短) +- 全局变量用 `let ` 声明,不加 `var`(ES6 更短) + +### 12.2 编码技巧 + +- **矩阵和向量函数**用一行写在高阶函数链中(`vec3_normalize(vec3_cross(...))`) +- **条件编译**:`/*DEBUG[*/ 'build/' + /*]*/` — 注释去掉后自动切换路径 +- **WebGL 方法名自动缩短**:运行时通过 `name.match(/(^..|[A-Z]|\d.|v$)/g).join('')` 提取缩写 +- **纹理定义数字编码**:把 30 个纹理的参数编码为紧凑数组,无需单独命名 + +### 12.3 内存分配策略 + +- 固定大小预分配:`Float32Array(R_MAX_VERTS * 8)`(64K 顶点上限) +- 无垃圾回收敏感操作:所有内存一次性分配,运行时复用 buffer +- 碰撞贴图:128³ 位图(262,144 bits = 32KB),空间换时间 + +--- + +## 十三、值得注意的架构决策 + +| 决策 | 原因 | +|------|------| +| **纯原生 WebGL,不用 Three.js** | 13KB 限制下无法容忍任何库的开销 | +| **程序化纹理 + 音效** | 没有空间放外部资产文件 | +| **碰撞用 128³ 位图(非 BVH/八叉树)** | 地图规模小(128³),位图查找 O(1) | +| **延迟绘制提交** | 减少 uniform 上传次数,优化光照计算 | +| **模型动画用顶点插值而非骨骼** | 省去骨骼矩阵计算,shader 中一行 mix 搞定 | +| **敌人在基础类中共享 AI 状态机** | 5 种敌人共用一套 AI 逻辑,参数差异化 | + +--- + +## 十四、与 fox-maze 的对比(参考) + +| 维度 | Q1K3 | fox-maze | +|------|------|----------| +| 渲染 | WebGL 1.0(3D) | Canvas 2D(Tile-based 2D) | +| 纹理 | 程序化生成(TTT) | 程序化生成(Canvas) | +| 地图 | TrenchBroom → 自定义二进制 | 程序化迷宫生成 | +| 模型 | OBJ → RMF 二进制格式 | 无 3D 模型 | +| 碰撞 | 128³ 位图 | Tile cell 碰撞 | +| 音频 | 程序化合成(Sonant-X) | 待定 | +| 压缩 | UglifyJS + Roadroller + advzip | 不适用 | + +--- + +*文档版本:2024-07-04* +*分析对象:q1k3 commit at clone*