Networking 基础知识
摘要
——
1. 同步变量(Synced Variables)
2. 同步时机与生命周期(Sync Timing & Lifecycle)
3. 同步目标与网络事件(Sync Target & Network Events)
4. 所有权机制(Ownership)
5. 持久化机制(Persistence)
6. 网络组件(Network Components)
7. 反外挂处理规范(Anti-Cheat)
——
附录:官方文档链接
1. 同步变量(Synced Variables)
核心问题
"我要同步什么数据?"
1.1 支持的类型
| 类型分类 | 具体类型 | 额外说明 |
|---|---|---|
| 布尔 | bool |
|
| 文字 | char、string、VRCUrl |
字符串为 2 字节/字符;Continuous 模式下实际可同步字符数受 200 字节限制(≈100 字符),精确上限需实测 |
| 整数 | byte、sbyte、short、ushort、int、uint、long、ulong |
优先使用短类型节省带宽 |
| 浮点 | float、double |
|
| 数组 | 以上所有类型的数组 [] |
数组元素值改变不触发 OnVariableChanged |
| 元组 | Vector2、Vector3、Vector4、Quaternion |
位置、旋转、缩放常用 |
| 颜色 | Color、Color32 |
💡 说明:官方文档仅明确 "string 为 2 字节/字符",未给出 Continuous 模式下的确切字符数上限。126 字符为社区经验值,实际可同步量受序列化开销影响,建议保守使用或实测确认。
1.2 声明方式
基本声明
[UdonSynced]
private int MyInt;
带 FieldChangeCallback
[UdonSynced, FieldChangeCallback(nameof(SyncedToggle))]
private bool _syncedToggle;
public bool SyncedToggle
{
set
{
_syncedToggle = value;
// 值改变时的逻辑
}
get => _syncedToggle;
}
带插值模式
[UdonSynced(UdonSyncMode.Linear)] // 线性插值
private float _progress;
[UdonSynced(UdonSyncMode.Smooth)] // 平滑插值
private float _position;
| 插值模式 | 说明 |
|---|---|
None |
无插值,值改变时立即更新(默认) |
Linear |
线性插值 |
Smooth |
平滑插值 |
NotSynced |
不同步(默认,当不使用 [UdonSynced] 时) |
💡 用途:插值模式适用于 Continuous 同步模式,通过平滑过渡减少视觉跳变。适用于位置、进度条等高频变化但需要视觉流畅的数据。
1.3 SetProgramVariable 与 OnVariableChanged
核心机制:SetProgramVariable 会触发目标变量的 OnVariableChanged 事件。
这里的事件在UdonSharp的对应就是 FieldChangeCallback() 特性。
SetProgramVariable(variableName, value)
↓
触发 OnVariableChanged(variableName)
↓
输出:newValue(当前值)、oldValue(旧值)
特性说明:
| 特性 | 说明 |
|---|---|
| 触发条件 | 调用 SetProgramVariable 或 Set Variable(sendChange 勾选)时 |
| 触发时机 | 立即在变量被写入时触发,而非所有变量同步完成后 |
| 适用范围 | 非同步变量和同步变量均可 |
| 接收同步时触发 | 当从其他玩家接收同步变量时也会触发 |
| 输出参数 | newValue(设置后的值)、oldValue(设置前的值) |
注意:由于 OnVariableChanged 在变量写入时立即触发,而非所有同步变量更新完成后,因此在一个 OnVariableChanged 中读取另一个同步变量时,无法保证该变量已经是最新的同步值。如需在所有同步完成后执行逻辑,应使用 OnDeserialization。来源:Network Components - OnVariableChanged
应用场景:
- 监听其他脚本通过
SetProgramVariable修改的变量 - 在变量变化时执行联动逻辑
- 实现跨脚本的状态同步通知
1.4 数组行为注意
| 操作 | 是否触发 OnVariableChanged |
|---|---|
| 数组大小改变 | ✅ 触发 |
| 数组元素值改变 | ❌ 不触发 |
如需监听元素变化,需要手动比较
💡 来源说明:官方文档明确指出"changing the contents of an array does not trigger a change, because the array itself is still the same"。来源:Network Components - OnVariableChanged
2. 同步时机与生命周期(Sync Timing & Lifecycle)
核心问题
"什么时候触发同步?同步前中后发生了什么?"
2.1 四种同步模式对比
| 模式 | 触发方式 | 可靠性 | 速度 | 带宽 | 适用场景 |
|---|---|---|---|---|---|
| Continuous | 自动(Owner 值改变时) | 不可靠(可能丢包) | 快 | 低(全局 200 字节) | 位置、旋转等高频数据 |
| Manual | 手动(调用 RequestSerialization) | 可靠 | 较慢 | 高(单次可达 280,496 字节 ≈ 274KB) | 配置、状态等需要确认的数据 |
| Event | 手动(调用网络事件) | 可靠 | 慢 | 低 | 一次性事件、请求(详见第 3 节) |
| Auto | 自动(系统级) | 可靠 | 最快 | - | Avatar Transform、VRCObjectSync |
2.2 Continuous 模式特性
更新频率:
| 状态 | 更新频率 |
|---|---|
| 正常 | 20 Hz ~ 5 Hz(每 50ms ~ 200ms 更新一次) |
| 带宽阻塞时 | 可能降至 数秒一次(甚至更久) |
⚠️ 阻塞降速:当网络或带宽受限时,VRChat 会主动降低 Continuous 同步频率以避免进一步拥塞。这意味着高频数据在网络不佳时可能严重滞后。而且这一过程无法手动干涉。
其他特性:
- 不保证每次都发送(VRChat 可能跳过)
- 不保证到达(丢包不重发)
- 会触发 OnDeserialization(更新后的行为,旧版本不触发)
- 需要搭配本地插值来平滑跳变
⚠️ 兼容性说明:在某个版本更新后,Continuous 模式会触发 OnDeserialization。这是一个破坏兼容性的变更。如果代码依赖了"Continuous 不触发 OnDeserialization"的旧行为,需要注意调整。
TIPS:至于为什么没人感觉到了——因为以前的做法是在Update里面检查。
2.3 Manual 模式特性
- 需要显式调用
RequestSerialization() - 触发时序:OnPreSerialization → 发送 → OnPostSerialization
- 接收端:变量更新 → OnDeserialization
- 常用模式:Owner 调用 RequestSerialization,非 Owner 通过事件通知 Owner
2.4 Event 模式概述
- 通过网络事件触发,不涉及变量同步
- 支持四种目标:All / Others / Owner / Self
- 分为基础事件和带参事件两种方式
- 详细说明见第 3 节:同步目标与网络事件
2.5 Manual 同步完整时序
┌─────────────────────────────────────────────────────────────┐
│ Manual 同步时序 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Owner 端: │
│ │
│ 1. 调用 RequestSerialization() │
│ ↓ │
│ 2. OnPreSerialization() 触发 │
│ ↓ │
│ 3. 序列化所有 [UdonSynced] 变量 │
│ ↓ │
│ 4. 发送网络包 │
│ ↓ │
│ 5. OnPostSerialization(result) 触发,返回发送结果 │
│ │
├─────────────────────────────────────────────────────────────┤
│ │
│ Non-Owner 端: │
│ │
│ 1. 接收网络包 │
│ ↓ │
│ 2. 解包并更新所有 [UdonSynced] 变量 │
│ ↓ │
│ 3. OnDesSerialization() 触发 │
│ │
└─────────────────────────────────────────────────────────────┘
2.6 序列化事件使用场景
| 事件 | 触发时机 | 典型用途 |
|---|---|---|
OnPreSerialization() |
序列化前 | 额外数据打包、压缩 |
OnPostSerialization(result) |
序列化后 | 检查发送结果、处理失败 |
OnDeserialization() |
反序列化后 | 应用同步数据、通知其他脚本 |
OnDeserialization(DeserializationResult) |
反序列化后(含元数据) | 区分实时数据与存储恢复数据 |
💡 TIP:OnDeserialization(DeserializationResult result) 可通过 result.isFromStorage 判断数据来源——若为 true,则数据是从持久化存储恢复的,而非实时同步数据。可用于区分"新加入实例"与"从存储恢复"的场景。(不过一般都是依靠存储回复事件OnPlayerRestored来判断的)
2.7 注意事项
- Manual 同步可被任意玩家触发,但只有 Owner 执行打包
- Owner 调用 RequestSerialization 后可直接调用 OnDeserialization 保证本地一致性
- Continuous 模式会触发 OnDeserialization(更新后的行为)
- Late Joiner 自动同步:新玩家加入实例时会自动接收所有同步变量的最新状态,无需手动调用
RequestSerialization()(适用于 Manual 和 Continuous 两种模式)
💡 来源说明:官方文档明确 "Late joiners receive the latest state of the variable, just like other users in the instance. This works regardless of sync type, you do not need to manually call RequestSerialization when a user joins." 来源:Network Variables
3. 同步目标与网络事件(Target & Events)
核心问题
"同步发给谁?如何发送网络事件?"
3.1 NetworkEventTarget 四种目标
| 目标 | 说明 | 典型用法 |
|---|---|---|
All |
所有玩家(包括发送者) | 广播状态变更 |
Others |
除发送者外的所有玩家 | 本地操作后通知他人 |
Owner |
对象的所有者 | 请求-响应模式 |
Self |
仅发送者自己 | 绕过速率限制、本地触发 |
⚠️ Event Splitting 机制:当参数数据 > 1024 字节时,单次
SendCustomNetworkEvent会被拆分为多个内部事件,每个分片独立计入限速队列。例如配置限速为 2 events/s,但发送了 2048 字节数据,则实际速率仅为 1 event/s。(反复强调)
3.2 基础网络事件
// 触发无参网络事件
SendCustomNetworkEvent(NetworkEventTarget.All, "OnStateChanged");
- 方法:
SendCustomNetworkEvent(target, eventName) - 不支持参数传递
- 适用于简单的事件通知
注意:所有的事件只要没有在开头使用_,都会被认为是网络同步事件,这个在VRChat官方标记为<历史遗留兼容>
3.3 带参网络事件
// 触发带参网络事件
NetworkCalling.SendCustomNetworkEvent(
(IUdonEventReceiver)this, // 重要:必须指定目标 Behaviour 实例(UdonBehaviour替换This)
NetworkEventTarget.Owner, // 事件对象
"OnPlayerRequest", // 事件名称
playerId, // 参数
requestType
);
- 方法:
NetworkCalling.SendCustomNetworkEvent((IUdonEventReceiver)udonBehaviour, target, methodName, args...) - 支持最多 8 个参数
- 需要配合
[NetworkCallable]使用 - 必须包含目标 UdonBehaviour 实例作为第一个参数
3.4 NetworkCallable 声明
[NetworkCallable]
public void OnPlayerRequest(int playerId, int requestType)
{
// 处理来自其他玩家的请求
// 相当于"服务端函数"
}
注意事项:
- 方法需要
[NetworkCallable]标记 - 调用方使用
NetworkCalling.SendCustomNetworkEvent(target, "MethodName", args...) - Self 目标可绕过速率限制(热知识—UdonSharp本来就可以实现带参函数调用,你没必要这么做)
速率限制配置:
[NetworkCallable(maxEventsPerSecond: 10)]
public void OnPlayerRequest(int playerId, int requestType) { }
| 配置项 | 值 |
|---|---|
| 默认速率 | 5 events/s |
| 最大可配置速率 | 100 events/s |
⚠️ 重要提示:网络事件存在全局总限速(约 100 events/s,不可配置)。参数数据 > 1024 字节时会被拆分为多个内部事件(Event Splitting),每个分片独立计入限速。建议事件参数尽量精简。
3.5 请求-响应模式示例(伪中心化网络架构)
玩家 A Owner 玩家 B
│ │ │
│──Request(id=5)───────────>│ │
│ │ │
│ │ 处理请求 │
│ │ 更新同步变量 │
│ │ RequestSerialization() │
│ │ │
│<───同步状态更新─────────────│───同步状态更新─────────────>│
│ (OnDeserialization) │ (OnDeserialization) │
说明:
- 玩家 A 通过带参网络事件向 Owner 发送请求
- Owner 处理请求后更新同步变量,并调用
RequestSerialization() - 所有玩家(包括请求方 A)通过
OnDeserialization收到状态更新 - 不存在"对 A 响应、对 B 广播"的区别——同步变量对全员生效
4. 所有权机制(Ownership)
核心问题
"谁来主导这个过程?"
4.1 Owner 的职责
| 同步模式 | Owner 职责 |
|---|---|
| Continuous | 值改变时自动同步给其他玩家 |
| Manual | 当调用 RequestSerialization() 时打包并发送数据 |
| Event | 作为 Owner 目标的接收方 |
4.2 Networking 常用属性/方法
| API | 类型 | 说明 |
|---|---|---|
Networking.IsOwner(gameObject) |
属性 | 判断是否为 Owner |
Networking.SetOwner(player, gameObject) |
方法 | 请求转移 Owner |
Networking.IsMaster |
属性 | 是否为 Master(全局主持人) |
Networking.IsInstanceOwner |
属性 | 是否为实例创建者(社团实例永远返回 false) |
Networking.InstanceOwner |
属性 | 实例创建者的 Player API 对象(社团/Public 实例可能为 null) |
Networking.LocalPlayer |
属性 | 本地玩家(需要缓存,高延时) |
Networking.IsNetworkSettled |
属性 | 数据已准备好发送 |
Networking.IsClogging |
属性 | 是否超过带宽阈值 |
Networking.SimulationTime |
属性 | 最后更新时间,用于计算延迟 |
Networking.Master |
属性 | 当前 Master 的 Player API 对象(始终有效) |
NetworkCalling.CallingPlayer |
属性 | 当前网络调用的发起者(仅在网络调用上下文中有效) |
NetworkCalling.InNetworkCall |
属性 | 当前是否处于网络调用上下文中 |
SimulationTime 用途示例:
// 计算玩家网络延迟
float latency = Time.realtimeSinceStartup - Networking.LocalPlayer.SimulationTime;
💡 InstanceOwner vs IsInstanceOwner:两者均针对实例创建者,但
IsInstanceOwner返回 bool,InstanceOwner返回 Player 对象。社团实例(Group)和 Public 实例中IsInstanceOwner永远返回 false,InstanceOwner始终为 null。
4.3 Owner 转移流程
请求方 当前 Owner
│
┌─ OnOwnershipRequest() ─┐
│ 返回 true → 允许转移 │
│ 返回 false → 拒绝转移 │
└────────────────────────┘│
│──── SetOwner() ─────────>│
│ │
│ ┌─ OnOwnershipRequest() ─┐
│ │ 返回 true → 允许转移 │
│ │ 返回 false → 拒绝转移 │
│ └───────────────────────┘
│ │
│<─── OnOwnershipTransferred ──│
│ │
4.4 OnOwnershipRequest 完整流程
以下流程适用于脚本定义了 OnOwnershipRequest 的情况。未定义时,默认允许所有转移。
请求方 当前 Owner
│ │
┌─ OnOwnershipRequest() ─┐ │
│ 请求方执行 │ │
│ 返回 true → 继续 │ │
│ 返回 false → 转移取消 │ │
└───────────────────────┘ │
│──── SetOwner() ─────────>│
│ │
│ ┌─ OnOwnershipRequest() ─┐
│ │ 当前 Owner 执行 │
│ │ 返回 true → 接受 │
│ │ 返回 false → 拒绝 │
│ └───────────────────────┘
│ │
│ │
│<── OnOwnershipTransferred ─│
│ (原 Owner + 其他玩家) │
关键说明:
- 请求方返回
true才继续进入后续流程;返回false时整个转移在此时取消,Owner 的 OnOwnershipRequest 不会被调用 - Owner 返回
false或未返回值时,转移被拒绝,请求方收到 OnOwnershipTransferred 但 Owner 不变 - Owner 转移给他人时,跳过第 4 步(被转移方不能拒绝)
- 预确认特性:请求方在 Owner 最终确认之前就会收到 OnOwnershipTransferred,此时转移可能因 Owner 拒绝而回滚
- 拒绝转移时:请求方仍会收到
OnOwnershipTransferred,但此时原 Owner 仍是所有者
SetOwner事件的实际速率 = LocalPlayer网络延迟 + Owner网络延迟 + OnOwnershipRequest
在SetOwner结束之前,整个UdonVM会进入阻塞状态。暂时不知道阻塞状态影响的是整个游戏线程还是单个Udon。
5. 持久化机制(Persistence)
核心问题
"如何跨会话保存数据?"
5.1 两种持久化方案对比
| 方案 | 组件 | 数据位置 | 访问方式 | 容量限制 |
|---|---|---|---|---|
| 脚本持久化 | VRCEnablePersistence | 挂载的脚本 | 脚本内部 | 100KB/玩家(PlayerObject,可压缩存储) |
| 数据库持久化 | PlayerData | 全局键值对 | 任意脚本 | 100KB/玩家(PlayerData,可压缩存储) |
💡 压缩说明:VRChat 使用压缩格式存储数据。如果数据易于压缩,实际可存储量可能超过 100KB(被压缩至 100KB)。
5.2 脚本持久化(VRCEnablePersistence)
- 配合
VRCPlayerObject使用效果最佳 - 持久化数据必须是同步变量
- 进入实例时自动恢复
- 需要在 OnPlayerLeft 之前保存
5.3 数据库持久化(PlayerData)
// 存储
PlayerData.SetString("key", "value");
// 读取
string value = PlayerData.GetString("key");
// 事件
OnPlayerDataUpdated() // 数据更新时触发
OnPlayerRestored() // 数据恢复完成时触发
PlayerData因为其以下特性也常被当作全局数据库使用:
1:无需引用
2:标准化读取
3:标准更新事件
4:可以跨玩家读取(其他玩家可读数据)
5.4 PlayerObject vs PlayerData
| 选择依据 | 选 PlayerObject | 选 PlayerData |
|---|---|---|
| 数据结构 | 复杂、多个字段 | 简单、键值对 |
| 访问范围 | 脚本内部 | 全局可访问 |
| 多实例 | 每个玩家独立 | 共享 |
| 持久化需求 | 高 | 一般 |
6. 网络组件(Network Components)
核心问题
"有哪些现成的网络组件可用?"
6.1 VRC Object Sync
用途:自动同步 Transform 和 Rigidbody,不占用 Udon 带宽。
| 功能 | 说明 |
|---|---|
| FlagDiscontinuity | 禁用平滑同步,运动不连续 |
| Set/Get Gravity | 同步重力值(非 Owner 必须从组件获取) |
| Set/Get Kinematic | 同步运动学状态 |
| Respawn() | 重置到初始位置,清除速度 |
Respawn() 详细行为:
- 设置
DiscontinuityHint = true,使后续变化立即生效 - 设置
transform.position→ 初始位置 - 设置
transform.rotation→ 初始旋转 - 如有 Rigidbody:同时设置
velocity和angularVelocity为Vector3.zero,并设置rigidbody.position和rigidbody.rotation
⚠️ 注意:
- 同一物体的 Udon 组件必须使用 Continuous 模式
- 多组件降级规则:如果同一个 GameObject 上有多个 UdonBehaviour,其中一个使用 Manual,另一个使用 Continuous,则两者都会被降级为 Manual 模式工作
6.2 VRC Object Pool
用途:对象池管理,自动同步启用状态。
| 方法 | 说明 |
|---|---|
TryToSpawn() |
尝试启用一个对象,返回对象或 Null |
Return() |
禁用对象(仅 Owner 可调用) |
6.3 VRCPlayerObject
用途:为每个玩家生成独立的 GameObject 副本。
- Owner 固定为对应玩家,不可转移
- 可使用带参事件与 Owner 通信
- 使用 VRChat 保留的 NetworkID,不占用同步带宽
7. 反外挂处理规范
核心问题
"如何保护网络同步不被外挂干扰?"
本段方法未验证可行性,里面涉及的方法是根据外挂客户端的一些行为进行针对性处理的方案,不保证任何效果。其中部分来自于VRChat官方推荐方案,部分来自于UdonVM特性处理。使用时注意辨别。
7.1 网络事件命名规范
原则:区分网络用途和本地用途的 Public 事件。
| 事件类型 | 命名规则 | 示例 |
|---|---|---|
| 网络用途 | 普通命名 | OnPlayerJoin()、RequestSync() |
| 本地用途 | 下划线前缀 _ |
_OnButtonClick()、_UpdateState() |
原因:VRChat 外挂通过扫描 UdonCache 中的 Public 方法列表来发现可调用事件。添加 _ 前缀可以让外挂无法通过简单的枚举找到本地事件。
示例:
// ✅ 网络事件(可被外部调用)
public void OnPlayerJoin(VRCPlayerApi player) { }
// ❌ 本地事件(不应被外挂发现)
public void _OnInternalUpdate() { }
7.2 同步来源验证
背景:对于 Requst 发送的参数同步,其调用者身份可通过 Networking.GetOwner(gameObject) 获取。但对于网络事件(NetworkCalling.SendCustomNetworkEvent),应使用 NetworkCalling.CallingPlayer 检测真实调用者。
两种检测场景对比:
| 场景 | 推荐 API | 说明 |
|---|---|---|
序列化(RequestSerialization()) |
Networking.GetOwner(gameObject) |
发送方应为对象 Owner |
| 带参网络事件(NetworkCallable) | NetworkCalling.CallingPlayer |
获取真实调用者 Player 对象 |
示例:
[NetworkCallable]
public void OnPlayerAction(int playerId, int actionType)
{
// 使用 NetworkCalling.CallingPlayer 获取真实调用者
VRCPlayerApi sender = NetworkCalling.CallingPlayer;
if (sender == null || sender != VRCPlayerApi.GetPlayerById(playerId))
{
// 来自伪造请求,直接忽略
return;
}
// 正常处理请求...
}
⚠️ 安全提示:建议在带参网络同步操作中增加使用 playerId 参数 ,在远端使用 VRCPlayerApi.GetPlayerById() 和NetworkCalling.CallingPlayer做二次验证,可以验证发送者是否篡改了发送信息。
7.3 使用 BehaviourSyncMode.None
原则:本地优先的脚本使用 None 同步模式。
[UdonBehaviourSyncMode(BehaviourSyncMode.None)]
public class LocalUIController : UdonSharpBehaviour
{
// 本地 UI 逻辑
// 没有网络 ID,外挂无法通过网络操作此脚本
}
None vs NoVariableSync 区别:
| 模式 | 同步变量 | 网络事件(SendCustomNetworkEvent) | 适用场景 |
|---|---|---|---|
None |
❌ 无 | ❌ 不工作 | 纯本地逻辑 |
NoVariableSync |
❌ 无 | ✅ 可用(可与 Manual/Continuous 共存) | UI 控制器、事件转发 |
None优势:
- 脚本没有网络 ID,外挂客户端无法通过网络访问
- 本地只有在实际交互后才会在 UdonCache 中出现
- 适合 UI 控制器、本地状态管理
适用场景:
- ✅ UI 面板
- ✅ 本地设置
- ✅ 临时状态
不适用场景:
- ❌ 需要网络同步的功能
- ❌ 需要多玩家协作的功能
这种情况可以由另一个脚本作为中继实现
7.4 SetProgramVariable 类型混淆保护
原理:Udon 的 SetProgramVariable 不进行类型检查,可以通过设置错误类型来创建"陷阱变量"。
实现方案:
public class SecureController : UdonSharpBehaviour
{
private void Start()
{
// 初始化:将 错误类型的值存入变量
SetProgramVariable("_security_tag", "init_string_value");
}
// 所有 Public 方法的第一行进行检测,正确调用时先修复问题,再调用
public void OnPlayerInteract()
{
// 检测陷阱变量是否被正确设置
if (!(GetProgramVariable("_security_tag") is string))
{
return; // 安全检查失败,忽略请求
}
// 正常处理...
}
// 在所有正常流程中,定期重置标志位
public void _InternalUpdate()
{
// 重置为错误的类型和值
SetProgramVariable("_security_tag", "valid_state");
}
}
原理说明:
| 步骤 | 说明 |
|---|---|
| 1. 初始化 | 存储一个类型为 A 的变量(如 string) |
| 2. 正常流程 | 使用时先将变量设置为类型 A 的正确值 |
| 3. 外挂调用 | 外挂直接调用 |
| 4. 检测 | Public 方法检查类型,发现不匹配则拒绝执行/立即崩溃 |
⚠️ 注意:此方法会导致正常玩家触发时 Udon 崩溃,因此仅用于保护其他玩家的体验,而非防止作弊者本身。
如果不想要大规模重写的话,也可以选择给所有脚本增加一段炸弹代码,只要作弊客户端调用,秒炸Udon。
因为Udon本身是线性执行的,可以一定程度防止更大规模的问题出现。
public class SecureController : UdonSharpBehaviour
{
private void Start()
{
// 初始化:将 错误类型的值存入变量
SetProgramVariable("_security_tag", "init_String_value");
}
// 所有 Public 方法的第一行进行检测,正确调用时先修复问题,再调用
public void ABoom()
{
// 检测陷阱变量是否被正确设置
_security_tag = _security_tag + _security_tag;
}
}
7.5 综合安全策略
| 层级 | 措施 | 防护目标 |
|---|---|---|
| 命名层 | _ 前缀命名本地事件 |
防止外挂发现事件 |
| 来源层 | PlayerId 验证 | 防止伪造发包者身份 |
| 访问层 | BehaviourSyncMode.None | 防止网络访问本地脚本 |
| 调用层 | 类型混淆检测 | 防止非正常途径调用 |
No comments to display
No comments to display