Compare commits

...

10 Commits

Author SHA1 Message Date
mai 5beb3d2940 chore: init repo, add .gitignore and tech stack analysis 2026-07-04 08:07:08 +08:00
js13kgames-internal[bot] 9735c0fdcf Initialize .website directory for q1k3 by @phoboslab
Reviewed by: @alcore
2026-05-28 15:57:01 +00:00
Dominic Szablewski 8ee934856c Merge pull request #14 from irackson/fix_build
fix build error ": extra characters at the end of d command" with sem…
2024-08-30 09:40:32 +02:00
irackson c419b7d126 fix build error ": extra characters at the end of d command" with semicolon 2024-08-29 19:02:27 -04:00
Dominic Szablewski d1759ae6ec Add link to Making Of blog post 2021-09-27 17:46:35 +02:00
Dominic Szablewski cfcff07c58 Make sfx_pickup a local var instead of an uncompressible global :/ 2021-09-27 17:45:33 +02:00
Dominic Szablewski 3112515834 Remove obsolete nav map array 2021-09-27 17:44:51 +02:00
Dominic Szablewski 4a24fcc2fa Remove obsolete setTimeout wrapper 2021-09-14 22:21:37 +02:00
Dominic Szablewski feff06d83b Fix nail ammo box stuck in wall 2021-09-14 12:13:56 +02:00
Dominic Szablewski a522cb597d Implement mouse invert checkbox 2021-09-14 11:43:44 +02:00
13 changed files with 378 additions and 36 deletions
+9 -7
View File
@@ -1,8 +1,10 @@
node_modules # Build artifacts
package.json build/*
package-lock.json !build/dummy.txt
autosave/
build/ # Compiled map packer
pack_map pack_map
\#*
*~ # OS
.DS_Store
Thumbs.db
+27
View File
@@ -0,0 +1,27 @@
---
post: https://phoboslab.org/log/2021/09/q1k3-making-of
# See github.com/js13kGames/hello-world for supported frontmatter
---
## Controls
- **Movement**: <kbd>W</kbd> <kbd>A</kbd> <kbd>S</kbd> <kbd>D</kbd> or <kbd>↑</kbd> <kbd>←</kbd> <kbd>↓</kbd> <kbd>→</kbd>
- **Attack**: <kbd><svg><use href=#i-mouse></svg> LMB</kbd>
- **Jump**: <kbd>Space</kbd> or <kbd><svg><use href=#i-mouse></svg> RMB</kbd>
- **Switch weapon**: <kbd>Q</kbd>/<kbd>E</kbd> or <kbd><svg><use href=#i-mouse></svg> Mousewheel</kbd>
If you accidentally scroll the page down when you want to change weapons, either resize your browser window so there's
no scrollbar, or go to fullscreen using the button below the game!
## Features
- 2 Levels
- 5 Types of enemies
- 3 Weapons
- 30 different textures
- Music from Andy Lösch - no-fate.net
- Dynamic lighting
- Doors(!)
- Somewhat robust collision detection, even for fast moving objects
- Enemy AI with line of sight checks. No pathfinding, but still does a reasonable job following the player
- "Spatial" Audio (stereo separation and falloff by distance)
Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
View File
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

+2
View File
@@ -4,6 +4,8 @@ My entry for the 2021 [js13k](https://js13kgames.com/) competition.
Play here: https://phoboslab.org/q1k3/ Play here: https://phoboslab.org/q1k3/
Making Of: https://phoboslab.org/log/2021/09/q1k3-making-of
### Controls ### Controls
- Movement: WASD or Arrow Keys - Movement: WASD or Arrow Keys
- Attack: Left Mouse Button - Attack: Left Mouse Button
+1 -1
View File
@@ -2415,7 +2415,7 @@
// entity 60 // entity 60
{ {
"classname" "pickup_nails" "classname" "pickup_nails"
"origin" "2660 1188 252" "origin" "2660 1204 252"
} }
// entity 61 // entity 61
{ {
+2 -2
View File
@@ -109,10 +109,10 @@ npx uglify-js build/game.packed.js \
--compress --mangle toplevel --mangle-props regex=/^_/ \ --compress --mangle toplevel --mangle-props regex=/^_/ \
-o build/game.min.js -o build/game.min.js
npx roadroller -Zab14 -Zlr1113 -Zmd16 -Zpr14 -S0,1,2,3,6,7,13,21,25,50,235,333 build/game.min.js -o build/game.roadrolled.js npx roadroller -Zab14 -Zlr930 -Zmd19 -Zpr14 -S0,1,2,3,7,13,14,19,58,97,305,422 build/game.min.js -o build/game.roadrolled.js
# Embed source into HTML # Embed source into HTML
sed -e '/GAME_SOURCE/{r build/game.roadrolled.js' -e 'd}' source/html_template.html > build/index.html sed -e '/GAME_SOURCE/{r build/game.roadrolled.js' -e 'd;}' source/html_template.html > build/index.html
# Build ZIP # Build ZIP
rm -f -- build/game.zip rm -f -- build/game.zip
+316
View File
@@ -0,0 +1,316 @@
# Q1K3 技术栈分析文档
> 作者:phoboslabDominic Szablewski
> 竞赛:2021 年 JS13K13KB 游戏编程大赛)
> 仓库: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 JavaScriptES5/ES6 | 所有游戏逻辑 |
| **音频** | Sonant-X(定制版) | 程序化生成音效 + 音乐 |
| **纹理** | Tiny Texture TumblerTTT | 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 TumblerTTT
由 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.03D | Canvas 2DTile-based 2D |
| 纹理 | 程序化生成(TTT) | 程序化生成(Canvas |
| 地图 | TrenchBroom → 自定义二进制 | 程序化迷宫生成 |
| 模型 | OBJ → RMF 二进制格式 | 无 3D 模型 |
| 碰撞 | 128³ 位图 | Tile cell 碰撞 |
| 音频 | 程序化合成(Sonant-X | 待定 |
| 压缩 | UglifyJS + Roadroller + advzip | 不适用 |
---
*文档版本:2024-07-04*
*分析对象:q1k3 commit at clone*
+1 -1
View File
@@ -17,7 +17,7 @@ document.body.innerHTML +=
'<div id="h"></div><div id="a"></div>'+ '<div id="h"></div><div id="a"></div>'+
'<div id="msg"></div>'+ '<div id="msg"></div>'+
'</div>'+ '</div>'+
'<p>MOUSE SPEED: <input id="m" type="range" value=10 min=1 max=50></p>'+ '<p>MOUSE SPEED: <input id="m" type="range" value=10 min=1 max=50> INVERT: <input type="checkbox" id="mi"></p>'+
'<p><input id="f" type="button" value="FULLSCREEN"></p>'+ '<p><input id="f" type="button" value="FULLSCREEN"></p>'+
'<p>'+ '<p>'+
'code: <a href="https://phoboslab.org">phoboslab.org</a> / music: <a href="http://no-fate.net">no-fate.net</a>'+ 'code: <a href="https://phoboslab.org">phoboslab.org</a> / music: <a href="http://no-fate.net">no-fate.net</a>'+
+1 -1
View File
@@ -24,7 +24,7 @@ class entity_player_t extends entity_t {
_update() { _update() {
// Mouse look // Mouse look
this._pitch = clamp(this._pitch + mouse_y * m.value * 0.00015, -1.5, 1.5); this._pitch = clamp(this._pitch + mouse_y * m.value * (mi.checked ? -0.00015 : 0.00015), -1.5, 1.5);
this._yaw = (this._yaw + mouse_x * m.value * 0.00015) % (Math.PI*2); this._yaw = (this._yaw + mouse_x * m.value * 0.00015) % (Math.PI*2);
// Acceleration in movement direction // Acceleration in movement direction
+19 -23
View File
@@ -44,6 +44,7 @@ sfx_enemy_hound_attack,
sfx_no_ammo, sfx_no_ammo,
sfx_hurt, sfx_hurt,
sfx_pickup,
sfx_plasma_shoot, sfx_plasma_shoot,
@@ -125,39 +126,34 @@ game_load = async () => {
g.onclick = () => { g.onclick = () => {
g.onclick = () => c.requestPointerLock(); g.onclick = () => c.requestPointerLock();
g.onclick(); g.onclick();
// title_show_message('...');
audio_init(); audio_init();
setTimeout(()=> { // Generate sounds
// Generate sounds sfx_enemy_hit = audio_create_sound(135, [8,0,0,1,148,1,3,5,0,0,139,1,0,2653,0,2193,255,2,639,119,2,23,0,0,0,0,0,0,0]);
sfx_enemy_hit = audio_create_sound(135, [8,0,0,1,148,1,3,5,0,0,139,1,0,2653,0,2193,255,2,639,119,2,23,0,0,0,0,0,0,0]); sfx_enemy_gib = audio_create_sound(140, [7,0,0,1,148,1,7,5,0,1,139,1,0,4611,789,15986,195,2,849,119,3,60,0,0,0,1,10,176,1]);
sfx_enemy_gib = audio_create_sound(140, [7,0,0,1,148,1,7,5,0,1,139,1,0,4611,789,15986,195,2,849,119,3,60,0,0,0,1,10,176,1]); sfx_enemy_hound_attack = audio_create_sound(132, [8,0,0,1,192,1,8,0,0,1,120,1,0,5614,0,20400,192,1,329,252,1,55,0,0,1,1,8,192,3]);
sfx_enemy_hound_attack = audio_create_sound(132, [8,0,0,1,192,1,8,0,0,1,120,1,0,5614,0,20400,192,1,329,252,1,55,0,0,1,1,8,192,3]);
sfx_no_ammo = audio_create_sound(120, [8,0,0,0,96,1,8,0,0,0,0,0,255,0,0,1075,232,1,2132,255,0,0,0,0,0,0,0,0,0]); sfx_no_ammo = audio_create_sound(120, [8,0,0,0,96,1,8,0,0,0,0,0,255,0,0,1075,232,1,2132,255,0,0,0,0,0,0,0,0,0]);
sfx_hurt = audio_create_sound(135, [7,3,140,1,232,3,8,0,9,1,139,3,0,4611,1403,34215,256,4,1316,255,0,0,0,1,0,1,7,255,0]); sfx_hurt = audio_create_sound(135, [7,3,140,1,232,3,8,0,9,1,139,3,0,4611,1403,34215,256,4,1316,255,0,0,0,1,0,1,7,255,0]);
sfx_pickup = audio_create_sound(140, [7,0,0,1,187,3,8,0,0,1,204,3,0,4298,927,1403,255,0,0,0,3,35,0,0,0,0,0,0,0]); sfx_pickup = audio_create_sound(140, [7,0,0,1,187,3,8,0,0,1,204,3,0,4298,927,1403,255,0,0,0,3,35,0,0,0,0,0,0,0]);
sfx_plasma_shoot = audio_create_sound(135, [8,0,0,1,147,1,6,0,0,1,159,1,0,197,1234,21759,232,2,2902,255,2,53,0,0,0,0,0,0,0]); sfx_plasma_shoot = audio_create_sound(135, [8,0,0,1,147,1,6,0,0,1,159,1,0,197,1234,21759,232,2,2902,255,2,53,0,0,0,0,0,0,0]);
sfx_shotgun_shoot = audio_create_sound(135, [7,3,0,1,255,1,6,0,0,1,255,1,112,548,1979,11601,255,2,2902,176,2,77,0,0,1,0,10,255,1]); sfx_shotgun_shoot = audio_create_sound(135, [7,3,0,1,255,1,6,0,0,1,255,1,112,548,1979,11601,255,2,2902,176,2,77,0,0,1,0,10,255,1]);
sfx_shotgun_reload = audio_create_sound(125, [9,0,0,1,131,1,0,0,0,0,0,3,255,137,22,1776,255,2,4498,176,2,36,2,84,0,0,3,96,0]); sfx_shotgun_reload = audio_create_sound(125, [9,0,0,1,131,1,0,0,0,0,0,3,255,137,22,1776,255,2,4498,176,2,36,2,84,0,0,3,96,0]);
sfx_nailgun_shoot = audio_create_sound(130, [7,0,0,1,132,1,8,4,0,1,132,2,162,0,0,8339,232,2,2844,195,2,40,0,0,0,0,0,0,0]); sfx_nailgun_shoot = audio_create_sound(130, [7,0,0,1,132,1,8,4,0,1,132,2,162,0,0,8339,232,2,2844,195,2,40,0,0,0,0,0,0,0]);
sfx_nailgun_hit = audio_create_sound(135, [8,0,0,1,148,1,0,0,0,0,0,1,255,0,0,2193,128,2,6982,119,2,23,0,0,0,0,0,0,0]); sfx_nailgun_hit = audio_create_sound(135, [8,0,0,1,148,1,0,0,0,0,0,1,255,0,0,2193,128,2,6982,119,2,23,0,0,0,0,0,0,0]);
sfx_grenade_shoot = audio_create_sound(127, [8,0,0,1,171,1,9,3,0,1,84,3,96,2653,0,13163,159,2,3206,255,2,64,0,0,0,1,9,226,0]); sfx_grenade_shoot = audio_create_sound(127, [8,0,0,1,171,1,9,3,0,1,84,3,96,2653,0,13163,159,2,3206,255,2,64,0,0,0,1,9,226,0]);
sfx_grenade_bounce = audio_create_sound(168, [7,0,124,0,128,0,8,5,127,0,128,0,125,88,0,2193,125,1,1238,240,1,91,3,47,0,0,0,0,0]); sfx_grenade_bounce = audio_create_sound(168, [7,0,124,0,128,0,8,5,127,0,128,0,125,88,0,2193,125,1,1238,240,1,91,3,47,0,0,0,0,0]);
sfx_grenade_explode = audio_create_sound(135, [8,0,0,1,195,1,6,0,0,1,127,1,255,197,1234,21759,232,2,1052,255,4,73,3,25,1,0,10,227,1]); sfx_grenade_explode = audio_create_sound(135, [8,0,0,1,195,1,6,0,0,1,127,1,255,197,1234,21759,232,2,1052,255,4,73,3,25,1,0,10,227,1]);
audio_play(audio_create_song(...music_data), 1, 1); audio_play(audio_create_song(...music_data), 1, 1);
game_init(0); game_init(0);
run_frame = game_run; run_frame = game_run;
},100); // Not sure why such a long delay is needed. Ideally we would
// want to use requestAnimationFrame, but the previous DOM
// update (...) never makes it to the screen then ;/
}; };
}, },
-1
View File
@@ -37,7 +37,6 @@ map_load_container = async (path) => {
for (let i = 0; i < data.length;) { for (let i = 0; i < data.length;) {
let blocks_size = data[i++] | (data[i++] << 8), let blocks_size = data[i++] | (data[i++] << 8),
cm = new Uint8Array(map_size * map_size * map_size >> 3), // collision map cm = new Uint8Array(map_size * map_size * map_size >> 3), // collision map
nm = new Uint8Array(map_size * map_size * map_size >> 3), // nav map
b = data.subarray(i, i += blocks_size), b = data.subarray(i, i += blocks_size),
r = [], r = [],
t; t;