Godot 多人游戏 (4.x 高级)
连接对等节点,使用 @rpc 远程调用函数,分配权限,并使用 MultiplayerSpawner/MultiplayerSynchronizer 复制状态。 目标版本为 Godot 4.7 (ENet)。将所有客户端输入视为不受信任;保持服务器权威。
何时使用
- 添加网络多人游戏时使用:通过 ENet 托管/加入、调用 RPC、
分配每个节点的权限,或在各个对等节点之间自动生成/同步节点。
**何时*不*使用:** 本地分屏(无网络);原始 TCP/UDP/WebSocket 协议 工作(低层 PacketPeer);HTTP 请求。对于保存/持久化 → save-systems。
核心工作流
- 创建对等节点 (
ENetMultiplayerPeer),调用create_server(port, max)或 - 处理连接信号 在
multiplayer上:peer_connected(id)、 - 定义 RPC 使用
@rpc(...)。通过rpc()(所有对等节点)或 - 保持 RPC 签名在每个运行该脚本的对等节点上完全一致 — Godot 会对脚本中所有
- 分配权限 每个节点使用
set_multiplayer_authority(id);通过 - 复制状态 使用
MultiplayerSpawner(在客户端自动实例化场景)和 - 在服务器上进行验证。 不要信任客户端报告的位置/结果。
create_client(ip, port),并将其分配给 multiplayer.multiplayer_peer。 服务器的唯一 ID 始终为 1;客户端获得随机正数 ID。
peer_disconnected(id)、connected_to_server、connection_failed、 server_disconnected。
rpc_id(peer_id)(单个对等节点)在 Callable 上调用它们。在其中,multiplayer.get_remote_sender_id() 会告诉你是谁发送的。
@rpc 方法进行校验和检查;不匹配会导致静默失败。
is_multiplayer_authority() 限制输入/RPC。
MultiplayerSynchronizer(自动同步选定的属性)。
模式
1. 托管或加入 (ENet)
const PORT := 7000
const MAX_PLAYERS := 8
func host() -> void:
var peer := ENetMultiplayerPeer.new()
var err := peer.create_server(PORT, MAX_PLAYERS)
if err != OK:
push_error("Cannot host: %s" % err); return
multiplayer.multiplayer_peer = peer
multiplayer.peer_connected.connect(_on_peer_connected)
func join(ip := "127.0.0.1") -> void:
var peer := ENetMultiplayerPeer.new()
peer.create_client(ip, PORT)
multiplayer.multiplayer_peer = peer
multiplayer.connected_to_server.connect(func(): print("connected"))
func leave() -> void:
multiplayer.multiplayer_peer = OfflineMultiplayerPeer.new()
2. RPC:客户端向服务器发送输入 (any_peer, call_local)
func _unhandled_input(event: InputEvent) -> void:
if event.is_action_pressed("fire") and is_multiplayer_authority():
request_fire.rpc_id(1) # 仅发送给服务器 (id 1)
# 客户端可以调用此函数;它在服务器上运行(如果服务器也是玩家,则在本地运行)。
@rpc("any_peer", "call_local", "reliable")
func request_fire() -> void:
var sender := multiplayer.get_remote_sender_id()
if not _can_fire(sender): # 服务器端验证
return
spawn_projectile.rpc(sender) # 告诉每个人生成它
@rpc("authority", "call_local", "reliable")
func spawn_projectile(owner_id: int) -> void:
_do_spawn(owner_id)
3. 每个节点权限(每个玩家控制自己的头像)
extends CharacterBody2D
func _ready() -> void:
# 节点名称是拥有该节点的 peer 的 id;该 peer 是权限拥有者。
set_multiplayer_authority(name.to_int())
func _physics_process(delta: float) -> void:
if not is_multiplayer_authority():
return # 只有拥有者读取输入并移动
velocity = Input.get_vector("left", "right", "up", "down") * 200.0
move_and_slide()
4. MultiplayerSynchronizer 配置(编辑器 + 复制)
# 添加一个 MultiplayerSynchronizer 子节点;在其 Replication 编辑器中添加要
# 同步的属性(例如 position, velocity)。为每个属性设置 "Sync"/"Spawn" 标志。从代码中,你
# 可以限定可见性:
@onready var sync: MultiplayerSynchronizer = $MultiplayerSynchronizer
func _ready() -> void:
# 仅将此节点复制给特定的对等节点(例如私有信息)。
sync.set_visibility_for(target_peer_id, true)
陷阱
- RPC 签名校验和。 脚本中的每个
@rpc方法必须在客户端和服务器构建中 - 默认
@rpc是"authority"。 除非你设置"any_peer",否则调用它的客户端会被忽略。使用"call_local"以便主机(也是玩家)也能运行它。 - NodePaths 必须在各个对等节点之间匹配。 RPC 路由使用节点的路径/名称;在所有对等节点上以相同的名称生成节点(使用
MultiplayerSpawner或add_child(node, true)以获得可读、确定性的名称)。 - 信任客户端。 永远不要让客户端直接设置权威状态(生命值、位置、命中)。发送*意图*,在服务器上进行验证,然后广播结果。
- 在非 Node 类上的 RPC 会失败。
@rpc方法必须在Node派生类上,而不是普通的Resource/RefCounted上。 - RPC 不序列化 Objects/Callables。 传递普通数据(整数、字符串、数组、字典、PackedArrays)。
- 忘记重置对等节点。 要干净地断开连接,设置
- Android 需要在导出预设中拥有 INTERNET 权限,否则所有网络通信都会被阻止。
以相同的声明存在 — *即使是未使用的方法*。不匹配会导致指向错误函数的 错误。参数名称/数量不会被检查,但 RPC 集合及其注解会被检查。
multiplayer.multiplayer_peer = OfflineMultiplayerPeer.new()。
参考
- 有关
MultiplayerSpawner设置、传输模式/通道、SceneMultiplayer
认证(auth_callback/complete_auth)、大厅框架以及专用服务器 导出说明,请阅读 references/replication-and-rpc.md。
相关技能
godot-nodes-scenes— 实例化将被生成/同步的场景。godot-signals-groups— 连接信号和事件流。godot-export— 导出无头专用服务器构建。