跳转到内容

理解章 A:一个世界是怎么从你的电脑到达别人面前的

约 8 分钟 难度:新手 理解章

第一部到这里,已经做出了一个能站进去的世界。但「Build & Test」按钮按下去之后到底发生了什么?把场景上传到 VRChat 之后,别人客户端里点一下 Go,又是怎么把这个空间复原回他眼前的?

这一章是一张全景图,不教新操作。它把前面做过的事和后面要做的事接起来。读完之后,「网络」「上传」「同步」「Quest」这些后面几部反复出现的词就有了具体的位置。

Unity 场景,本质是一份引用清单

Section titled “Unity 场景,本质是一份引用清单”

Hierarchy 里那一串 GameObject 看起来像一堆「物体」,但保存到磁盘上的 MyFirstWorld.unity 文件,本质是一份 YAML(一种纯文本的结构化格式,主打人类可读,缩进表示层级):

GameObject Floor
Transform: position 0,0,0 scale 10,0.2,10
Mesh Renderer → 引用 → Cube.mesh + Default-Material.mat
Box Collider: size 1,1,1
GameObject Spawn
Transform: position 0,0.2,0
GameObject WorldDescriptor
VRC_SceneDescriptor: Spawns[0] → Spawn

注意全是「引用」。Cube.meshDefault-Material.mat、那张默认天空盒贴图,都不在场景文件里,它们躺在 Library/PackageCache/ 或 Unity 内置资源里。场景文件只记录「我用到了它们」。

这件事之所以重要,是因为接下来要打包,必须把所有被引用到的东西都拽进同一个文件里:别人电脑上没有这套 Library/

按下 Build & Test 时,VRChat SDK 调用 Unity 的 BuildPipeline,做了三件事:

  1. 依赖收集。从 MyFirstWorld.unity 出发,沿着引用图把所有用到的 mesh、texture、material、shader、audio、动画、UdonSharp 编译产物全找出来。没有被引用的资源不会进包,所以 Assets/ 里那些试做了一半又删掉的废稿,不会偷偷被传上去。
  2. 平台编译。Shader 会被编译成对应图形 API 的字节码(图形 API 是 GPU 听得懂的指令规范,PC 用 D3D11/12 + Vulkan,Quest 用 OpenGL ES + Vulkan),UdonSharp 脚本被编译成 Udon 字节码。这一步很慢,是 Build 时间的大头。
  3. 打成 .vrcw 文件。底层是 Unity 的 AssetBundle 格式,外层套一层 VRChat 的元信息(世界名、容量、推荐 PC/Quest)。这就是别人将来要下载的那个文件。

Build & Test 和正式发布走的不是同一条路。

Build & Test 打完包之后,文件直接落在本地一个临时目录(Windows 默认 %USERPROFILE%\AppData\LocalLow\VRChat\VRChat\Worlds\),然后通过命令行参数把本机 VRChat 客户端拉起来,让它直接读这个本地文件。整条路都不出这台电脑。所以:

  • 别人加不进来。他没本机硬盘上那个文件。
  • 不消耗世界配额,也不会被 VRChat 后端审核。
  • 速度快,是日常迭代用的循环:改一行 → Build & Test → 进世界看 → 改一行。

这条本地捷径是开发时主要的工作模式。它解释了一件事:场景从来没传到任何地方,但已经能在一个能跑的世界里走来走去。

点 SDK 控制面板里的 Build & Publish 时,多了一段「往外走」的路:

这里先垫两个词:对象存储可以先理解成「云端文件仓库」,负责放 .vrcwCDN(内容分发网络)可以先理解成「离玩家更近的下载仓库」,负责让不同地区的玩家更快把文件拉下来。

  1. 同样先打成 .vrcw
  2. SDK 用登录账号时拿到的 token(一段临时的登录凭证),把这个文件上传(HTTP PUT)到 VRChat 后端的对象存储(目前主要是 Cloudflare R2),再通过 CDN 分发给玩家下载。
  3. 同时把世界元信息(名字、描述、缩略图、容量、Tag、是 Public 还是 Private)写进 VRChat 的数据库,得到一个全局唯一的 wrld_xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx ID。
  4. 后端把这个 ID 和 CDN URL 返回给客户端。

到这一步为止,「世界」其实就是 CDN 上的一个文件加数据库里一行记录。

别人加入:从 CDN 拉,再装进 Unity Runtime

Section titled “别人加入:从 CDN 拉,再装进 Unity Runtime”

朋友点开他的 VRChat 客户端,搜到这个世界,按 Go

  1. 客户端向 VRChat 后端要这个 wrld_xxxx 的当前版本 URL。
  2. 客户端去 CDN 把 .vrcw 拖回本地。世界越大,别人加载越慢;这也是为什么后面 Quest 章节会反复要求压贴图,加载方可能在用 4G 网下载这些图片。
  3. 文件落地后,客户端内嵌的 Unity Runtime 解包 AssetBundle,把 mesh、material、shader 装进显存(GPU 自己用的高速内存,专门给画面渲染用),把场景结构在内存里重建出来。这一步做的事和编辑器里按 Play 时做的事几乎一样
  4. 实例化 GameObject,VRC Scene Descriptor 决定把人放到 Spawn 物体的位置。

第一次加入会有一段「Downloading」「Loading」转圈,就是步骤 2 和 3。第二次再来就快得多,因为 .vrcw 已经在他客户端的本地缓存里了,除非有新版本上传(那 ID 不变但版本号变了)。

实例(Instance)才是真正在跑的东西

Section titled “实例(Instance)才是真正在跑的东西”

到目前为止说的「世界」,其实只是一份静态地图:一份 .vrcw 文件加一行数据库记录。它躺在 CDN 上不会动。

真正能多人玩的东西叫实例 (Instance)

World = 一个可被加载的空间(一个 .vrcw 文件)
Instance = 一群人此刻一起跑这个世界的一次具体聚会

第一个进入这个世界的人会在 VRChat 的网络服务器上「开一个房间」,他成为这个实例的 Master(实例主);后来的人加入的是这个具体的实例。同一个世界可以同时存在几百个实例,互相完全隔离。想和朋友在同一个世界里见面,必须确保进的是同一个实例(这就是为什么 VRChat 客户端里有 Public / Friends+ / Friends / Invite+ / Invite 这些访问级别和长串的 Instance ID)。

实例本身不在 CDN 上,它活在 VRChat 自研的网络层里。所有玩家位置、按了哪个按钮、抓起了什么东西,都是通过这一层广播给同一实例里的其他人。这条线在第六部会展开。

跨平台:PC、Android(含 Quest)、iOS 各一份

Section titled “跨平台:PC、Android(含 Quest)、iOS 各一份”

VRChat 现在跑在 PC、Meta Quest、iOS 等平台上。GPU、CPU、可用内存差几个数量级,没法共用一个 AssetBundle。

所以同一个世界在 CDN 上存的是多份文件:PC 版、Android 版、iOS 版。每一份都要在 Unity 里切到对应 Build Target 重新打一次包再上传。Android 版还会受 Mobile 限制:shader 白名单、实时光数量、贴图大小等(第七部第 39 章会专门展开)。

SDK 文档里写的 Quest,在创作者侧通常就是指 Android 构建。Unity 里选 Android 打出来的包,Quest 头显和手机 App 下载的是同一份世界资源;iOS 另有一份构建。

没有 Quest 时,把手机连 Unity 做 Android Build & Test,能验加载、粉材质、包体超限等「Android 版能不能用」,日常开发多半到这一步就够。

Quest 和手机跑的是同一份资源,体验不是一回事:Quest 走 VR 双眼渲染和手柄/手势,手机是触屏。掉帧在 VR 里会晕,平屏上不一定感觉得到;视距、尺度、VR 交互手机也验不准。Quest 上帧率过关,手机通常也没问题;反过来不成立。对外标 Quest 支持前,最好还能用 Quest 实机过一遍。

服务器在玩家加入时按客户端平台返回对应那一份。PC 上 Build & Publish 完只发了 PC 版时,Quest 玩家点开会看到「This world has no Quest version」。

那 Unity 编辑器版本为什么要钉死

Section titled “那 Unity 编辑器版本为什么要钉死”

.vrcw 是用某个 Unity 版本编译出来的二进制资源。Unity 大版本之间会改 AssetBundle 格式、改 IL2CPP 中间表示、改 shader 编译流程。如果允许大家用任意 Unity 版本上传,VRChat 客户端就得同时兼容十几种 AssetBundle 格式,工程上撑不住。

VRChat 的解法是钉死一个 Unity 版本(写作此书时是 2022.3.22f1),客户端和所有创作者都用这同一个版本。版本不对的项目根本打不出有效 .vrcw。这是为什么 VCC 第一件事就是检查这个特定版本的 Unity 装没装,它在保护这条管道的兼容性。

创作者电脑 玩家电脑
───────── ──────────
Unity Scene ─[SDK BuildPipeline]→ .vrcw ──→ VRChat CDN ──→ 下载 .vrcw
│ │
│ Build & Test ▼
└─→ 本地临时目录 ─→ 本机 VRChat 客户端 Unity Runtime 解包
加入某个 Instance(VRChat 网络层)
位置 / 按钮 / 拾取 / 语音 全部走网络层广播

第一部到这里收尾。后面几部的位置可以这样找:

  • 第二部 工作台:放大图最左上角那个 Unity Scene 的内部结构,包括 GameObject、Component、Prefab、Material、Light。
  • 第三、四部 编程和交互:往 Scene 里塞 Udon Behaviour,让按钮、Trigger、门有反应,但都还在「单机」尺度。
  • 第六部 和别人一起:图最右下角那条「网络层广播」展开。多人同步从这里讲起。
  • 第七部 让世界变成一个地方:在每个环节里抠体验和性能:Mesh 多大、贴图多重、Light 怎么烘、加载多久。
  • 第九部 发出去:把 Build & Publish 那条路走完,包括元信息、Tag、Community Labs。