网络

会告诉你何时变化的同步值,以及谁都能发送的带类型消息,场景里不用准备任何东西。

更新于 2026-09-10

一个世界里的两个客户端要对同一件事达成一致,办法有两种,选错了就会安静地出错。

同步变量是所有人都持有的事实。 分数、门是开是关、轮到谁。它能留存:十分钟后进来的人不用谁重发,就能拿到当前的值。

消息是发生过一次的事。 打中了、回合开始了、按钮被按了。把它当变量发,出错的样子会像什么都没发生:同一个值赋两次只算一次变化,于是两次相同的攻击变成一次;晚来的人会收到自己没看见的那场战斗的最后一击,像刚刚发生一样;而且只有所有者能写,两个人打同一个目标就会争这个变量,其中一次攻击悄无声息地消失。

这个包两者都有,并且把通常出错的部分接管了。

public class Door : UdoniteBehaviour
{
    private Synced<bool> open = new Synced<bool>();

    public override void Start()
    {
        open.Subscribe(OnOpenChanged);
        Room.Subscribe<Knocked>(OnKnocked);
    }

    public override void Interact()
    {
        open.Value = !open.Value;      // 会复制,每个客户端的处理器都会跑
        Room.Send(new Knocked());      // 包括发送者在内,每个人都收到一次
    }

    private void OnOpenChanged(bool value) => panel.SetActive(value);

    private void OnKnocked(Knocked message) => sound.Play();
}

Room.Send 去的是整个房间,且任何人都能发。关于某一个物体的消息则在那个物体上流动,写成就是该物体的那个 behaviour 上的 this.Send,而那个需要拥有该物体。两者都在消息

open.Value = ... 写入变量并请求复制,没有容易忘记的 RequestSerializationSubscribe 会在值到达时在每个客户端调用你的处理器,而且是在整批更新落地之后,而不是其中一半还是旧值的时候。

谁需要拥有什么

所有权是 VRChat 的,不是这个包的:只有所有者对物体的写入会被复制。不同的是当你不是所有者时会发生什么。

你做的事 需要谁拥有 如果你不拥有
Synced<T> 的值 持有该字段的物体 写入只留在你的客户端,并被下一个到达的值覆盖
对这个物体的通道用 this.Send 这个物体 什么也不会积攒、什么也不会送达,并在控制台说一次
NetworkBuschannel.Send 那条总线所在的物体 同样
Room.Send 不需要 每个客户端写进自己玩家物体上的总线,所以没有什么可争的
移动 NetworkTransform 那个物体 由所有者发送,其他所有人朝它插值

唯一不需要所有者的是房间消息,这也是任何客户端都可能动手时优先选它的主要原因。物体的通道和同步字段最终都只有一个写入者,所以谁都可以动手的世界会先取得所有权:

if (!Ownership.IsMine(gameObject))
    Ownership.Claim(gameObject);

pickup 或 Interact 通常已经取到了。MasterOwned 是相反的情形:继承它,就只有负责仲裁的客户端能拥有该物体,用于不许别人写的状态。

这里有什么

Synced<T> 每个客户端都持有、并会告诉你变化的变量
Room 发送和订阅带类型消息;谁在场、谁在仲裁
NetworkEvent 带字段的消息,而不是一堆字节
NetworkBus 某个物体自己的通道,适合关于该物体的消息
NetworkTransform 位置、旋转、缩放,带插值和瞬移检测
Ownership 谁拥有一个物体,以及取得或交出它
MasterOwned 只允许仲裁客户端拥有的物体的基类

你不用去搭的东西

房间消息需要场景里的一个物体来承载,因为消息必须写进某个客户端拥有的东西,而这种东西无法在世界运行时创建。这个包会替你添加它,并在控制台说明一次原因。从不发送的世界不会被加任何东西。

如果你想自己放,就在带 VRCPlayerObject 的物体上加一个 NetworkBus,那它就会被使用。它按组件查找,而不是按名字。

Udonite → Open Window → Settings → Net 里有个开关管这件事:关掉它,不管世界发不发消息,这个包都不会再碰场景——放总线这件事就完全归你自己管了。默认是开着的。这一个设置放在 ProjectSettings/ 里,和排除列表一样会被提交,因为这是场景归谁所有的事实,不是某个开发者自己的偏好。

出了点问题 重新加载