Godot(4.X): 全局AutoLoad网络管理中心实现
本文介绍了在Godot引擎中使用AutoLoad实现的全局网络管理中心方案。该方案基于ENet网络库,采用对等体架构(P2P),通过封装multiplayer API构建全局网络服务单例。核心实现包括:1) 根管理器提供创建/关闭服务器/客户端的API;2) 子管理器分别处理服务端和客户端逻辑;3) 共用接收端统一数据传输。方案特点包括:自动处理连接管理、支持可靠/不可靠传输模式、数据收发与逻辑处
前言:本文是Godot使用新框架实现的一个全局AutoLoad网络管理中心,相对于旧框架
针对Godot(2D)游戏架构的研究_godot(2d)最佳架构-CSDN博客 ,本文将使用全局自动加载直接作为单例系统,而不是手写。
在阅读本文前,会用到编辑器操作:
Godot(2D)架构细分4:编辑器全局设置解释-CSDN博客 中的全局AutoLoad设置
1. Godot全局AutoLoad网络管理中心概述
1. Godot中使用的网络
(这段是本架构实现需要知道的基本知识)
Godot中网络联机使用的网络库是ENet,ENet 是基于 UDP 的可靠传输库,相对原始UDP传输,ENet在TCP与UDP之间取了平衡 (通过 ACK 确认、超时重传、有序重组实现了可控的可靠性),具有一定的可靠性。
UDP原生可以理解为数据直接往指定位置发送,与接收端不建立连接、不重传、不保证有序,速度极快,但是会丢包
Godot中ENet的核心用法结构是:1个服务端,多个客户端
同时ENet可以支持设置 Reliable(可靠不丢包) 传输与 Unreliable(不可靠速度快) 等4种传输模式,演示只使用提到的两种
自动缓存玩家已发送的数据,底层自动处理连接管理(UDP实现的伪三次握手,自动超时断连等)
需要注意的是,Godot中使用 @rpc 收发数据,由发送端发起,由 @rpc 配置决定 同结构下的同名带 @rpc关键字的函数 谁执行
所以网络AutoLoad结构必须一致 ,AutoLoad只负责数据的收发 ,处理必须将数据传出
2. Godot本网络架构实现核心要点
1. 封装原生 multiplayerAPI 到全局网络服务单例,避免直接使用 multiplayer 实现网络连接
2. 全局网络对于数据传输,只负责数据的传输与接收,并且接收数据后需将数据传出供逻辑层使用
3. 外部函数应避免直接修改单例中的变量,尽量使用开放API
2. 全局AutoLoad网络管理中心的具体实现
首先给出本演示文件结构(网络部分) 以及网络管理器场景


1. 构建全局网络AutoLoad
构建全局网络AutoLoad,首先将全局网络节点加入全局自动加载,这里我给全局网络节点命名为Server

使用AutoLoad创建的全局场景,可以直接使用 名字.代码 的方式调用自动加载的场景的根节点对象的代码
然后如上图管理器场景所示,将主管理器下面划分为服务端和客户端,用以分别控制服务器代码和客户端代码
这里根节点Server只负责向外提供API,内部创建网络等一系列操作由子管理器完成并返回给根节点。
2. 网络根管理器向外API开放实现
根Server节点负责向外提供创建网络,创建连接等一系列操作的API,这里是实现代码
extends Node2D
#服务端口,客户端口,服务器运行端口的变量(子管理器)
@export var ServerSide: Node2D # 服务端管理器
@export var Client: Node2D # 客户端管理器
@export var ServerRunning: Node2D # 服务器运行时管理器
@export var GameRunning: Node2D # 游戏运行时管理器
# 是否已经创建服务端,即服务器状态管理(客户端)
var alreadyCreateServe: bool = false
var alreadyCreateClient: bool = false
func _ready() -> void:
#这里接收所有子管理器信号as
_signal_initialize()
#创建服务端
func create_serve() -> void:
# 只启动服务端或者客户端
if alreadyCreateServe or alreadyCreateClient:
return
# 调用子管理器创建,得到是否创建成功的返回值
if ServerSide.create_server():
alreadyCreateServe = true
ServerRunning.player_list.append("1")
# 创建客户端
func create_client() -> void:
# 只启动服务端或者客户端
if alreadyCreateClient or alreadyCreateServe:
return
## 创建客户端成功,但是没有连接成功返回true
## 连接失败返回信号并重新变更为false,信号函数在下文
if Client.create_client():
alreadyCreateClient = true
# 对外开放 关闭服务器
func close_serve() -> void:
if !alreadyCreateServe:
return
# 关闭服务器
if !ServerSide.close_serve():
print("服务器关闭失败")
return
alreadyCreateServe = false
# 对外开放 关闭客户端
func close_client() -> void:
if !alreadyCreateClient:
return
# 关闭客户端连接
if !Client.close_client():
print("客户端关闭失败")
return
alreadyCreateClient = false
## 子管理器信号连接
func _signal_initialize() -> void:
#这里连接的是子管理器客户端连接超时的信号
Client.server_connect_failed.connect(_on_client_connect_failed)
# 客户端连接失败
func _on_client_connect_failed() -> void:
alreadyCreateClient = false
# 同步玩家位置信息
func sync_position(send_position: Vector2)-> void:
GameRunning.sync_with_player_position_dic(send_position)
# 同步玩家杂项属性信息(补充)
func sync_infomation(send_information: Dictionary) -> void:
GameRunning.sync_with_player_information(send_information)
# 网络全局场景切换
func change_all_scene(path: String) -> void:
# 服务器切换场景
if !(multiplayer.get_unique_id() == 1):
return
rpc_change_scene(path)
rpc_change_scene.rpc(path)
# 得到玩家列表
func get_player_list() -> Array:
# 这里 .duplicate() 返回的是数组的副本,防止篡改内部信息
return ServerRunning.player_list.duplicate()
# 得到玩家位置信息字典
func get_player_position_dic() -> Dictionary:
# 这里 .duplicate() 返回的是字典的副本,防止篡改内部信息
return GameRunning.player_position_dic.duplicate()
# 得到玩家杂项信息字典(补充)
func get_player_information() -> Dictionary:
# 这里 .duplicate() 返回的是字典的副本,防止篡改内部信息
return GameRunning.player_information.duplicate()
# 更新主机玩家位置
func change_main_player_position(main_position: Vector2) -> void:
GameRunning.server_position = main_position
# 更新主机玩家信息(补充)
func change_main_player_infomation(main_information: Dictionary) -> void:
GameRunning.server_dic = main_information
# 发送信息
func send_message(msg: String) -> void:
GameRunning.send_player_message(msg)
# 获取玩家信息列表
func get_player_chat_list() -> Array:
# 这里 .duplicate() 返回的是数组的副本,防止篡改内部信息
return GameRunning.player_chat_messages.duplicate()
# 返回服务器(客户端)连接状态
func is_connect() -> bool:
var peer = multiplayer.multiplayer_peer
if peer == null or peer is OfflineMultiplayerPeer:
return false
return peer.get_connection_status() == MultiplayerPeer.CONNECTION_CONNECTED
# 得到玩家网络ID
func get_player_id() -> int:
return multiplayer.get_unique_id()
# 切换场景全局广播
@rpc("any_peer", "reliable")
func rpc_change_scene(path: String):
get_tree().change_scene_to_file(path)
以上代码对外开放了服务器的基本操作,其他场景仅需要使用
Server.create_serve()
Server.create_client()
等操作实现服务器的创建,关闭等操作
3. 子管理器代码实现
首先解释: 本网络架构为对等体架构(Peer - to - Peer 架构)
对等体架构:
对等网络(Peer-to-Peer,简称P2P)是一种分布式应用架构
在这种架构中,网络中的每个节点(Peer)既是资源、服务和内容的提供者(Server),又是资源、服务和内容的获取者(Client)。这种网络结构没有中心节点,每个节点都可以直接与其他节点进行通信和资源共享。
Godot中通过创建对等体进行网络数据的传输,每个在网络中的对等体有自己独特的ID,其中服务器对等体的ID固定为1, 其余客户端均为随机值
子管理器负责网络的实现部分,包括创建网络,关闭网络,检查错误等等。
这里将网络子管理器的客户端和服务端,以及服务器和客户端共用的接收端三个子管理器分开,便于实现不同操作的单独控制
1. 服务端
服务器代码包含对根管理器开放的创建服务器,关闭服务器等操作,以下为实现代码
extends Node2D
#演示,固定端口 7777,以及最大连接数量为4
var PORT: int = 7777
var MAX_PLAYER: int = 4
#创建服务器
func create_server() -> bool:
#创建连接器(创建对等体,注意此时还没有规定服务端)
var peer = ENetMultiplayerPeer.new()
if peer == null:
push_error("服务器启动失败")
return false
#创建服务端(这时候规定了此对等体为服务端)
var err = peer.create_server(PORT, MAX_PLAYER)
if err != OK:
push_error("服务器启动失败")
return false
multiplayer.multiplayer_peer = peer
## 启动监听
## peer_connected 是Godot中网络连接自带信号,负责监听是否有新连接
## peer_disconnected 是Godot中网络连接自带信号,负责监听是否有连接断开
multiplayer.peer_connected.connect(_on_peer_connect)
multiplayer.peer_disconnected.connect(_on_peer_disconnect)
print("服务器启动,端口号:", PORT)
return true
#服务器接入端口
func _on_peer_connect(peer_id) -> void:
print("玩家连入: ID:", peer_id)
rpc_id(peer_id, "attend_game")
#服务器接出端口
func _on_peer_disconnect(peer_id) -> void:
print("玩家断开: ID:", peer_id)
#关闭服务器
func close_serve() -> bool:
#获取服务器对等体并断开(关闭服务器)
var peer = multiplayer.multiplayer_peer
if peer == null:
return false
#关闭服务器(防止客户端因为某种原因点击关闭服务器后误关客户端自己)
if !(multiplayer.get_unique_id() == 1):
return false
#断开信号连接
multiplayer.peer_connected.disconnect(_on_peer_connect)
multiplayer.peer_disconnected.disconnect(_on_peer_disconnect)
peer.close()
#清空网络设置
multiplayer.multiplayer_peer = null
if multiplayer.multiplayer_peer != null:
return false
print("服务器已关闭")
return true
2. 客户端
客户端代码包含对根管理器开放的创建客户端,关闭客户端等操作,以下为实现代码
extends Node2D
#固定连接端口(演示)
var PORT: int = 7777
#本机测试地址
var SERVER_IP: String = "26.224.10.101"
#服务器连接超时信号
signal server_connect_failed
#创建客户端
func create_client() -> bool:
var peer = ENetMultiplayerPeer.new()
if peer == null:
push_error("客户端启动失败")
#创建客户端(对等体连接IP)
var err = peer.create_client(SERVER_IP, PORT)
if err != OK:
push_error("服务器启动失败")
return false
multiplayer.multiplayer_peer = peer
## 监听连接结果
## connected_to_server 为Godot中网络自带API,负责检测成功连接到服务器(连接成功)
## connection_failed 为Godot中网络自带API,负责检测未能连接到服务器(连接超时)
multiplayer.connected_to_server.connect(_on_connected_ok)
multiplayer.connection_failed.connect(_on_connected_fail)
print("正在连接服务器")
return true
func _on_connected_ok() -> void:
print("成功连接到服务器")
func _on_connected_fail() -> void:
print("连接服务器失败")
#连接超时发送连接超时信号
server_connect_failed.emit()
# 断开客户端连接
func close_client() -> bool:
# 获取客户端对等体并清空
var peer = multiplayer.multiplayer_peer
if peer == null:
return false
#关闭客户端(防止服务器端口误点击关闭客户端而关闭服务端)
if multiplayer.get_unique_id() == 1:
push_error("本机器为服务端口,不是客户端口,不可使用客户端口关闭")
return false
#断开信号连接
multiplayer.connected_to_server.disconnect(_on_connected_ok)
multiplayer.connection_failed.disconnect(_on_connected_fail)
peer.close()
#清空网络设置
multiplayer.multiplayer_peer = null
if multiplayer.multiplayer_peer != null:
return false
print("已断开连接")
return true
3. 服务器和客户端共用的接收端
服务器和客户端共用的接收端 负责统一全局网络网络数据传输操作,保证网络中各个对等体的需要同步的数据相同,同时将得到的数据传给游戏逻辑层或其他层
需要注意的是,只有服务器端第一时间更新数据,再同步发送给其他对等体客户端
extends Node2D
## 这里先演示同步玩家ID
## 这里是手动同步,Godot可以使用multiplayer.get_peers()直接获得玩家列表
## 拆开的手写同步的目的是:
## 1. Godot中同步的玩家数据不会有服务器ID,需要手动加
## 2. 将玩家列表从 服务器AutoLoad传出去而不是直接调用Godot网络API
## 需要注意,@rpc 关键字标明的代码需要使用如 player_list_get.rpc(player_list) 来进行发射
## .rpc() 发射代码,括号里面可以写入传参
#玩家列表
var player_list: Array[String] = []
func _ready() -> void:
#初始化玩家信号连接
_initialize_signal()
func _physics_process(delta: float) -> void:
# 持续检测玩家连接(注意这里是清空列表,不是持续更新列表),更新列表
# 不是持续更新玩家列表,这里条件到了只执行一次
_detect_connect()
# 接收到玩家连接信号,后将玩家列表更新
func online_player_connect(peer_id: int) -> void:
## 只有服务器端第一时间更新数据
## multiplayer.get_unique_id() == 1 这句是判断是否为服务器端
if multiplayer.get_unique_id() == 1:
print("服务器接收: 新玩家加入: ID: %d" % peer_id)
player_list.append(str(peer_id))
#发送数据给其他客户端对等体
player_list_get.rpc(player_list)
print("服务端口: ", player_list)
# 客户端受到玩家加入信号,输出玩家信息
if multiplayer.get_unique_id() != 1:
print("客户端接收: 新玩家加入: ID: %d" % peer_id)
# 接收玩家断连接信号,更新玩家列表
func online_player_disconnect(disconnect_peer_id: int) -> void:
# 先转换为字典种存储的字符串类型
var peer_id: String = str(disconnect_peer_id)
# 断开连接清空数据(只有服务端)
if multiplayer.get_unique_id() == 1:
player_list.erase(peer_id)
# 服务器端发送玩家列表
player_list_get.rpc(player_list)
# 清理玩家数据
var game = get_parent().GameRunning
if game:
# 擦除玩家字典与属性
if game.player_position_dic.has(peer_id):
game.player_position_dic.erase(peer_id)
if game.player_information.has(peer_id):
game.player_information.erase(peer_id)
# 重新发送玩家数据同步
game.player_list_get.rpc(game.player_position_dic)
game.player_list_information.rpc(game.player_information)
## 统一发送玩家列表
## @rpc("authority", "reliable")
## 意思为 1. 可发送端: 服务权威端 2. 发送模式: 可靠不丢包发送
@rpc("authority", "reliable")
func player_list_get(get_player_list: Array[String]) -> void:
# 服务器端发送玩家列表
if multiplayer.get_unique_id() != 1:
player_list = get_player_list
print("客户端口: ", player_list)
#初始化连接所有玩家信号
func _initialize_signal() -> void:
## 连接玩家连接,断连数据
## 连接函数 online_player_connect()
## 连接函数 online_player_disconnect()
## 触发信号自动连接发送函数
multiplayer.peer_connected.connect(online_player_connect)
multiplayer.peer_disconnected.connect(online_player_disconnect)
#客户端自检测
func _detect_connect() -> void:
## 检测联机节点
## 断连自动清除玩家列表
if multiplayer.multiplayer_peer == null:
if player_list != []:
player_list.clear()
return
# 这里检测对等体是否为空或者本地离线对等体,清空玩家列表
if multiplayer.multiplayer_peer is OfflineMultiplayerPeer:
if player_list != []:
player_list.clear()
return
var state = multiplayer.multiplayer_peer.get_connection_status()
#检查断连
if state == multiplayer.multiplayer_peer.CONNECTION_DISCONNECTED:
if player_list != []:
player_list.clear()
这里得到的数据列表,要外部需要的部分手动来取
3. 实际演示
这里实际演示也有调用代码,将会分文章细讲,这里先给出效果
1. 启动三个演示

2. 使用其中一个窗口创建服务器

3. 加入一个玩家客户端口

4. 再次加入一个玩家,服务器自动同步

5. 关闭服务器,其余客户端自动断开

这里的玩家列表显示,是外部调用服务器玩家列表后,实例化管理玩家列表的效果,后续将给出文章细讲。
4. 开源以及调用细节
1. 游戏运行时管理器补充(游戏数据传输管理器)
Godot(4.X): 全局AutoLoad网络管理中心: 全局数据同步-CSDN博客
2. 游戏实际使用演示
1. 玩家位置控制与信息同步
Godot(4.X): 网络管理中心细分:玩家位置控制与同步-CSDN博客
2. 玩家聊天系统
Godot(4.X): 网络管理中心细分:玩家通用聊天系统-CSDN博客
2. 演示开源地址(本网络中心AutoLoad支持扩展)
looooiiui/2D-Online-Sample-Game: 这是一个2D联机的示例游戏,使用全局管理器,联机解耦,可以扩展
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐



所有评论(0)