为社团活动写了一个「扫码即玩」的多人贪吃蛇
大屏投映整局对战,手机扫一下码就能加入。不用装 App、不用注册、不用等加载——难点全在「现场」这两个字上。
社团活动需要一个能把人留在摊位前的东西:大屏上放着一局正在进行的对战,路过的人掏出手机扫一下码就能进来玩。不能装 App,不能注册,不能等加载。于是就做了这个多人贪吃蛇。
双端结构:大屏负责看,手机负责搓
- 大屏端:打开就是一个投屏画面,渲染整局对战——蛇身、霓虹拖尾、食物、小地图和排行榜。
- 手机端:就是
controller.html,屏幕上只有摇杆和加速键。 - 服务端权威:所有移动、碰撞、吃食物都在服务端按固定步长计算,客户端只负责把状态画出来。
手机只上行一个方向指令,不上传位置。这一点在现场很关键:几十台手机同时连着 WiFi,如果每台手机都把自己算出来的位置发上来,网络和服务器都吃不消。
现场必须提前想好的几件事
1. 人少的时候不能冷场
机器蛇会在人数不足时补位。它们有独立的决策间隔,还带一套「体力」设定——每隔几秒掉一段长度,长度缩到阈值以下就自然退场,所以不会出现机器人无限长大霸榜的情况。
2. 死了还能接着玩
每条蛇三条命,死亡后 1.2 秒复活,并给 2 秒无敌时间。社团现场最怕的就是「一进来就撞死,然后尴尬离场」,三条命刚好够新手把操作摸熟。
3. 带宽要算着花
地图是 362×226 格,几十条蛇在上面游动。如果把完整世界状态每帧广播给每个客户端,流量会非常难看,所以做了裁剪:只下发以你为中心、视野范围内的高精度身体;视野外分档降级处理;同时严格保证「视野和碰撞范围内至少要把粗身体补全」——绝不能出现撞上一条看不见的蛇。
食物也不全量推,只下发一定半径内的,另外按客户端错峰做周期性对账,保证重新进视野的时候不会缺食物。
4. 弱网和旧缓存
现场 WiFi 什么状况都有,所以心跳时间放宽,避免被误判掉线。传输上直接走 WebSocket,跳过 polling 握手省两个 RTT;压缩关掉,因为在 2 核机器上压缩的 CPU 开销比省下来的流量更贵,还额外增加延迟。
const io = new Server(server, {
transports: ["websocket"], // 跳过 polling 握手
allowUpgrades: false,
perMessageDeflate: false, // 关压缩:CPU 比流量更贵
httpCompression: false,
pingInterval: 30000, // 心跳放宽,弱网不易误判
pingTimeout: 45000
});
页面一律 no-store,静态资源用 no-cache——手机端刷新即得新版,不会出现「手机还跑着旧 JS、和服务器新协议对不上」的情况;没改动的资源走 304,几乎不耗流量。
手感
操作是摇杆平滑转向,不是四方向硬拐:基础速度 11 格/秒,按住加速 1.85 倍,转向有最大角速度限制,所以蛇走的是弧线而不是直角。食物带小范围磁吸,蛇头靠近就会被吞掉,省得新手为了对准食物而烦躁。
部署
游戏跑在一台云服务器上,通过 Cloudflare Tunnel 挂到 wxekre.cn/game/。为什么不直接解析到服务器 IP,原因写在另一篇里:《把个人站和个人服务一起挂上 Cloudflare》。