网络事件
网络事件允许在脚本之间进行简单的单向网络通信。当脚本执行网络事件时,它会为当前在实例中的目标玩家执行一次该事件。
网络事件不会为后来加入的玩家重复执行,因此它们最适合仅适用于短时间的临时动作,例如装饰性效果。不要将网络事件用于对后来加入者重要的逻辑或 状态更改。相反,请使用网络变量。
定义事件
要声明一个事件,你必须给它一个名称。每个 Udon behaviour 的名称必须唯一,但可以在不同的 behaviour 之间重复使用。名称由 Udon Graph 事件节点上的文本字段或 UdonSharp 方法定义决定,区分大小写。在 UdonSharp 中,使用 nameof 让 IDE 为你检查。
当使用 SendCustomNetworkEvent 调用事件时,事件名称决定要执行的方法。
要允许方法或图事件节点远程执行,它必须满足下面列出的规则。对于 Graph 和 UdonSharp,接收端 UdonBehaviour 不能使用同步模式 None。
- Udon Graph
- UdonSharp
要让 Udon Graph 自定义事件节点可被网络调用,必须满足以下要求:
- 自定义事件的名称不能以下划线
_开头。 - 事件节点必须有活跃的 Flow 连接。
调用事件
要触发网络事件,请使用 UdonBehaviour 和 UdonSharpBehaviour 上的 SendCustomNetworkEvent 方法,或通过 NetworkCalling.SendCustomNetworkEvent 显式调用。它们是相同的,仅为兼容性而提供。
您调用时针对的 UdonBehaviour 可以与执行 SendCustomNetworkEvent 的不同。可以针对已禁用的 behaviour。
示例
按照以下示例为房间实例中的玩家触发自定义网络事件:
- Udon Graph
- UdonSharp

- 确保你的 UdonBehaviour 的同步模式设置为"Continuous"或"Manual",而不是"None"。
- 创建一个"Event Custom"节点。
- 使用其输入框为此节点指定唯一名称。
- 添加一个"Send Custom Network Event Node"节点。
- 在
eventName下拉菜单中,选择你分配给事件的名称。你也可以附加一个字符串流来动态选择事件名称,或从其他 behaviour 输入名称。 - 保留默认的
All作为目标,以对房间中的每个玩家触发此事件,或将其更改为不同选项。 - 你可以将
instance输入留空以针对当前 UdonBehaviour,或连接对另一个 UdonBehaviour 的引用以在该 behaviour 上触发自定义事件。
using UdonSharp;
using VRC.SDK3.UdonNetworkCalling;
using VRC.Udon.Common.Interfaces;
public class SyncedHelloOnInteract : UdonSharpBehaviour
{
public override void Interact()
{
SendCustomNetworkEvent(NetworkEventTarget.All, nameof(SayHello));
}
[NetworkCallable]
public void SayHello()
{
Debug.Log("Hello, World!");
}
}
请注意,你的 Udon 代码不会等待这些事件在远程玩家上被调用——它只是发送它们然后立即继续执行。然而,如果事件也将被发送者自己接收,它会在继续之前在本地触发,就像常规函数调用一样。
事件的发送和接收顺序是有保证的,除非你达到了自己定义的速率限制。如果你发送事件 A 后跟事件 B,接收端也会按 A, B 的顺序接收它们。此保证适用于场景中的所有 behaviour,但不适用于多个玩家同时发送事件的情况。
但是,如果一个事件被 [NetworkCallable(maxEventsPerSecond: X)] 特性(不包括 VRChat 内部速率或吞吐量限制!)限速,它不会阻止其他队列的处理。例如,如果事件 A 限制为每秒 3 次,而你一次性发送了五个事件:A1, A2, A3, A4, B1——那么它们将按 A1, A2, A3, B1, A4 的顺序到达,因为 A4 将触发速率限制,因此事件 B1 可以"插队"。
事件目标
网络事件始终针对实例中的一个或多个玩家。你可以选择以下目标:
| 目标 | 描述 |
|---|---|
NetworkEventTarget.All | 实例中的所有玩家都接收事件。 |
NetworkEventTarget.Others | 实例中除本地玩家外的所有玩家都接收事件。 |
NetworkEventTarget.Owner | 对象的拥有者接收事件。 |
NetworkEventTarget.Self | "环回"目标。仅发送玩家接收事件。这将绕过所有速率限制,因为它实际上从未通过网络发送。 |
如果本地玩家向自己发送网络事件,他们会立即执行它。例如,NetworkEventTarget.All 向所有其他玩家发送网络事件,但本地玩家立即执行该事件而无需等待。
要向特定玩家发送事件,你可以将目标玩家的 playerId 作为参数包含在内,仅当接收到的 ID 与本地玩家匹配时才执行事件。在这种情况下使用 NetworkEventTarget.All,或为本地触发事件添加特殊情况。
带参数发送事件
网络事件最多可以携带八个参数。每个参数必须是可同步变量类型(int、string、float、bool 等),在 UdonSharp 中,接收方法必须标记为 [NetworkCallable]。
- Udon Graph
- UdonSharp
在 Udon Graph 中创建自定义事件时,你可以选择它应具有的参数数量。默认变体没有参数。你可以稍后使用节点上的下拉菜单更改参数数量。
在带参数的节点上,你可以在左侧的下拉菜单中选择类型。输出数据端口将自动更改类型。
要从"Send Custom Network Event"节点发送参数,请从下拉菜单中选择具有正确参数数量的重载。
例如,这是一个接收字符串和整数的简单图:

你可以像在 UdonSharp 中通常那样声明带参数的网络可调用函数。
using UdonSharp;
using UnityEngine;
using VRC.SDK3.UdonNetworkCalling;
using VRC.Udon.Common.Interfaces;
public class EventParameterExample : UdonSharpBehaviour
{
public override void Interact()
{
NetworkCalling.SendCustomNetworkEvent(this, NetworkEventTarget.All, nameof(PrintMessage), "VRCat", 11);
// or: this.SendCustomNetworkEvent(NetworkEventTarget.All, nameof(PrintMessage), "VRCat", 11);
}
[NetworkCallable]
public void PrintMessage(string name, int age)
{
Debug.Log($"Congratulations on your {age}th birthday, {name}!");
}
}
你需要确保传递给 SendCustomNetworkEvent 的参数类型与你在事件上声明的类型匹配,否则发送将失败!
当将 null 作为输入传递给 SendCustomNetworkEvent 时,接收端调用的方法将接收 default(T),其中 T 是方法签名中声明的参数类型。这意味着可空类型将接收 null,而不可空类型将接收默认值(例如,作为 null 发送的 int 参数将接收 0,即 default(int))。
参数大小限制和事件拆分
一般来说,保持参数大小最小化,避免发送大型对象或复杂结构以避免问题。存在一些硬性限制:
- 单个网络事件中所有参数的总大小不能超过 16 KB。
- 单个参数的大小不受限制,但受事件总大小的限制。
- 总传出数据的硬上限约为 18 KB/s。但这包括所有网络开销,因此实际上你不太可能看到超过 8-10 KB/s。
此外,还有两个级别的限制可能导致事件延迟:
- 基于吞吐量的限制——这适用于任何时候正在处理的网络数据总量,遵循与常规 Udon 同步相同的规则。
- 用户配置的速率限制——这控制每秒可以发送网络事件的次数,可以使用
[NetworkCallable(maxEventsPerSecond: X)]特性进行调整。
如果你发送包含超过 1024 字节(1 KB)参数数据的事件,它将在内部被拆分为多个事件。这个过程对 Udon 几乎透明,因为接收端会重新组装事件,并仅在目标 UdonBehaviour 上调用一次。
然而,这些内部事件在速率限制队列和 Get(All)QueuedEvents 函数中是可见的。例如,如果你将速率限制配置为"每秒 2 个事件",但使用 2048 字节数据调用 SendCustomNetworkEvent,那么有效的允许速率将仅为每秒 1 个事件调用。这是因为在 2048 字节时,单个自定义网络事件会变成 2 个内部事件。
"参数大小"指的是参数数据将被编码成的字节数。这不包括内部标头或你无法控制的其他开销。以下是一些示例:
// fits into 1 internal event:
int x = 0; // = 4 bytes, sizeof(int)
Vector3 v = Vector3.zero; // = 12 bytes, sizeof(float) * 3
new string('x', 400); // = 400 bytes, UTF-8 encoded
"うどんは美味しい"; // = 24 bytes, UTF-8 encoded with non-ASCII characters
new char[128]; // = 256 bytes, UTF-16 (following C# spec)
new byte[1024]; // = 1024 bytes
// requires more than 1 internal event:
new byte[1025]; // = 1025 bytes, 2 events sent
new byte[16 * 1024] // = 16384 bytes, 16 events sent, maximum allowed size
new int[512]; // = 2048 bytes, sizeof(int) * 512, 2 events sent
// string[] and VRCUrl[] are special cases:
new string[2] { "test", "foobar" }; // = 18 bytes, 4 + 6 from UTF-8 encoded strings, 8 additional for a length value per array entry ( 2 * sizeof(int) )
速率限制
网络事件被限速以防止过度使用:
- 默认速率:每秒 5 个事件
- 最大可配置速率:每秒 100 个事件
要修改速率限制,请使用 [NetworkCallable(maxEventsPerSecond: X)] 特性。X 可以是 1 到 100 之间的任意整数(包含两端)。
在 Udon Graph 中,只需在节点上的相应输入字段中设置所需的值。请注意,对于无参数的事件,你可以将其设置为 0 以使其成为旧版事件。
此参数指定事件发送的速度。它以"每秒事件数"给出,例如值 5 表示"最大每秒 5 个事件"。就速率限制而言,一次 SendCustomNetworkEvent 调用可能发出多个事件,请参见事件拆分!
所有速率限制都是尽力而为的,可能无法根据本地性能、网络利用率或服务器负载精确匹配配置或指定的值。
此限制的存在是为了让你将其用作安全措施!强烈建议将此值设置得尽可能低,以减轻恶意行为者滥用事件在世界中造成问题。
请注意,此速率限制在发送客户端和服务器端都执行。在常规使用中,服务器端仅用于防止恶意用户,通常不可见。本地客户端行为是排队,意味着如果在短时间内发送太多事件,它们将被排队,直到速率限制允许它们发送。排队的消息数量没有限制,所以要注意不要无限地快速发送消息,因为那将导致世界中所有 Udon 网络出现问题。
由于事件对本地玩家立即执行,在这种情况下不应用速率限制。这意味着事件可能在本地执行后,在远程玩家的队列中排队。
最后,请注意传出事件也有全局总体限制,目前也约为每秒 100 个事件。此限制是动态的且不可配置——它可能随时更改,但 VRChat 会在重大减少之前进行沟通。此限制的适用方式与可配置的限制相同,事件将被排队。
拥塞监控
NetworkCalling 中的以下函数可用于处理速率限制:
| 函数 | 描述 |
|---|---|
int NetworkCalling.GetQueuedEvents(udonBehaviour, eventName) | 返回当前排队等待发送的事件数量。在正常操作中,此数字将在 0 和你配置的速率限制之间。如果超过你的速率限制,你发送事件太快,它们将被排队,直到速率限制允许它们被处理。 |
int NetworkCalling.GetAllQueuedEvents() | 返回整个世界中排队的事件数量。大于 0 的数字并不自动表示网络状况不佳,因为在低负载下事件也可能短时间排队。 |
你也可以使用整体 Networking.IsClogged 属性来确定网络状况。这可能会受到过多事件发送的影响。
例如:
using TMPro;
using UdonSharp;
using UnityEngine;
using VRC.SDK3.UdonNetworkCalling;
using VRC.Udon.Common.Interfaces;
public class EventQueueExample : UdonSharpBehaviour
{
[SerializeField] private TextMeshProUGUI queueStatus;
void Update()
{
queueStatus.text = $"队列: {NetworkCalling.GetAllQueuedEvents()}";
queueStatus.text += $"\n特定事件队列: {NetworkCalling.GetQueuedEvents(this, "SomeNetworkEvent")}";
queueStatus.text += $"\n阻塞: {Networking.IsClogged}";
}
// 一些网络事件...
}
同一实例中不匹配的世界版本
作为一个非常特殊的边缘情况,有一种情况服务器端速率限制可能会主动丢弃事件而无需恶意操作。由于速率限制是基于每个客户端对世界的本地视图应用的,如果你上传了具有降低速率限制的世界新版本,并且同一实例中的用户分布在不同的世界版本中,发送客户端可能会超出接收客户端的速率限制预期。在这种情况下,且仅在这种情况下,服务器可能会静默丢弃事件并不进行传递。
访问事件的发送者
NetworkCalling 类有一些处理事件的有用属性:
| 属性 | 描述 |
|---|---|
VRCPlayerApi NetworkCalling.CallingPlayer | 发起此网络调用的玩家的 VRCPlayerApi。如果不在网络调用中则为 null。 |
bool NetworkCalling.InNetworkCall | 指示当前行是否作为网络调用的一部分执行。请注意,这仅在入口函数终止时重置,即从事件入口点调用辅助脚本或不同函数将保持此状态。 |
旧版事件与安全
[NetworkCallable] 在 SDK 3.8.1 中引入——Udon 以前允许调用任何公共方法,除非它以下划 线开头,如 _MethodName。为了向后兼容,你仍然可以调用任何不以开头的无参数公共方法,但不建议这样做。向带有下划线的方法添加 [NetworkCallable] 特性将允许它通过网络调用。
在 Udon Graph 中,如果 MaxEventsPerSecond 输入字段设置为 0,则自定义事件节点被视为"旧版"。
为防止公共方法或图事件被通过网络调用,你应该使用以下划线 _ 开头的名称。这可以提高世界或预制体的安全性。
组件索引目标
关于旧版事件的额外特别说明,考虑向 GameObject 发送事件与向对象上的特定 Component 发送事件的语义差异。
通过 [NetworkCallable] 标记的函数将使用 Component 定位语义进行调用。这意味着即使一个 GameObject 上有 2 个或更多 UdonBehaviour,也只有指定的 UdonBehaviour 会接收到该事件。
传统方式——即向_未_标记 [NetworkCallable] 的函数发送无参数事件——将使用 GameObject 语义。也就是说,它类似于 Unity 内置的 GameObject.SendMessage,将在目标 behaviour 所在对象上的_所有_ UdonBehaviour 上调用该函数。
注意,Component 定位依赖于组件索引(顺序),因此不建议在使用了网络化 UdonBehaviour 的 GameObject 上使用 Destroy 销毁组件。这样做将导致未定义行为。