Godot 4 P2P 联机基础 —— ENetMultiplayerPeer 与 RPC 注解

📇 速览卡片(3 分钟复习)

概念 一句话
ENet 基于 UDP 的可靠传输库,Godot 4 内置封装
ENetMultiplayerPeer 端对端(P2P)联机的核心实例,替代底层 Socket 管理
call_remote RPC 只发远端,本地不执行
call_local RPC 本地与远端同时执行
authority 仅该节点的网络权限所有者(通常是房主/服务器)可调用
any_peer 任意对等体均可调用
reliable 保证送达,适合状态变更、扣血
unreliable 不保证送达,适合高频位置同步
unreliable_ordered 不保证送达,但保证顺序,适合连续动画帧
通道 (Channel) 0~9 共 10 条,类似端口分流,可分别设带宽上限

📝 详细笔记

1. Host 与 ENetMultiplayerPeer

Godot 4 的 P2P 联机基于 ENet 库,底层走 UDP 协议。开发者不需要手动处理 Socket、NAT Punch-through 或数据包序列化,直接通过 ENetMultiplayerPeer 一个高层实例完成端对端管理。

1
2
3
4
5
6
7
var peer = ENetMultiplayerPeer.new()
# 创建主机(监听)
peer.create_server(port, max_clients)
# 或加入房间(客户端)
peer.create_client(address, port)

multiplayer.multiplayer_peer = peer

ENetMultiplayerPeer.new() 即为一个完整的端对端实例,封装了连接管理、心跳、丢包重传等细节。


2. @rpc 注解四参数

Godot 4 使用 @rpc(...) 标记远程调用函数,四个位置参数决定了网络行为的全部策略:

1
2
3
@rpc(A, B, C, D)
func my_remote_func():
pass

A —— 执行范围(本地 vs 远端)

取值 语义
call_remote 仅远端执行,本地不执行。适合”通知别人”
call_local 本地 + 远端全部执行。适合确定性逻辑,确保所有端状态一致

B —— 调用权限(谁能发起)

取值 语义
authority 仅该节点的网络权限所有者(默认是创建它的端,通常是 Host)可以调用。防作弊首选
any_peer 任意对等体均可调用。适合玩家输入、自由交互

C —— 传输可靠性

取值 语义 适用场景
reliable 保证送达,丢包会重传,可能延迟 状态变更、扣血、拾取道具、游戏开始/结束
unreliable 不保证送达,不重传,最低延迟 高频位置同步、旋转、速度
unreliable_ordered 不保证送达,但保证顺序 连续动画帧、弹道轨迹,允许丢帧但不允许乱序

D —— 通道编号 (Channel)

  • 取值范围:0 ~ 9,共 10 条独立通道
  • 作用类似端口分流:不同通道的数据互不阻塞,可分别设置带宽上限
  • 典型用法:
    • 通道 0:reliable 状态同步(低带宽、高优先级)
    • 通道 1:unreliable 位置同步(高频、可丢包)
    • 通道 2:聊天/语音(独立带宽限制)

💻 代码示例

主机创建与 RPC 调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
extends Node

@rpc("call_remote", "authority", "reliable", 0)
func start_game():
# 只有房主能调用,通知所有客户端游戏开始
get_tree().change_scene_to_file("res://Game.tscn")

@rpc("call_local", "any_peer", "unreliable", 1)
func update_position(pos: Vector2):
# 任意玩家都能发,本地也立即执行,高频位置同步
$Character.position = pos

func _ready():
var peer = ENetMultiplayerPeer.new()
peer.create_server(9999, 4)
multiplayer.multiplayer_peer = peer
multiplayer.peer_connected.connect(_on_peer_connected)

func _on_peer_connected(id: int):
print("玩家 ", id, " 已连接")
# 房主调用,所有客户端(包括本地)执行 start_game
start_game.rpc()

客户端加入

1
2
3
4
5
6
7
8
func join_game(ip: String, port: int):
var peer = ENetMultiplayerPeer.new()
peer.create_client(ip, port)
multiplayer.multiplayer_peer = peer
multiplayer.connected_to_server.connect(_on_connected)

func _on_connected():
print("已连接到服务器,本机 ID: ", multiplayer.get_unique_id())