收录关于Godot网络的方方面面,陆续增加.
高级多人游戏
# 客户端
var peer = ENetMultiplayerPeer.new()
peer.create_client(IP_ADDRESS, PORT)
multiplayer.multiplayer_peer = peer
# 服务端
var peer = ENetMultiplayerPeer.new()
peer.create_server(PORT, MAX_CLIENTS)
multiplayer.multiplayer_peer = peer
# 中止网络
multiplayer.multiplayer_peer = null
各客户端都会分配一个整数ID,服务端ID为1
事件
peer_connected(id: int)
peer_disconnected(id: int) 有客户端断开时(向余下客户端)触发
以下事件仅在客户端上触发:
connected_to_server()
connection_failed()
server_disconnected()
multiplayer.get_unique_id() 获取唯一ID
multiplayer.is_server() 是否为服务端
远程过程调用
远程过程调用(RPC)是可以在其他客户端上调用的函数。使用@rpc表示创建一个RPC。
要调用时,在函数后使用rpc()方法在每个客户端调用。或者使用RPC_id()在指定客户端中调用。
func _ready():
if multiplayer.is_server():
print_once_per_client.rpc()
@rpc
func print_once_per_client():
print("I will be printed to the console once per each connected client.")
要使远程调用成功,发送节点和接收节点需要具有相同的NodePath,这意味着它们必须具有相同的名称。
对预期使用RPC的节点使用add_child()时,请将参数force_readable_name设置为true。
如果一个函数在客户端脚本(resp.server脚本)上用@rpc注释,那么这个函数也必须在服务器脚本(resp.client脚本)上声明。两个RPC必须具有相同的签名,该签名使用所有RPC的校验和进行评估。同时检查脚本中的所有RPC,并且必须在客户端脚本和服务器脚本上声明所有RPC,即使是当前未使用的函数。
RPC的签名包括@RPC()声明、函数、返回类型和节点路径。如果RPC驻留在附加到/root/Main/Node1的脚本中,则它必须驻留在客户端脚本和服务器脚本上完全相同的路径和节点中。函数参数(例如:func sendstuff():和func sendstoff(arg1,arg2):将通过签名匹配)。
@rpc(“authority”, “call_remote”, “unreliable”, 0) 注释?
mode:
“authority”: 有多人游戏权限(服务器)可以远程调用。
“any_peer”: 许客户端进行远程呼叫。可用于传输用户输入。
remotesync:
“call_remote”:不会在本地对等机上调用该函数。
“call_local”: 该函数可以在本地对等机上调用。当服务器也是玩家时很有用。
(意思是:local即会在本地调用,也会被远程调用。而remote表示只会被远程调用)
transform:
“unreliable”: 数据包不被确认,可能会丢失,并且可以按任何顺序到达。
“unreliable_ordered”: 数据包是按发送顺序接收的。这是通过忽略稍后到达的数据包来实现的,如果在这些数据包之后发送的另一个数据包已经被接收到。如果使用不当,可能会导致数据包丢失。
“reliable”: 重新发送尝试一直发送到数据包得到确认,并保留其顺序。具有显著的性能损失。
transfer_channel 是通道索引
前3个可以按任何顺序传递,但transfer_channel必须始终是最后一个。
函数multilayer.get_remote_sender_id()可用于在rpc调用的函数中使用时获取rpc发送方的唯一id。
func _on_some_input():
transfer_some_input.rpc_id(1) # 仅发送到服务器(ID为1)
# 如果服务器也是玩家,则需要调用本地。
@rpc("any_peer", "call_local", "reliable")
func transfer_some_input():
# # 让服务器知道是谁发送了输入
var sender_id = multiplayer.get_remote_sender_id()
...
大厅的示例实现
这是一个示例大厅,可以处理对客户端的加入和离开,通过信号通知UI场景,并在所有客户端加载游戏场景后启动游戏。
extends Node
# 自动加载名为大厅
# 这些信号可以通过UI大厅场景或游戏场景连接到。
# (即通过其它场景来获取信号,从而进行响应。本段代码并没有响应以下几个事件。)
signal player_connected(peer_id, player_info)
signal player_disconnected(peer_id)
signal server_disconnected
const PORT = 7000
const DEFAULT_SERVER_IP = "127.0.0.1"
const MAX_CONNECTIONS = 20
# 包含每个玩家的玩家信息,其中的Key是每个玩家的唯一id。
var players = {}
# 这是本地玩家信息。在建立连接之前,应在本地对其进行修改。它将被传递给其他所有peer。
# 例如,“name”的值可以设置为玩家在UI场景中输入的内容。
var player_info = {"name": "Name"}
var players_loaded = 0
func _ready():
multiplayer.peer_connected.connect(_on_player_connected) # peer连接事件
multiplayer.peer_disconnected.connect(_on_player_disconnected) # peer禁止连接事件
multiplayer.connected_to_server.connect(_on_connected_ok) # 连接服务器事件
multiplayer.connection_failed.connect(_on_connected_fail) # 连接服务器失败事件
multiplayer.server_disconnected.connect(_on_server_disconnected) # 服务器禁止连接事件
# 加入游戏(客户端)
func join_game(address = ""):
if address.is_empty():
address = DEFAULT_SERVER_IP
var peer = ENetMultiplayerPeer.new()
var error = peer.create_client(address, PORT)
if error:
return error
multiplayer.multiplayer_peer = peer # 处理 RPC 系统的对等体对象
# 创建游戏(服务端)
func create_game():
var peer = ENetMultiplayerPeer.new()
var error = peer.create_server(PORT, MAX_CONNECTIONS)
if error:
return error
multiplayer.multiplayer_peer = peer # 处理 RPC 系统的对等体对象
players[1] = player_info # 第1个玩家是自己(服务器)
player_connected.emit(1, player_info) # 触发peer连接事件
# 移除peer
func remove_multiplayer_peer():
multiplayer.multiplayer_peer = null
# 当服务器决定从UI场景启动游戏时,执行Lobby.load_game.rpc(filepath)
@rpc("call_local", "reliable")
func load_game(game_scene_path):
get_tree().change_scene_to_file(game_scene_path)
# 每个客户端在加载游戏场景后都会调用此命令。
@rpc("any_peer", "call_local", "reliable")
func player_loaded():
if multiplayer.is_server():
players_loaded += 1
if players_loaded == players.size():
$/root/Game.start_game()
players_loaded = 0
# 当一个同伴连接时,把我的玩家信息发给他们。
# 这允许为每个玩家传输所有想要的数据,而不仅仅是唯一的ID。
func _on_player_connected(id):
_register_player.rpc_id(id, player_info)
@rpc("any_peer", "reliable")
func _register_player(new_player_info):
var new_player_id = multiplayer.get_remote_sender_id()
players[new_player_id] = new_player_info
player_connected.emit(new_player_id, new_player_info)
func _on_player_disconnected(id):
players.erase(id)
player_disconnected.emit(id)
func _on_connected_ok():
var peer_id = multiplayer.get_unique_id()
players[peer_id] = player_info
player_connected.emit(peer_id, player_info)
func _on_connected_fail():
multiplayer.multiplayer_peer = null
func _on_server_disconnected():
multiplayer.multiplayer_peer = null
players.clear()
server_disconnected.emit()
游戏场景的根节点应该被命名为game。在附加的脚本中:
extends Node3D # Or Node2D.
func _ready():
# 预配置游戏。
Lobby.player_loaded.rpc_id(1) # 告诉服务器peer客户端已加载。
# 仅在服务器上被调用
func start_game():
# 在此场景中,所有peer端都已准备好接收RPC。
ENetConnection.compress(mode: CompressionMode) 网络数据包压缩 (最适合小数据包)
COMPRESS_NONE = 0 无压缩
COMPRESS_RANGE_CODER = 1 ENet 的内置范围编码。适用于小数据包,但对于大于 4 KB 的数据包不是最有效的算法。
COMPRESS_FASTLZ = 2 FastLZ 压缩。
COMPRESS_ZLIB = 3 Zlib 压缩。
COMPRESS_ZSTD = 4 Zstandard 压缩。此算法对小于 4 KB 的数据包效率不高。