Skip to main content

网络

CyanTrigger 和 Udon 提供多种网络功能。本文档涵盖其基础知识,建议读者同时参考 VRChat 官方网络文档

广播类型

CyanTrigger 中,每个事件都具有广播类型。默认类型为 Local,仅在本地客户端执行,不进行网络广播。其他两种广播类型用于网络交互:

  • 发送给所有人: 此广播类型将事件操作发送给实例中的所有客户端。
  • 发送给所有者: 此广播类型仅将事件操作发送给对象的拥有者。其他客户端不会执行该操作。

这些网络广播类型允许用户在虚拟空间中协调事件。

所有权

每个游戏对象都只有一个拥有者。默认情况下,实例的 master 客户端拥有该对象。对于拾取物品,拥有者是最后拾取该物品的客户端。 修改对象所有者的唯一方法是使用 Udon 或 CyanTrigger 中的 Networking.SetOwner 函数。游戏对象的所有权仅对网络相关功能(例如位置同步和同步变量)重要。只有拥有者才能更新对象的同步位置和修改同步变量,并将修改后的值发送给其他客户端。

同步设置

每个 UdonBehaviour 都具有一个 Sync Method 属性,用于控制对象如何处理网络同步。它主要决定变量何时与其他客户端同步。选择 Sync Method None 将禁用 UdonBehaviour 的所有网络功能。禁用 GameObject 或 UdonBehaviour 将阻止所有同步变量的同步,因此不建议这样做。CyanTrigger 默认尝试自动设置同步模式,但用户可手动调整。

image.png

有 4 种不同类型的 Sync Methods 可以在 CyanTrigger 上使用:

CyanTrigger 提供四种 Sync Method

image.png

Continuous (连续)

  • 此方法会定期多次序列化 UdonBehaviour 上的同步变量,即使值未发生变化。用户无法控制序列化时机。仅当变量需要频繁更新且变化迅速时才应使用此方法。如果 UdonBehaviour 所属的游戏对象也包含 VRCObjectSync 组件,则必须使用连续同步。大多数情况下,建议使用 Manual 同步而非 Continuous。 Continuous 同步方法不能用于包含其他 UdonBehaviour(使用 Manual 同步)的游戏对象。

Manual (手动)

  • 除非用户调用 UdonBehaviour.RequestSerialization,否则此方法不会执行任何同步操作。只有调用该函数后,才会序列化变量数据并同步值。Manual 同步方法不能用于包含其他使用 Continuous 同步的 UdonBehaviour 的游戏对象,或包含 VRCObjectSync 组件的游戏对象。 详情请参考 VRChat 官方文档
Manual with auto request (手动与自动请求)
  • 这是 Manual 同步方法的特殊版本,CyanTrigger 会自动判断是否需要调用 RequestSerialization。如果事件修改了同步变量,则会自动调用 RequestSerialization。此方法简化了用户操作,无需手动调用函数。如果需要精确控制同步时机,建议使用 Manual 同步。请注意,如果 CyanTrigger 尝试修改另一个 CyanTrigger 上的变量,则必须调用该 CyanTrigger 以确保正确同步。

None (无)

  • 此方法禁用所有同步功能。同步变量和网络事件将被忽略,所有操作仅在本地客户端执行。如果这是游戏对象上唯一的 UdonBehaviour,则该对象也不具有所有权。Sync Method None 可用于包含其他 UdonBehaviour(使用不同 Sync Method)的游戏对象。

自动设置同步模式

默认情况下,CyanTrigger 会自动确定所需的 Sync Method。此时,Sync Method 选择项将被禁用,并显示自动选择的模式。如果不需要自动设置,可以取消选中 "Auto Set Sync Mode" 并手动选择同步模式。自动设置模式会分析 CyanTrigger 中使用的所有操作和输入,以确定合适的 Sync Method。同步变量和网络事件将确保对象同步。如果没有同步变量或网络事件,但需要所有权,则检查游戏对象的所有者(使用 "This GameObject" 变量)将确保 CyanTrigger 已启用网络功能。

image.png

同步变量

CyanTrigger 可以定义同步的全局变量。这些变量将被序列化并同步到实例中的所有客户端。如果你是对象的拥有者,在序列化同步变量之前,将调用 PreSerialization 事件;序列化完成后,无论成功或失败,都会调用 PostSerialization 事件并返回结果;在所有远程客户端上,接收到新的变量数据时,将调用 OnDeserialization 事件。可以使用此事件检查变量更新。请注意,即使通过网络更改同步变量,OnVariableChanged 事件也会自动调用。

常规同步变量设置

使用同步变量时,需要考虑以下两个方面:

  1. 设置同步变量的值 (仅一个客户端执行):
  2. 对同步变量的值更改做出反应 (所有客户端执行):

例如,请参阅下面的示例。您还可以查看 CyanTrigger 包中包含的 Networking Example 场景,以获取同步变量用法的更多不同示例。

设置同步变量

确保在 CyanTrigger 顶部的变量列表中定义变量,并将其设置为 "Synced"。注意,并非所有变量类型都支持同步。务必设置初始值。

确保所有权

设置同步变量的值时,必须是 GameObject 的拥有者。使用 Networking.SetOwner 操作将游戏对象的所有者设置为本地客户端。如果不是拥有者,则设置变量的值只会在本地客户端生效一帧,随后会重置为拥有者的值。

设置变量值

当本地客户端是游戏对象的拥有者时,可以使用任何具有输出变量的操作来设置同步变量,例如:

  • bool.set
  • bool.UnaryNegation
  • int.setint.Addition
  • float.set
  • VRCPlayerApi.get DisplayName

具体方法取决于用例和变量类型。每种类型都有一个 Set 操作,允许将变量设置为特定值。某些情况下可能需要数学运算或先从其他对象获取值。

请求序列化

设置变量值后,需要通知 Udon 将该变量同步到其他客户端。CyanTrigger 的同步模式将决定是否需要手动调用 UdonBehaviour.RequestSerialization。如果同步模式为 Continuous,则系统会自动处理;如果为 Manual,则需要手动调用;如果为 Manual with Auto Request,CyanTrigger 会自动生成此调用。请注意,只有在 CyanTrigger 自身操作中设置变量时,才会自动生成 RequestSerialization 调用。如果 CyanTrigger 尝试修改另一个 CyanTrigger 上的变量,则必须调用该 CyanTrigger 以确保正确同步。

注意

只有一个客户端执行设置变量值的操作,这一点非常重要。通常,这意味着将 CyanTrigger 中的事件设置为 Local。虽然存在控制哪个客户端执行操作的高级方法,但确保正确设置基础操作至关重要。“Send to Owner” 是一种门控方式。“Send to All” 事件不应用于设置同步变量,尤其是在设置同步变量时应避免使用。如果事件设置为 local,但由于其他原因仍进行同步,则应使用门控机制来限制只有一个客户端执行操作。例如,使用本地 OnTriggerEnter 事件检测 ObjectSynced 对象。虽然该事件是本地的,但每个客户端都会尝试执行它,因为每个客户端都会检测到 ObjectSynced 对象进入其触发器。在这种情况下,一种简单的门控方法是检查同步对象的所有者是否为本地客户端,并仅允许拥有者执行同步变量的设置。

对同步变量做出反应

当同步变量发生更改时,所有客户端都需要知道并处理这一变化,包括设置该值的客户端。有多种方法可以对同步变量的更改做出反应:

  • OnDeserialization
  • OnVariableChanged

这些方法仅用于了解同步变量何时发生更改。这取决于您的使用案例如何处理这些信息。对于这两种方法,请将它们视为同步事件。如果您计划从这些事件中更改另一个同步变量,则必须将其设限,以便只有所有者或一个特定玩家执行操作。

OnDeserialization

在 Udon 中,在设置新的网络数据后调用 OnDeserialization 事件。可以在此事件中处理变量更改,但无法知道哪些变量发生了更改。一种方法是处理所有变量的更改,而不检查具体更改的变量,但这效率较低且并非总有效。另一种方法是使用局部变量并比较同步变量的差异,以确定哪些变量发生了更改。OnVariableChanged 事件可以自动执行此操作,但需要权衡利弊。

请注意,OnDeserialization 仅由非拥有者客户端调用。如果使用 OnDeserialization,设置同步变量值的客户端也需要一种方法来处理值更改。可以在设置任何同步变量后手动调用 UdonBehaviour.SendCustomEvent 来调用 OnDeserialization。另一种选择是将 OnPostSerialization 事件与 OnDeserialization 事件一起使用,但这不会立即发生,因为它会等待所有同步变量数据发送完毕。

OnVariableChanged

每当变量值发生更改时,都会调用 OnVariableChanged 事件。这不仅限于同步变量,它适用于所有变量类型。请注意,当使用多个操作执行数学运算时,最好对操作使用多个变量,并且仅在最终操作中使用 OnVariableChanged 事件设置变量。如果在其他操作中使用此变量,它仍然会每次调用该事件,这可能会导致奇怪的结果,有时还会出错。

OnDeserialization 和 OnVariableChanged 的区别在于:OnDeserialization 保证所有同步变量都已更新其值;而 OnVariableChanged 只能保证该特定变量的值已更新,其他变量可能已更改但尚未更新。Udon 在调用 OnDeserialization 之前仍然需要设置变量的值,这意味着同步变量可以按任意顺序更改。当使用多个同步变量时,每个变量都有自己的 OnVariableChanged 事件,请确保只检查其自己的 OnVariableChanged 事件中的一个变量,而不检查其他同步变量。如果需要一起检查多个同步变量的某些逻辑,建议改用 OnDeserialization 事件。

同步开关示例

image.png

本指南也以视频教程的形式提供。

同步切换

使用同步变量,可以轻松地为您的世界创建同步的切换对象。通过与 OnVariableChanged 事件配对的同步变量,可以轻松处理变量在任何客户端上更新的情况。

SyncedToggleExample

在此示例中,您可以看到为所有玩家(包括迟到的加入者)同步切换所需的一切。这有三个部分,变量定义、修改变量、处理变量更改。
变量定义

在示例中,只有为此 CyanTrigger 定义了一个变量。这个变量称为 “value”。它是 bool 类型,并且设置为 sync。其中的一个重要部分是它被设置为对象的默认状态。在场景中,切换的对象已经处于活动状态,因此变量也应开始处于活动状态。否则,修改它的第一次交互将不会为玩家正确更改。

修改变量

在此示例中,我使用 Interact 事件来修改变量状态。这可以是任何事件,但 Interact 很容易演示。修改变量时,您必须是对象的所有者才能更改该值。考虑到这一点,Event 中的第一个操作是 Networking.SetOwner。这将使本地玩家成为对象的所有者,从而允许我们更改变量的值。然后,下一个操作将修改变量的值。在此示例中,我使用 bool 翻转 bool 值。UnaryNegation 并将其存储回同一个变量中。

处理变量更改

OnVariableChanged 是您应该用于处理变量值更改的事件。这将检查变量的值何时因本地操作或同步变量更改而发生更改。执行此 Event 时,变量中具有更新的值,可以将其用于其他操作。OnVariableChanged 事件还提供包含旧值的变量。在此示例中,我采用另一个名为 “Thing” 的对象,并将其活动状态设置为变量的值。

事件重播(Event Replay)

image.png

事件重播(Event Replay)是一种为延迟加入者同步事件的新方法。在任何具有“发送给所有人”广播的事件中,都可以选择事件重播选项,以确定当玩家稍后加入时,事件如何处理。事件重播(Event Replay)与 SDK2 缓冲不同,效率较低,建议优先使用同步变量。 注意:使用事件重播时,尤其是在事件依赖于未设置的变量或事件本身数据时,需谨慎处理潜在的计时问题。务必在多玩家环境下,并包含延迟加入者进行测试。

视频讲解

YouTube 上提供了 Event Replay 的视频解释和示例。

ReplayVideoTutorial

重播类型

None (无)

等同于不进行重播。延迟加入者不会接收到任何事件。

Replay Once (重播一次)

如果事件已触发,则保存重播数据,并为之后加入的用户重播一次。适用于一次性事件或在多状态系统中设置状态。

Replay Parity (重播奇偶校验)

如果事件已触发,则保存重播数据。如果事件触发奇数次,则重播一次;如果事件触发偶数次,则重播两次。适用于切换对象状态的事件。

Replay All (重播全部)

如果事件已触发,则保存重播数据,并在每次触发事件时重播该事件。适用于需要计数的事件。

清除重放操作

Clear Replay 操作将重置给定事件的所有重播数据。当多个事件使用重播并修改相同对象或属性时,必须使用 Clear Replay 操作来确保同步性。

image.png

在此示例中,有两个事件设置为 Replay Once (重播一次)。这两个事件都将修改相同的游戏对象:“Door/Open”和“Door/Closed”。因此,我们需要确保这些事件中只有一个会为延迟加入者重播,以保持所有内容同步。这就是 Clear Replay 操作的用武之地。在调用 “_Open” 时,我们知道不应该调用 “_Close”,并且重放数据被清除。在调用 “_Close” 时,我们知道不应该调用 “_Open”,并且重放数据被清除。

CyanTrigger 事件重播与 SDK2 缓冲的区别

CyanTrigger 事件重播与 SDK2 缓冲机制不同:

事件顺序

CyanTrigger 事件重播不保留事件的调用顺序,而 SDK2 缓冲机制会保留顺序。在 CyanTrigger 中,重播事件将始终按照其在触发器中定义的顺序播放。不同对象上的 CyanTrigger 的重播顺序可能每次都不同。

CyanTrigger 中的 Order 示例

image.png

SDK2 中的订购示例 VRC_Trigger

image.png

在此示例中,CyanTrigger 和 VRC_Trigger 似乎执行相同的操作。在 Interact 上,调用 custom 以禁用对象,然后调用 custom 以启用该对象。在 SDK2 中,GameObject 将始终处于启用状态,因为这是要调用的最后一个事件。在带有 Replay 的 CyanTrigger 中,将为房间中的用户启用 GameObject,但对于较晚加入的用户,将禁用该游戏对象。这是因为后期加入者将始终先重播 “Enable” 事件,然后再重播 “Disable” 事件,从而使对象处于禁用状态。这是使用 ClearReplay 操作准确确定延迟联接器会发生什么情况的地方。
对象启用状态

对于 CyanTrigger 事件重播,对象的启用状态很重要。SDK2 不关心对象或 VRC 触发器是否启用。要使 CyanTrigger 事件重播正常工作,必须在启动时启用 GameObject 和 UdonBehaviour。如果在启动时禁用了其中任何一个,则在启用该对象之前,用户不会对重放数据执行操作。这意味着,如果在启用对象之前触发重放事件,则实例中的用户将触发该事件,并在启用对象时重放该事件,从而过度计算该事件。