社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  chatgpt

二维码也能跑游戏?开发者用3KB极限挑战复刻DOOM,曾一度崩溃后...ChatGPT救了场

程序员专栏 • 1 月前 • 78 次点击  
自 1993 年问世以来,《DOOM》(毁灭战士)早已超越了一款经典射击游戏的范畴,而是成为“极限移植”的代名词。从烤面包机、MacBook Touch Bar、智能冰箱,到我们此前报道过的 PDF 文档、TypeScript 类型系统,它几乎无处不在——“It Runs Doom”(万物皆可跑 DOOM)也因此成为一代程序员的狂欢梗。

最近,这一狂欢再度升级:一位开发者竟然将一款可玩的《DOOM》风格游戏完整封装进一个二维码中,扫码即可在浏览器中直接启动,无需下载,无需安装。

整个开发仅耗时一周,旨在突破了二维码在存储和压缩方面的极限,展示了将轻量级 Web 应用完全托管于二维码内的全新可能。

而更令人惊讶的是——整个游戏大小还不到 3KB。

目前,此项目已在 GitHub 上开源:https://github.com/Kuberwastaken/backdooms。二维码如下:

图片

值得注意的是,iPhone、Android 等智能手机无法直接扫描,其只适配于浏览器端,需要借助在线二维码扫描器(https://scanqr.org/),扫描之后的游戏界面如下:

图片


图片

从荒诞构想到技术落地

这个项目由开发者 Kuber Mehta 发起,挑战是在不超过 3KB 的数据限制下,尽可能还原《DOOM》的游戏体验。

为什么是 3KB?

Kuber Mehta 解释说,这是二维码所能容纳的最大文本或二进制数据量。为了对比,原版《DOOM》中“机枪”(chaingun)贴图就需要约 1.2KB,而这次项目的总容量预算,仅为其两倍多一点。时下,Kuber Mehta 要在这个限制内完成整款游戏的逻辑与视觉呈现,可谓是极限挑战。

之所以会萌生这样的想法,是由于几年前 Kuber Mehta 偶然刷到一位 YouTube 博主 matttkc 发布的一条 YouTube 视频,其曾提出一个疯狂问题:“你能把一整个游戏塞进一个二维码里吗?”

这个设想从此深埋 Kuber Mehta 心底,但他一直没付诸实践——“因为我觉得自己可能不够聪明。”Kuber Mehta 自嘲道。

直到不久前,这个想法再次浮现,他决定不再犹豫,动手一试。可真正开始后,他很快发现:网上几乎找不到现成的 HTML 版本的 DOOM 实现可供复用。

资源有限的情况下,他退而求其次:“制作一个可玩的、受《Doom》启发的游戏,其大小不超过三段纯文本。”

正如他自己总结的:

“真的太难了,因为我的条件只有这些——
没有游戏引擎,只能写原生 HTML/JS;
没有素材资源,所有图形都得代码生成;
不能用任何库,因为每个字节都弥足珍贵。
但也正因如此,我学到了太多。”

所以它其实并不是《Doom》本尊——但 Mehta 的游戏确实很有《Doom》的味道。除了从 1993 年原版射击游戏中汲取灵感外,他还参考了迷因式的“迷离空间”恐怖故事 The Backrooms,于是他的项目被命名为“the backdooms”。

「这是不是一个愚蠢的决定?也许是。但结果酷不酷?毫无疑问——酷炸了!」,Kuber Mehta 说道。

末日审判在密室里的 Reddit 图片


图片

极限代码压缩:每一个字节都算数

游戏是用 HTML 编写的,Mehta 必须让每个字符都物尽其用。

现实来看,要在二维码的 2,953 字节容量(QR Code Version 40)内嵌入一款游戏,他需要用到所谓的“压缩技术”,更准确说,是极限压缩

Kuber Mehta 以一段游戏代码代码为例,展示了他具体的做法:
html><html><head><meta charset="utf-8"><style>body{margin:0;overflow:hidden;background:#000;cursor:crosshair}canvas{width:100vw;height:100vh}style>head><body><canvas id=c>canvas><script


    
>M=Math,c=document.getElementById("c"),c.width=320,c.height=240,h=c.getContext("2d"),x=4,y=4,a=0,H=100,am=25,rc=0,fl=0;f=(i,j)=>(Math.abs(i-4)<4&&Math.abs(j-4)<4)?"0":((((i+1000)%7)==3||((j+1000)%7)==3)?"0":(Math.random()<.05?"1":"0"));e=[{x:5,y:4,h:100},{x:4,y:5,h:100}],k={};onkeydown=e=>k[e.key]=1;onkeyup=e=>k[e.key]=0;onclick=_=>{if(am){am--;fl=2;rc=.2;e.forEach(o=>{d=M.hypot(o.x-x,o.y-y),r=M.atan2(o.y-y,o.x-x);if(d<5&&Math.abs(r-a)<.3)o.h-=50})}};R=_=>{rc=Math.max(0,rc-.02);fl=Math.max(0,fl-1);e=e.filter(o=>o.h>0);h.fillStyle="#000";h.fillRect(0,0,320,240);k.ArrowLeft&&(a-=.1);k.ArrowRight&&(a+=.1);m=.1;
如果你没看到 ,你可能都不敢相信这居然是 HTML。
再看一个例子,原始代码:
function drawWall(distance) {    const height = 240 / distance;    context.fillRect(x, 120 - height/21, height);  }
压缩后:
h.fillRect(i,120-240/d/2,1,240/d)
其中变量名都被压缩为单个字符,注释也完全消失。“从最终的代码看上去,有点像《Doom》里的恶魔挨了爆头一样令人震撼。”

图片
地图生成机制
在游戏设计方面,起初,Kuber Mehta 打算采用 16x16 或 8x8 的固定小地图设计,这种尺寸在一个超小型游戏中本就算得上“合理”。但他并不满足于此,更希望玩家能获得“真正可玩”的体验。
因此,他决定引入无限地图生成,并辅以“种子(Seed)”机制。
这一做法与《Minecraft》中世界生成的思路类似:一个看似随机的字符串,就能决定整个游戏世界的布局。
随机 Minecraft 种子点击诱饵视频

Kuber Mehta 利用了这一原理,让玩家理论上可以通过特定种子值生成心仪的地图,并在代码中硬编码,从而每次运行都得到一致的世界:

SEED = Math.random() * 100;

在图形表现方面,Kuber Mehta 没有采用 WebGL 或 Canvas 的高级图形接口,而是回归原点,用了《DOOM》早期版本(1992 年)使用的光线投射(Raycasting)技术。这种方法以极低的资源消耗,模拟出类似 3D 的视觉效果。

其实现方式是:对屏幕上每一列像素投出一条方向略有偏差的射线,并计算这条射线与墙体的交点距离。墙体越近,绘制的柱状图形就越高,最终呈现出纵深感。

简版代码如下:

for (let i = 0; i 320; i++) {    const rayAngle = playerAngle + (i - 160) / 500;    let distance = 0;    while (!isWall(x + distance * cos(rayAngle), y + distance * sin(rayAngle))) {      distance += 0.1;    }    drawColumn(i, distance);  }

虽然这一逻辑只涉及基础的三角函数,但它在整个代码体积中占据了相当大的比重。Kuber Mehta 坦言,如果不是为了动态生成地图,他可能早就选择直接将 HTML 页面 Base64 编码后塞进 URL。但现在看来,这份坚持是值得的。

敌人机制

相比图形渲染,敌人系统的实现则更加艰难。项目早期版本中,敌人数量固定,分布于地图初始区域。玩家一旦走远,地图就变得空空如也——这在小地图中还能接受,但在无限生成的大地图中,问题立刻凸显。

Kuber Mehta 回忆,这部分开发过程“非常头痛”。在体积受限的前提下,想做出像样的射击反馈与敌人 AI,几乎是不可能完成的任务。他也坦言自己并不是游戏开发出身:“我……确实不太会做游戏。”

起初,敌人是完全静止的。后来,他陆续加入了简单的追踪机制,并最终实现了一个核心改进:玩家在移动时,系统会在其附近随机生成敌人 ,从而让整个地图始终保持紧张感和挑战性。

if ((k.ArrowUp || k.w || k.ArrowDown || k.s || k.ArrowLeft || k.ArrowRight) && e.length 10 && Math.random() .01) {    t = Math.random() * 6.283;    Rdist = 1 + Math.random();    X = x + M(t) * Rdist;    Y = y + N(t) * Rdist;    f(~~X, ~~Y) == "0" && e.push({ x: X, y: Y, h100 });}
完成以上版块之后,Kuber Mehta表示,“制作游戏只是挑战的一半,因为真正的挑战是将其放入二维码中。”

图片

概念与可行性

标准最大的二维码(Version 40)最多只能容纳 2,953 字节,也就是大约 2.9KB。这个容量有多小?做个对比:

  • 一个仅 1/15 秒的 Windows 音效文件,就已经 11KB

  • 一张传统软盘(1.44MB)理论上可以存下将近 500 个这样的二维码

面对如此限制,Kuber Mehta 的首个版本大小达到了 3.4KB,超标严重。他的第一反应是:“完蛋了。”

为了解决这个问题,他连续四天进行疯狂压缩与优化,最终将游戏压缩至 2.4KB,虽然过程中做出了一些艰难但必要的取舍。

殊不知,一段折腾之后,更难的还在后面。

要知道二维码只能存储文本或二进制数据,而 HTML 并不属于这两种类型。直接嵌入 HTML 代码并不可行。

对此,不少开发者建议采用 Base64 编码,但这带来了 33% 的体积开销,几乎会吃掉本就所剩无几的存储空间,意味着实际可用空间不到 1.9KB。

在反复尝试后,Kuber Mehta 一度陷入绝望,甚至想要放弃。

后来,他连续两天轮番咨询 ChatGPT、DeepSeek 和 Claude 等 AI 模型,尝试上百种提示词,几乎每次都得到建议:“干脆托管在网站上会更容易。”直到某次,ChatGPT 随口提到了一句DecompressionStream

这成了转折点。

DecompressionStream 是一个现代浏览器支持的 Web API,能够解压 gzip 格式的数据流,简而言之,相当于浏览器内置了“WinRAR”。

Kuber Mehta 形容自己当时“仿佛被雷劈中”,豁然开朗。他确信:在尝试了几乎所有极限游戏优化技巧后,这是当前环境下唯一可行的解决方案

整个游戏压缩嵌入流程如下:

  1. 输入 HTML 内容

  2. 进行 Gzip 压缩(最大压缩等级)

  3. 将压缩数据进行 Base64 编码

  4. 嵌入一个自解压的 HTML 包装器

  5. 转为 data:URI

  6. 尝试生成二维码(使用最高版本)

  7. 如果仍超出体积限制

    → 回头继续压缩优化

  8. 成功生成二维码!

图片

Kuber Mehta 为此还编写了一个 Python 脚本自动化完成上述流程,期间测试了 34 个不同版本,不断调试压缩策略,最终,二维码成功诞生。


图片

未来的可能性

受以上种种限制影响,the backdooms 的画面非常有限,你只会在灰色墙壁之间躲避红眼矩形,但它传达的“感觉”是到位的。

在 Kuber Mehta 看来,这不仅是一个小游戏的诞生,更是一个技术可能性的证明。他将这个项目(现已开源在 GitHub,https://github.com/Kuberwastaken/backdooms)视为一种宣言:

只要压缩得当,再加上 AI 助力,即便是在二维码这种极限载体上,也能运行真正的交互式游戏。

尽管不适合开发复杂项目,但这个方向打开了许多令人兴奋的应用场景。 Kuber Mehta 表示,这种想法也可以在未来应用在更多场景中,如:

  • 离线游戏传播:可通过海报、传单分享

  • 极限编程展示(Demoscene):作为编程创意作品展示平台

  • 低带宽环境下的教育小游戏:无需联网即可运行,具备实用性与趣味性

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/182328
 
78 次点击