跳转到内容

第 3 章 · Owner 不是 Master

约 9 分钟 难度:1 动手章

第 2 章实现 C 的按钮里有一行:

if (!Networking.IsOwner(gameObject))
{
Networking.SetOwner(Networking.LocalPlayer, gameObject);
}

这一行同时出现了三个网络身份概念:IsOwnergameObject(被拥有的对象)、LocalPlayer(当前客户端是谁)。还有一个概念第 2 章里没出现,但很多教程会把它和 IsOwner 混用:IsMaster。这一章把这四个概念彻底分开。


判断回答的问题API
Networking.IsOwner(obj)这个对象当前是不是在拥有?Networking.IsOwner(gameObject)
Networking.IsMaster是不是这个实例的 Master?Networking.IsMaster
Networking.LocalPlayer这个客户端代表的是哪个 Player?VRCPlayerApi 对象

三个判断的「主语」不一样。Owner 是网络 GameObject 上的属性:同一个 GameObject 上的 UdonBehaviourVRCObjectSync 共享 Owner,不同 GameObject 可以各有不同 Owner,所以同一时刻 A 物体属于玩家 1、B 物体属于玩家 2 是正常的。Master 是实例上的属性,整个房间里只有一个人是 Master。Local Player 是客户端上的属性,每台机器上 Networking.LocalPlayer 就是这台机器前面的人。

写错最常见的形态是把第二条当第一条用:「我是 Master,所以这个按钮归我管」。这是错的。Master 不是默认的「全局裁判」,新版 SDK 文档里官方明确把 IsMaster 标记为旧逻辑,推荐改用 IsOwner 判断(针对具体对象)或 IsInstanceOwner 判断(针对实例创建者)。


每个网络对象有且只有一个 Owner。Owner 的两条核心能力:

  1. 它是唯一能修改这个对象上 [UdonSynced] 字段并让变更被同步出去的人。
  2. 它是唯一能调用 RequestSerialization() 真正发包的人(手动同步模式下)。

非 Owner 即使代码里直接写 isOn = true,本地内存里那个字段确实变了,但 VRChat 网络层不会把它发出去,过一会儿 Owner 那边的同步又会把它盖回来。

写 VRChat 同步代码时这是第一条纪律:改同步字段前,确认是 Owner

if (!Networking.IsOwner(gameObject))
{
Networking.SetOwner(Networking.LocalPlayer, gameObject);
}
isOn = !isOn;
RequestSerialization();

这段代码适合入门示例,但有一个伏笔:SetOwner 的结果要经过网络传播,远端确认会有延迟。所有权转移后的可靠确认点是 OnOwnershipTransferred(player)。如果业务逻辑更复杂,比如先抢权、等待一段时间、再修改关键字段,就不要把「调用了 SetOwner」直接当成「所有客户端都承认我是 Owner」。第二部 13 章会回到这个时间窗口的问题。

场景里一开始就摆着的网络对象,Owner 默认是 Master。Master 是房间第一位玩家,或上一任 Master 离开后被自动指定的那位。

空场景刚被加载时,所有同步组件的 Owner 都是 Master。Master 离开时,他拥有的对象会自动转给新 Master。任何时候有人调用 Networking.SetOwner(somePlayer, obj),对象的 Owner 就脱离 Master 系,归 somePlayer 了。

所以 Master 既不是「全局裁判」,也不是「永远的 Owner」。它的真实身份是:没人主动 SetOwner 时的兜底 Owner


Master 的官方定义只有几条。进入空实例的第一个玩家成为初始 Master。当前 Master 离开时,所有权会自动转给某位剩余玩家,具体选举规则官方未公开。要感知自己接管了什么,靠对象上的 OnOwnershipTransferred,而不是去赌「我是不是新 Master」。Networking.IsMaster 返回的就是本地玩家是不是 Master。

这里有两个容易踩的细节。一个是 Master 切换不可控:Master 离开、Android 玩家把 VRChat 切到后台太久,都可能让 Master 易主,任何依赖「这个人一直是 Master」写出来的逻辑都会在某次切换后崩。另一个是不应把「是不是 Master」作为玩法权限的门槛:需要门槛时用 Networking.IsInstanceOwner(实例创建者)或自己维护一个「主持人」同步字段。

IsMaster 在 Vol.2 里会出现,但出现的位置非常有限,主要在第 11 章讲 GameState 兜底的时候。除此之外的代码里,看到 IsMaster 都应该停下来想一想能不能换成 IsOwner 或者一个明确的「裁判玩家」字段。


Networking.LocalPlayer 在每一台机器上返回该机器上的玩家本人。它是 VRCPlayerApi 对象,能查 ID、姓名、位置、朝向、是否手持物体等。

几个要点。编辑器里 Networking.LocalPlayer 可能是 null,因为编辑器里没有真实玩家,需要时用 Utilities.IsValid(player) 包一层。每台机器的 LocalPlayer 不一样,所以 Networking.SetOwner(Networking.LocalPlayer, gameObject) 含义是「让我自己成为这个对象的 Owner」,是常用的抢权写法。

不要把 Networking.LocalPlayer 存到 [UdonSynced] 字段然后同步出去并期待别人那边读到的是「同一个玩家」。它是本地概念。需要跨客户端引用某位玩家时,用 playerIdint)同步。


下面三种情况是写 Vol.2 同步代码时几乎一定会撞到的。

场景一:Master 离开后,怪不动了

Section titled “场景一:Master 离开后,怪不动了”

症状:写了一个简单的 AI,刷怪、巡逻、追玩家全在一段 UdonSharp 里跑。Master 离开后,怪愣在原地不动。

原因:刷怪用的那个 GameObject 是场景里默认存在的,它的 Owner 默认是 Master。Master 离开后 Owner 转给了新 Master,但 AI 代码里所有「移动怪」的逻辑只在 Owner 端跑,新 Master 的客户端可能还没意识到自己接管了这件事,或者 AI 代码里写了 if (!Networking.IsMaster) return; 这种判断,新 Master 的客户端要等下一次 tick 才发现自己是 Master,这中间的几百毫秒到几秒,怪就停了。

正确思路:AI 状态由对象的 Owner 驱动,而不是由 Master 驱动。OnOwnershipTransferred(player) 事件会在所有人那边触发,新 Owner 接到这个事件就知道「现在该轮到我推动 AI 了」。第二部 14 章会展开「Owner 失效恢复检查表」。

场景二:抢权后立刻改字段,对方先看到旧值

Section titled “场景二:抢权后立刻改字段,对方先看到旧值”

症状:玩家 A 按按钮,本地代码先 SetOwner(LocalPlayer, obj),再设 isOn = true,再 RequestSerialization()。但远程玩家 B 那边看到「灯先亮一下,又灭一下,再亮起来」。

原因:抢权请求和值变更几乎同时发出。B 这边的执行序列可能是:先收到「Owner 变成了 A」,但这个对象当前的同步值还是上一帧 A 还没设完时的旧值,于是先用旧值刷一次本地,几十毫秒后再收到 A 的新值,再刷一次。视觉上就是闪一下。

正确思路:本地切完 Owner 和值之后,本地直接调用 ApplyLight() 让本地立刻是对的状态。远程那边短暂的「值还没到」就让它呈现旧值好了,新值很快会到。如果业务实在不能接受这种闪烁,把抢权和应用值拆开做:先抢权,等 OnOwnershipTransferred 在自己这边触发了再改值并 RequestSerialization。第二部 13 章详细展开这个时序。

症状:每帧 Update 里写了 if (Networking.IsOwner(gameObject)) 来决定要不要做事,但游戏里有几十个这样的对象,性能监视器显示 Networking 调用占用很高。

原因:Networking.IsOwner(go) 不是缓存查询,每次调用都要做一次内部检查。在 Update 里频繁调用是有代价的。

正确思路:在 Start 里取一次自己的 player,在 OnOwnershipTransferred(player) 里更新一个本地 bool isOwner 字段,Update 里直接读这个字段。这种小优化第六部会再讲,但基础概念这里先提一下。


合作防守世界里有一个 GameState 对象,承载当前波次、总分、阶段。回答下面三个问题:

  1. 它的 Owner 应该是谁?是 Master?是某个固定玩家(比如 InstanceOwner)?还是「轮流当」?三种方案各有什么取舍?
  2. 如果 Owner 中途离开了,波次和分数会发生什么?怎么避免「波次倒退」「分数被清零」?
  3. 一个临时陷阱物体(玩家进区域时触发),它的 Owner 默认是 Master 还是被触发的那个玩家?为什么?

不需要写代码。能在脑子里讲清楚三种 Owner 拓扑的取舍,就准备好读第二部了。


三个判断各自回答不同问题:IsOwner(这个对象归我?)、IsMaster(我是兜底裁判?)、LocalPlayer(我这个客户端代表谁?)。默认 Owner 是 Master,但 SetOwner 后归别人;Master 是兜底,不是裁判。改同步字段前先 IsOwner,看到 IsMaster 都该停下来想一想能不能换。

第 4 章会把「RequestSerialization 不是立即发送」这一句拆开来讲,把整条手动同步生命周期画清楚。


  • VRChat Creator Docs · Network ComponentsIsOwner / IsMaster / LocalPlayer / SetOwner 等 API 的官方说明。
  • VRChat 汉化文档 · Udon 网络IsMaster 旧逻辑提示与新推荐做法。
  • 附录 · 术语表OwnerMasterLocal PlayerAuthoritySetOwner 的客观定义。