ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

基于WebXR与WebSocket的浏览器多人VR二十一点游戏开发实践

基于WebXR与WebSocket的浏览器多人VR二十一点游戏开发实践 简介这是一份基于浏览器虚拟现实技术实现的多人二十一点游戏项目面向对Web游戏架构与实时通信感兴趣的进阶开发者。资源源于作者对网络游戏服务器与客户端职责划分的探索采用网络套接字服务器加静态客户端的结构服务器端完整处理发牌、玩家手牌状态、得分计算、回合结束和胜负判定客户端则承担三维场景渲染与操作反馈。压缩包共102个文件以脚本、页面、配置、贴图及三维模型为主其中包含TypeScript与JavaScript源码、HTML入口、JSON配置以及glTF/OBJ格式场景模型和PNG/JPG纹理素材整体大小约15.96兆字节。通过该工程可以直观学习多人在线回合制游戏的基本流程了解如何搭建实时数据通道、设计服务端权威逻辑并整合虚拟现实渲染接口。目前已有132人学习下载适合作为WebVR棋牌类游戏的入门参考或二次改造蓝本。 我最初决定做 blackjack-vr原因其实特别简单我手头有一副支持 WebXR 的 VR 眼镜但打开设备自带的应用商店翻了半天发现想找一个能随时开局、不用重新下载安装、还能拉上朋友一起玩的多人卡牌游戏居然比想象中难得多。于是我想与其等别人做不如自己直接在浏览器里做一个“打开就能玩”的多人二十一点 VR 游戏。这个项目从立项到跑通第一局完整对局前后花了大概三周。它不是一个只停留在“能看不能玩”的技术 Demo——目前已经可以在同一网络或公网服务器下支持最多 7 名玩家1 名荷官 6 名玩家同桌玩标准规则二十一点。整个项目没有用到任何原生客户端框架渲染部分走 WebXR Three.js网络部分走 WebSocket数据同步的复杂度控制在一个房间内可接受的范围内。这篇文章主要面向两类人一类是想做 WebXR 多人应用但还没想清楚“3D 场景 实时同步”该怎么落地的开发者另一类是想在浏览器里复刻一个桌游空间、棋牌房间的产品经理或独立开发者。我会把整体设计、核心实现、多人同步的思路、跨浏览器兼容的取舍还有我在实际开发里踩过的坑都写清楚。你能照着这份思路至少把一个“多人同桌打牌”的骨架搭起来。1. 方案定调为什么是“浏览器 VR 二十一点”1.1 浏览器路线的三个现实理由最开始我考虑过 Unity WebGL 的导出方案也考虑过用独立的 WebSocket 服务端去接一个原生客户端。最后选择纯浏览器路线不是因为它的渲染性能最好而是因为它解决了三个之前一直绕不开的现实问题。第一个是分发成本。浏览器游戏不需要走应用商店审核不需要用户下载安装包也不需要处理 Android 和 iOS 两套签名体系。你只要把一个 HTTPS 地址发给朋友对方戴上 VR 眼镜在 Chrome 或 Edge 里打开链接点一下“进入 VR”就完成了从零到进入牌桌的全部过程。这对“临时组局”的场景来说几乎是决定性的优势。第二个是权限和平台限制最少。WebXR 在桌面浏览器、安卓平台以及主流独立 VR 头显的浏览器里都有相对成熟的支持路径。我做这套项目用的是 Meta Quest 2 和一台支持 WebXR 的安卓手机做验证桌面端用 Chrome 配合官方的 WebXR API Emulator 来调试整个过程中没有遇到因为设备厂商政策导致的阻塞问题。第三个是迭代效率。浏览器里跑的是 JavaScript / TypeScript热更新只需要刷新页面。做 VR 开发时每一次调试都会涉及“戴眼镜、观察画面、摘眼镜、改代码”的循环这个循环越短开发效率就越高。相比之下原生 Unity 项目哪怕只是改一个 UI 文案也要重新构建到设备上来回一次至少几分钟。浏览器方案让我把单次调试循环压缩到了十几秒。1.2 为什么选二十一点作为第一个场景多人 VR 应用的第一个场景选什么其实大有讲究。我最初想做过“多人德州扑克”但很快放弃了因为德扑的公共牌、筹码分级、加注节奏、座位轮转状态实在太复杂而且在 VR 里要精确展示筹码数量变化对 UI 和信息层级的要求非常高。我也想过做“狼人杀”语音场但那个更偏语音互动3D 空间感不是刚需。二十一点是一个刚好卡在“足够简单”和“足够有仪式感”之间的游戏。它的规则足够简单发牌、要牌、停牌、爆牌、比较点数整个规则用一张状态机图就能画清楚适合做 MVP。同时它又有非常强的桌面仪式感——荷官发牌、翻牌、玩家决定要不要牌这些动作天然适合在 VR 里用“手”去完成而不是用鼠标点按钮。从数据模型的角度看二十一点的牌桌状态其实是一个非常清晰的单例状态机一局只有唯一一个荷官、一个牌靴shoe、若干玩家的手牌和筹码没有任何并行分支。这种确定性让多人同步的实现难度大幅降低。我的判断是如果连二十一点这种单桌状态明确的游戏都跑不通多人同步那做更复杂的棋牌类 VR 应用就更不用想了。1.3 技术栈取舍技术栈我最终圈定为下面这四块渲染Three.jsr155 以上版本WebXR Device API。3D 交互手部控制器射线检测 控制器按键事件。网络WebSocket服务端用 Node.js ws库。编译构建Vite开发时用它的局域网调试能力非常方便。Three.js 对于卡牌游戏来说其实有点“杀鸡用牛刀”但它有两个不可替代的好处一是 WebXR Device API 的封装已经非常成熟只要配置好 scene 的渲染层和 reference space就能在桌面端、移动端和头盔端共用同一套场景代码二是它的对象模型足够直观每张牌、每个筹码都可以是一个THREE.Object3D后面做同步、位移、动画都很好操作。不用现成 WebXR 游戏框架比如 A-Frame是因为这个场景里没有太多复杂物理需求反而需要大量自定义的回合状态逻辑纯框架反而碍手碍脚。我用 Three.js 就是为了在需要写自定义逻辑时没有“框架的黑洞感”。2. 核心实现虚拟牌桌上的四个关键模块2.1 牌桌空间和座位设计整个场景的空间布局是我最早确定的部分因为后续所有交互和多人同步都要建立在这套空间坐标上。我用了一个 1.6 米直径的椭圆桌面高度 0.75 米这是人体工程学上比较自然的桌面操作高度。玩家座位是呈 C 形分布在桌子外侧的荷官在正前方玩家在两侧和正对面。每个座位在服务器端都有一个固定的座位编号seatId从 0 到 6。座位 0 是荷官其余 6 个是玩家座位。这个 seatId 不只是一个显示标签它是所有同步消息里的关键字段。比如hit指令不是由客户端发“我要牌”这么简单而是要发“座位 3 的玩家要牌”。服务器拿到消息后会去校验座位 3 是否真的在等待要牌状态校验通过才允许发牌。做 VR 场景时有个容易被忽略的点很多用户进入 VR 之后不会像在手机上那样主动去阅读界面引导。所以我的座位分配方式是“原地匹配”——玩家进入房间后戴上 VR看到桌上每个空座位上方都有一个悬浮的“坐下”按钮拿手柄对准后扣动扳机就会坐到那个座位上。这个方式比“自动随机分配座位”要有临场感也能避免玩家进入后不知道该看哪里的问题。2.2 卡片、筹码的 3D 表现与交互卡牌的模型我没有去下载贴图资源而是用代码直接生成的桥牌尺寸矩形89.9mm × 50.8mm 的比例换算成 Three.js 单位。牌面信息用 Canvas 离屏绘制出牌面文字和花色图案然后作为材质贴到牌的正反面。这样做的好处是你想要任意多副牌的牌面不需要额外加载任何图片资源还能动态生成本地化牌面文字。为了减少 draw call我把所有静态牌桌贴图合成了一张图集牌的贴图也尽量复用材质实例。卡牌放在鞋具shoe附近时默认是“牌背向上”的暗面状态。玩家要牌后荷官从鞋具里抽出一张牌做一个弧线发牌动画落到玩家座位前的投注圈内。这里的关键细节是发牌动画不能被多人同步消息打断动画由发牌玩家荷官视角本地触发但牌落定后的状态必须由服务器广播。筹码的交互也是一样。下注阶段玩家从自己座位侧边的筹码堆里拿取筹码拖放到面前的下注圈。三个常用面值分别是白色 1、红色 5、蓝色 10。为了避免误操作我规定只有在下注阶段才允许拾取筹码其他阶段射线穿过筹码不会有任何交互反馈。2.3 游戏流程的状态机我一开始以为二十一点的状态机很简单写完才发现里面还是有不少边缘情况。最终的流程状态定义如下状态名状态含义允许的玩家动作IDLE等待房间人数达到最低要求坐下、起身BETTING下注阶段选筹码、下注、确认DEALING发牌阶段等待PLAYING玩家行动阶段要牌、停牌、加倍DEALER_TURN荷官行动阶段等待SETTLING结算阶段等待PLAYING 阶段内部又按座位号逐个轮流当前轮到的玩家座位上方会出现一圈高亮光晕。这个设计特别重要因为 VR 里玩家视野有限如果只靠声音或者文字提示很容易出现“不知道现在该谁出牌”的尴尬。高亮跟随当前 action 移动就算你的座位背对着荷官余光也能看到轮次在推进。状态机的推进完全由服务器控制。客户端可以上报“我想要牌”但“现在能不能要、要完以后进入什么状态”全部由服务器决定。这样做是多人卡牌项目里最稳妥的设计能大幅减少作弊和不同步的可能。2.4 操作映射与反馈VR 里最常见的交互方式是手柄射线 扳机点击。我在交互层做了一个统一的PointerAction抽象任何可交互对象牌、筹码、按钮都可以注册自己的onAction回调。射线判定用的是THREE.Raycaster但这里有个性能陷阱——每帧对场景里所有对象做射线检测没必要ETC。实际中我用了简化的方式可交互对象的高亮体积collider volume默认隐藏只有射线前 5 米距离内扫过时才动态启用对应的对象高亮。这个方法大大减少了射线检测的候选对象数量让手柄移动时的高亮反馈更跟手。操作反馈上除了视觉高亮我还加了手柄里的轻震动WebXR 的navigator.xr支持部分控制器震动 API如果不支持就用视觉反馈代替。下注成功、要牌成功、爆牌这三个情形分别用不同频率的震动玩家不摘眼镜就知道这步操作是否被系统接受。3. 多人同步从“各自看牌”到“同桌打牌”3.1 同步方案选型多人 WebRTC 和 WebSocket 之间我纠结了一小段时间。WebRTC 的数据通道延迟更低适合 FPS 那种高频状态同步但遇到网络波动时的重连、数据通道协商、ICE 穿透问题工程量要上一个台阶。二十一点这种回合制卡牌游戏单局内状态变化频率极低我最后选了 WebSocket。选 WebSocket 有一个很实际的好处调试方便。我可以在浏览器控制台直接看到每一条收发消息也可以单独写一个脚本模拟一个玩家去连接服务器做自动化测试。WebRTC 的端到端加密传输链路在调试时反而是一个障碍日志很难同时挂在两个端上对比。卡牌类应用对延迟的容忍度其实比大多数人想象的高很多。一次“要牌”操作从按下到看到牌飞出哪怕是 300 毫秒的延迟玩家也基本感知不到问题。真正需要低延迟的是筹码的物理拾取和牌的拖拽但这些都属于“本地表现层”不需要实时同步给别人。把数据同步和本地表现拆开是我这套架构里最核心的一个决定。3.2 消息协议设计消息协议我全部采用 JSON 文本格式没有用二进制因为在卡牌场景里消息体很小而文本格式对调试、录日志、写测试脚本的好处远超那一点网络开销。核心消息分成三类房间控制join、leave、sit、stand、startRound。对局动作placeBet、hit、stand、double。状态广播stateSync全量状态用于新玩家进房或掉线重连、actionBroadcast增量事件如card_dealt、turn_changed。stateSync是全量 JSON包含当前牌桌所有座位信息、每人手牌、下注额、阶段状态。它的启动时机有两个一是新玩家加入房间时二是玩家掉线重连时。增量事件则是每局动作发生时才广播。一个容易踩坑的地方在于牌的归属信息不能通过增量事件以明文广播。如果服务器把荷官的暗牌也广播给所有客户端那玩家直接看控制台网络日志就能看到荷官的牌这等于粗暴地作弊。我的做法是每个人手牌的字段里区分publicCards明牌和privateCards暗牌只有对应座位的玩家和荷官本人能收到自己的privateCards内容。在服务器里加一个简单的座位权限校验就能解决但很多人第一次做棋牌类应用时往往会漏掉这层。3.3 掉线和重连VR 应用最容易出现的崩溃场景不是代码 bug而是设备休眠或者网络切换。用户把 VR 眼镜摘下来再接回去WebSocket 可能已经断了。我的处理方案是客户端断开后进入重连等待每隔 2 秒尝试重新连接最多尝试 10 次同一局游戏尚未结束时服务器保留该玩家的座位和手牌状态 60 秒。重连成功后的流程是客户端发reconnect消息并带上上一轮拿到的sessionToken服务器校验通过后回发全量的stateSync。这样玩家的座位、手牌、筹码、当前阶段能完全恢复到断线前的状态不需要重新开始整局游戏。还有一类掉线原因是浏览器空闲后被系统回收了 WebSocket 内存这是 Web 平台的一个天然局限。我在客户端加了心跳机制每 15 秒发一次ping服务器收到后回pong。心跳同时也能检测死链防止服务器端积累一堆半开连接。4. 跨浏览器兼容与性能优化4.1 WebXR 在浏览器里的足迹做浏览器 VR 开发躲不开跨浏览器兼容这个话题。我实测下来当前对 WebXR 支持最稳的还是桌面版 Chrome 和 Edge它们对immersive-vr模式、手柄控制器的抽象都比较一致安卓手机上的 Chrome 浏览器如果配合轻量级 VR 盒子也能跑起来基本的透视和旋转。Meta Quest 系列自带的浏览器对 WebXR 的支持相当好基本上不需要额外设置就能直接访问项目地址进入 VR 模式。需要特别注意的是不同浏览器的启动条件差异。桌面 Chrome 里进入沉浸式 VR 前会弹一个“是否允许使用 VR 设备”的权限询问而 Quest 浏览器里则通常是在页面顶部显示一个“进入 VR”的按钮。我建议开发者不要依赖浏览器自己弹出的提示而是在页面上做一个显眼的“进入 VR”按钮再通过navigator.xr.isSessionSupported(immersive-vr)去判断环境是否支持避免用户在“不知道在哪点进入”的地方卡住。另外WebXR 只允许在安全上下文HTTPS 或 localhost下启用。如果直接在局域网 IP 上裸跑 HTTP控制器、头部追踪等关键接口会被浏览器直接屏蔽。开发时我用 localhost线上必须是 HTTPS。如果你买了域名但还没配置证书先用 Let’s Encrypt 免费证书顶上否则 VR 功能会莫名其妙不可用。4.2 兼容降级不在 VR 里也能进牌桌并不是每个想一起玩的朋友手里都有 VR 眼镜所以我在项目里做了一个“桌面模式”作为降级方案。桌面模式下场景视角走普通透视相机鼠标拖拽旋转视角点击操作代替手柄射线。这个降级方案只改交互层不改游戏逻辑层所以桌面玩家和 VR 玩家是可以同时坐在同一张牌桌上的。这里的核心技巧是把“控制器指针”和“鼠标指针”都抽象成同一个Pointer接口。VR 里这个接口由XRController实现桌面里由MousePointer实现而所有可交互对象只认这个接口。这样跨端兼容就不是“做两套界面”而是“同一套业务逻辑接两个输入源”。4.3 性能预算和渲染优化VR 渲染的性能预算比普通 Web 页面苛刻得多。普通页面掉帧可能只是用户觉得“卡”VR 里掉帧直接带来眩晕。我的目标是把帧率稳定在 72fps 以上为此做了三件事。第一件事是严格控制 draw call。整个牌桌场景的静态部分牌桌模型、背景、灯光合并成两个静态网格动态对象只有卡牌、筹码和一个指示光晕。我统计过一局中最多 6 名玩家同时在场每名玩家手牌不超过 5 张、筹码不超过 10 个总动态对象数量在 90 个以内每个都是独立的网格。实测 draw call 最大值在 130 左右还在 WebGL 能轻松承受的范围内。第二件事是纹理内存控制。卡牌背面共用一张 256×256 的贴图牌面用 128×128 的 Canvas 纹理池来动态生成。筹码烘焙成 64×64 的图集整个项目的纹理总量不到 5MB。浏览器的 VR 渲染对纹理内存上限非常敏感贴图一大就容易在某些设备上直接黑屏能省则省。第三件事是避免主线程卡顿。牌的发牌动画、筹码滑行动画都放在 requestAnimationFrame 循环里跑不阻塞网络消息解析。WebSocket 消息收到后只更新数据模型不直接操作 Three.js 对象等到下一帧渲染时才把数据变化反映到场景里。这个“数据/渲染分离”的模型让我在处理多人事件时几乎没有遇到帧率抖动。5. 常见问题和排查记录5.1 VR 会话无法启动或黑屏这是最常遇到、也最让人头疼的问题。我排查的顺序一般是这样的确认页面是 HTTPS 环境不能用局域网 IP 裸跑 HTTP。在浏览器控制台执行navigator.xr.isSessionSupported(immersive-vr)看返回结果。如果返回 false说明当前浏览器或设备不支持 WebXR需要换 Chrome/Edge 或升级系统。点击“进入 VR”按钮后黑屏但听到按钮音效通常是 WebGL 上下文丢失或渲染循环还在用旧相机。检查renderer.xr.enabled true是否已经设置并且renderer.setAnimationLoop()有没有注册成功。如果是 Quest 浏览器进入 VR 前会要求用户手动授权授权弹窗被忽略时也会表现为按钮点击后没反应。提示把“进入 VR”按钮绑定到用户手势里再调用session.requestSession()不要在一进入页面时就自动请求 VR 会话这会被浏览器的自动播放/自动权限策略拦截。5.2 抓牌不稳和穿模卡牌这种薄片几何体在 VR 里用手柄射线去抓取时特别容易“一碰就飞”。主要原因是牌太薄射线的 tolerance 范围过小。我把每个牌对象里面包了一层不可见的胶囊体碰撞体积厚度扩大到实际卡牌的 3 倍这样一来手柄放过去的感觉就自然多了。穿模问题则是卡牌落桌时的 z-fighting。牌的落桌高度我设置成 0.002 单位但部分显卡深度精度不足时会出现闪烁。最终的解决方法是给桌面所有牌增加一个polygonOffset并且统一牌的落桌高度为固定值不靠物理引擎临时碰撞决定位置避免浮点抖动。5.3 多人状态延迟/错乱多人对局中比较常见的一个坑是客户端本地先播放了发牌动画但服务器因为校验失败没有广播“牌已发出”导致每个玩家的画面出现不同步。我的方案是把所有动作的“确认”流程做成两段式——客户端先进入“等待确认”的临时状态显示一个转圈动画只有收到服务器的actionBroadcast确认消息后才真正把牌亮出来或进入下一阶段。如果有多台机器同时连同一个房间我建议在测试环境里开启 WebSocket 的消息序号校验每一条落地消息都带一个单调递增的seq编号。客户端如果发现收到消息的seq比预期的跳号了说明有丢包或消息乱序这时强制触发一次stateSync拉全量状态来校准。我的实际体会做 blackjack-vr 这段时间让我特别有感触的地方在于浏览器这个平台比大家想象中更适合做轻量级 VR 多人应用。很多人谈到 VR 第一反应是“要装客户端、要买独立设备、要折腾基础 SDK”但实际上一套成熟的 WebXR Three.js WebSocket 组合足够支撑起一个规则明确、空间感真实的多人牌桌体验。二十一点只是这套架构的第一个验证场景轮盘、斗地主甚至狼人杀只要把状态机拆清楚都能在同样的框架里跑起来。最后再分享一个小经验如果你打算做浏览器 VR 项目一定要尽早准备一个“桌面降级模式”。它不只是给没有 VR 设备的朋友准备的也给你自己留着一条活路——当 VR 设备不在身边但又想快速检查逻辑改动时打开桌面模式几分钟就能验证完了不用每次都戴眼镜。这个习惯能极大减少开发时的疲惫感我后来几乎每天都靠它做日常调试。本文还有配套的精品资源点击获取
返回列表