关于 multiplayer-sync
面向 Godot 4.3+ 开发者的多人同步技能,讲解 MultiplayerSynchronizer 的属性复制、插值和带宽优化。还介绍客户端预测、延迟补偿,以及同步状态与输入的取舍。
主要功能
- 01MultiplayerSynchronizer 属性复制
- 02全量同步与差异同步
- 03远程状态插值
- 04客户端预测与延迟补偿
- 05带宽优化与同步模式选择
适用场景
- 01同步玩家位置、速度和生命值
- 02减少远程玩家画面跳变
- 03为快节奏游戏实现预测与命中验证
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jame581/ godotprompter --skill multiplayer-sync安装后在 Claude Code 中输入/multiplayer-sync确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/bandwidth-optimization.mdclient-prediction.mdinterpolation.mdlag-compensation.md详细介绍
Godot 4.3+ 中的多人游戏同步
所有示例均适用于 Godot 4.3+,且不使用已弃用的 API。先展示 GDScript,再展示 C#。
相关技能: multiplayer-basics 介绍 ENet 设置、RPC 和权限模型,dedicated-server 介绍无头导出和部署,physics-system 介绍物理插值和 RigidBody 同步。
1. MultiplayerSynchronizer
MultiplayerSynchronizer 是 Godot 内置的节点,用于通过网络复制属性。将其添加为要共享状态的节点的子节点。
功能
- 按配置的间隔将属性值从权威对等方发送给所有其他对等方
- 同时支持增量同步(“On Change”属性,发生变化时可靠发送)和完整同步(“Always”属性,每个间隔不可靠发送)
- 支持使用可见性筛选器控制哪些对等方会收到更新
在编辑器中配置复制
- 在场景树中选择
MultiplayerSynchronizer节点。 - 在检查器中打开 Replication,然后点击 Add Property。
- 选择父节点路径和属性名称(例如
position、velocity)。 - 为每个属性设置 Sync(每个间隔发送)或 Spawn(仅在生成时发送)。
- 设置 Replication Interval(秒)。
0表示每个网络处理帧都发送(多人游戏 API 每个处理帧轮询一次,而非每个物理帧)。
关键属性
| 属性 | 说明 |
|---|---|
replication_interval | 完整同步更新之间的秒数。0(默认值)= 每个网络处理帧 |
delta_interval | 增量同步更新之间的秒数。0(默认值)= 每个网络处理帧 |
public_visibility | 为 true 时,更新会发送给所有对等方(默认值) |
add_visibility_filter(callable) | 方法,而非属性:添加一个接收对等方 ID 并在该对等方应接收更新时返回 true 的 Callable |
增量同步与完整同步
| 模式 | 工作方式 | 最适用场景 |
|---|---|---|
| 完整同步 | 每隔 replication_interval 不可靠地发送每个 “Always” 属性 | 持续变化的值(位置、速度) |
| 增量同步 | 每隔 delta_interval 可靠地发送发生变化的 “On Change” 属性 | 很少变化但必须送达的值(生命值、状态) |
每个属性只使用一种模式,因此这两个间隔分别控制不同的属性集合——replication_interval 不会刷新 “On Change” 属性的完整状态。
可见性筛选器(GDScript)
# Only send updates to peers within 500 units of this object.
func _ready() -> void:
$MultiplayerSynchronizer.add_visibility_filter(_is_peer_in_range)
func _is_peer_in_range(peer_id: int) -> bool:
var peer_player := _get_player_node(peer_id)
if peer_player == null:
return false
return global_position.distance_to(peer_player.global_position) <= 500.0
可见性筛选器(C#)
// Only send updates to peers within 500 units of this object.
public override void _Ready()
{
var sync = GetNode<MultiplayerSynchronizer>("MultiplayerSynchronizer");
sync.AddVisibilityFilter(Callable.From<int, bool>(IsPeerInRange));
}
private bool IsPeerInRange(int peerId)
{
var peerPlayer = GetPlayerNode(peerId);
if (peerPlayer is null)
return false;
return GlobalPosition.DistanceTo(peerPlayer.GlobalPosition) <= 500.0f;
}
2. 属性同步
要同步的内容
只同步在远程对等方上重建画面所需的最少状态。常见属性如下:
| 属性 | 类型 | 说明 |
|---|---|---|
position | Vector2 / Vector3 | 核心变换——每帧同步,或使用插值 |
velocity | Vector2 / Vector3 | 有助于远程预测跟上位置跳变 |
health | int / float | 发生变化时可靠同步;适合使用增量同步 |
animation_state | String / int | 发生变化时同步;使用枚举整数可节省带宽 |
is_crouching | bool | 变化较少的布尔值;可使用增量同步或在变化时调用 RPC |
已同步的玩家(GDScript)
# synced_player.gd
extends CharacterBody2D
## Sync interval in seconds — exposed so designers can tune per object type.
@export var sync_interval: float = 0.05 # 20 Hz
@export var speed: float = 200.0
# These properties are listed in the MultiplayerSynchronizer replication config.
var synced_position: Vector2 = Vector2.ZERO
var synced_velocity: Vector2 = Vector2.ZERO
var synced_health: int = 100
var synced_anim: int = 0 # 0 = idle, 1 = run, 2 = jump
@onready var _sync: MultiplayerSynchronizer = $MultiplayerSynchronizer
func _ready() -> void:
_sync.replication_interval = sync_interval
# Only the authority (owner) drives movement.
set_physics_process(is_multiplayer_authority())
func _physics_process(_delta: float) -> void:
# Authority: write canonical state so MultiplayerSynchronizer can replicate it.
synced_position = global_position
synced_velocity = velocity
synced_anim = _compute_anim_state()
已同步的玩家(C#)
// SyncedPlayer.cs
using Godot;
public partial class SyncedPlayer : CharacterBody2D
{
/// <summary>Sync interval in seconds. Exposed so designers can tune per object type.</summary>
[Export] public float SyncInterval { get; set; } = 0.05f; // 20 Hz
[Export] public float Speed { get; set; } = 200.0f;
// These properties are listed in the MultiplayerSynchronizer replication config.
public Vector2 SyncedPosition { get; set; } = Vector2.Zero;
public Vector2 SyncedVelocity { get; set; } = Vector2.Zero;
public int SyncedHealth { get; set; } = 100;
public int SyncedAnim { get; set; } = 0; // 0=idle, 1=run, 2=jump
private MultiplayerSynchronizer _sync = null!;
public override void _Ready()
{
_sync = GetNode<MultiplayerSynchronizer>("MultiplayerSynchronizer");
_sync.ReplicationInterval = SyncInterval;
SetPhysicsProcess(IsMultiplayerAuthority());
}
public override void _PhysicsProcess(double delta)
{
// Authority: write canonical state for replication.
SyncedPosition = GlobalPosition;
SyncedVelocity = Velocity;
SyncedAnim = ComputeAnimState();
}
private int ComputeAnimState()
{
if (!IsOnFloor()) return 2;
return Velocity.Length() > 1f ? 1 : 0;
}
}
3. 插值
MultiplayerSynchronizer 会按同步间隔(例如 30 Hz)更新目标属性,但渲染以帧率(60+ Hz)运行。如果不使用插值,远程玩家看起来会在快照之间瞬移。解决方法:每次收到 synchronized 信号时保存前一个位置和新位置,然后在 _process 中根据更新后经过的时间除以同步间隔,在两者之间进行线性插值;使用带时间戳的快照缓冲区(渲染时间落后约 100 ms)是更稳健的做法。
完整的 GDScript 和 C# 前一帧/当前快照插值示例(
synchronized信号、在_process中按经过时间进行线性插值),请参阅 references/interpolation.md。
4. 客户端预测
为了提升本地玩家的响应速度:在客户端立即预测移动,将输入发送到服务器,并在收到服务器快照时进行校正。如果服务器状态与客户端预测的差异超过阈值,则立即跳转到服务器状态;否则,在 100-200 ms 内平滑插值修正。
完整的预测与校正模式(输入环形缓冲区、服务器校正、重放)见 references/client-prediction.md,其中包含 GDScript + C# 示例。
5. 延迟补偿
对于快节奏游戏中的射线检测武器:服务器验证命中时,会将世界状态回溯至客户端的视图时间(now - client_rtt/2 - interp_delay),并在该历史状态下检测命中。
快照历史模式、视图时间计算以及 GDScript + C# 射线检测验证器见 references/lag-compensation.md。
6. 状态同步与输入同步
根据游戏需求选择合适的同步模式:
| 因素 | 同步状态 | 同步输入 |
|---|---|---|
| 发送内容 | 当前属性值(位置、生命值等) | 每帧的玩家输入操作 |
| 模拟方 | 仅权威端;其他端接收结果 | 所有对等端运行相同的模拟 |
| 是否需要确定性 | 否 | 是——所有对等端必须根据相同输入生成完全相同的输出 |
| 带宽 | 较高——每个间隔发送完整状态 | 较低——每帧发送较小的输入结构体 |
| 响应速度 | 较低——非权威对等端需等待下一次同步周期 | 较高——确定性模拟下,本地预测非常简单 |
| 复杂度 | 较低——无需校正循环 | 较高——需要确定性物理、定点数学或锁步 |
| 适用场景 | 动作游戏、射击游戏及大多数实时游戏 | 格斗游戏、RTS、回合制游戏、模拟游戏 |
| 是否需要延迟补偿 | 是,用于命中检测 | 通常不需要——所有对等端保持同步 |
混合方案(适用于大多数实时游戏):对本地玩家角色同步输入(以支持预测),对所有其他对象和游戏事件同步状态。
7. 带宽优化
有四种优化手段:仅同步发生变化的属性(为每个属性设置复制配置标志)、量化浮点数(组件使用 16 位厘米值而非浮点数——RPC 参数和同步属性会将整数压缩为 1/2/4/8 字节,另加 1 个类型字节,因此 16 位整数占 3 字节,而浮点数占 8 字节)、按距离调整同步频率(远处对象以 5 Hz 同步,近处对象以 30 Hz 同步),以及选择通道(必须送达的状态变化使用可靠通道,被新数据取代的位置流使用不可靠通道)。
每种优化手段的完整 GDScript + C# 示例,以及可靠与不可靠通道的决策树,见 references/bandwidth-optimization.md。
8. 实现检查清单
-
MultiplayerSynchronizer是其所同步节点的直接子节点 - 只有 authority 对等端会写入同步属性;其他对等端只能读取
- 在生成时调用
set_multiplayer_authority(),并传入正确的对等端 ID - 根据对象的更新速率调整
replication_interval和delta_interval - 远程玩家的视觉效果在
_process中使用插值,而非在_physics_process中使用 - 插值会存储前一状态和当前状态,并根据距上次更新的时间进行混合(而非使用
Engine.get_physics_interpolation_fraction(),因为它跟踪的是物理帧,而非网络更新) - 客户端预测仅应用于本地玩家自己的角色
- 待处理输入缓冲区有上限(最多约 128 个 tick),以防止内存增长
- 校正阈值可防止微小修正导致抖动
- 位置和速度使用
unreliableRPC;状态变化使用reliable - 通过网络发送位置数据前,先对浮点数进行量化
- 每个 tick 都会将延迟补偿快照历史记录裁剪到有界时间窗口内
- 服务端会验证所有命中检测;客户端绝不会自行报告击杀
- 基于距离的同步速率会降低远处对象的带宽占用