mcpField
M
multiplayer-sync

multiplayer-sync

github.com/jame581开发889165 次安装4 天前更新
代码仓库

关于 multiplayer-sync

面向 Godot 4.3+ 开发者的多人同步技能,讲解 MultiplayerSynchronizer 的属性复制、插值和带宽优化。还介绍客户端预测、延迟补偿,以及同步状态与输入的取舍。

主要功能

  1. 01MultiplayerSynchronizer 属性复制
  2. 02全量同步与差异同步
  3. 03远程状态插值
  4. 04客户端预测与延迟补偿
  5. 05带宽优化与同步模式选择

适用场景

  1. 01同步玩家位置、速度和生命值
  2. 02减少远程玩家画面跳变
  3. 03为快节奏游戏实现预测与命中验证

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/jame581/godotprompter --skill multiplayer-sync

安装后在 Claude Code 中输入/multiplayer-sync确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
bandwidth-optimization.md
client-prediction.md
interpolation.md
lag-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”属性,每个间隔不可靠发送)
  • 支持使用可见性筛选器控制哪些对等方会收到更新
在编辑器中配置复制
  1. 在场景树中选择 MultiplayerSynchronizer 节点。
  2. 在检查器中打开 Replication,然后点击 Add Property。
  3. 选择父节点路径和属性名称(例如 position、velocity)。
  4. 为每个属性设置 Sync(每个间隔发送)或 Spawn(仅在生成时发送)。
  5. 设置 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. 属性同步

要同步的内容

只同步在远程对等方上重建画面所需的最少状态。常见属性如下:

属性类型说明
positionVector2 / Vector3核心变换——每帧同步,或使用插值
velocityVector2 / Vector3有助于远程预测跟上位置跳变
healthint / float发生变化时可靠同步;适合使用增量同步
animation_stateString / int发生变化时同步;使用枚举整数可节省带宽
is_crouchingbool变化较少的布尔值;可使用增量同步或在变化时调用 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),以防止内存增长
  • 校正阈值可防止微小修正导致抖动
  • 位置和速度使用 unreliable RPC;状态变化使用 reliable
  • 通过网络发送位置数据前,先对浮点数进行量化
  • 每个 tick 都会将延迟补偿快照历史记录裁剪到有界时间窗口内
  • 服务端会验证所有命中检测;客户端绝不会自行报告击杀
  • 基于距离的同步速率会降低远处对象的带宽占用

评论

支持 Markdown