Godot 3 GDScript 教程 / Godot 3 GDScript 教程(十五):网络多人游戏基础
网络多人游戏基础
Godot 3 内置了基于 ENet 的网络模块,支持 UDP 可靠传输、RPC 远程调用和节点同步。本章讲解 Godot 3 的网络多人游戏开发核心知识。
NetworkedMultiplayerENet
extends Node
var peer: NetworkedMultiplayerENet
func _ready():
get_tree().connect("network_peer_connected", self, "_on_peer_connected")
get_tree().connect("network_peer_disconnected", self, "_on_peer_disconnected")
get_tree().connect("connected_to_server", self, "_on_connected")
get_tree().connect("connection_failed", self, "_on_failed")
# 创建服务器
func create_server(port: int = 9876, max_players: int = 32):
peer = NetworkedMultiplayerENet.new()
peer.create_server(port, max_players)
get_tree().network_peer = peer
# 加入服务器
func join_server(ip: String = "127.0.0.1", port: int = 9876):
peer = NetworkedMultiplayerENet.new()
peer.create_client(ip, port)
get_tree().network_peer = peer
func disconnect():
if peer:
peer.close_connection()
get_tree().network_peer = null
func _on_peer_connected(id: int):
print("玩家已连接: ", id)
func _on_peer_disconnected(id: int):
print("玩家已断开: ", id)
func _on_connected():
print("已连接到服务器")
func _on_failed():
print("连接失败")
客户端-服务器模型
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Client 1 │◄───────►│ Server │◄───────►│ Client 2 │
│ (Player) │ │ (权威) │ │ (Player) │
└─────────┘ └─────────┘ └─────────┘
| 角色 | 职责 |
|---|---|
| Server | 维护游戏状态,验证操作,广播更新 |
| Client | 发送输入,接收状态,本地预测/插值 |
func _ready():
if get_tree().is_network_server():
print("我是服务器")
else:
print("我是客户端: ", get_tree().get_network_unique_id())
remote/puppet/master 关键字
| 关键字 | 调用方 | 执行方 | 用途 |
|---|---|---|---|
remote | 任意一端 | 另一端 | 通用远程调用 |
master | puppet 端 | master 端 | 只有 master 才执行 |
puppet | master 端 | puppet 端 | 只有 puppet 才执行 |
remotesync | 任意一端(含自己) | 所有端 | 同步执行 |
# 服务器调用,客户端执行(同步位置)
puppet func sync_position(pos: Vector2):
global_position = pos
# 客户端调用,服务器执行(提交输入)
master func submit_move(direction: Vector2):
var player_id = get_tree().get_rpc_sender_id()
var player = get_player(player_id)
player.move(direction)
# 所有端都执行(特效、音效)
remotesync func play_explosion(pos: Vector2):
var effect = explosion_scene.instance()
effect.position = pos
get_parent().add_child(effect)
⚠️ 注意:remote 方法默认不在本机执行。如果需要本机也执行,使用 remotesync。
RPC 调用同步
# 广播给所有人(除了自己)
rpc("sync_position", global_position)
# 广播给所有人(包括自己),0 表示所有 peer
rpc_id(0, "play_explosion", position)
# 只发给服务器(ID 1 固定为服务器)
rpc_id(1, "submit_move", direction)
# 多人射击同步
extends KinematicBody2D
var health: int = 100
func _physics_process(delta):
if is_network_master():
var direction = _get_input_direction()
move_and_slide(direction * 200)
rpc_unreliable("sync_position", global_position) # 不可靠传输(丢包无影响)
puppet func sync_position(pos: Vector2):
global_position = pos
# 攻击(可靠同步)
remotesync func fire_bullet(from_pos: Vector2, dir: Vector2):
var bullet = bullet_scene.instance()
bullet.global_position = from_pos
bullet.direction = dir
get_tree().root.add_child(bullet)
remotesync func take_damage(amount: int):
health -= amount
if health <= 0:
die()
💡 提示:rpc_unreliable 适合高频更新(位置同步),rpc 适合关键事件(射击、伤害)。
网络延迟补偿
客户端预测
extends KinematicBody2D
var position_history: Array = []
func _physics_process(delta):
if is_network_master():
var input = _get_input()
move_and_slide(input * 200)
position_history.append({"tick": OS.get_ticks_msec(), "pos": global_position})
rpc_id(1, "submit_input", input, OS.get_ticks_msec())
# 服务器确认后纠正位置
puppet func server_confirm(pos: Vector2, tick: int):
if global_position.distance_to(pos) > 10.0:
global_position = pos
插值
puppet var network_position: Vector2 = Vector2.ZERO
func _physics_process(delta):
if not is_network_master():
global_position = global_position.linear_interpolate(network_position, delta * 15.0)
延迟补偿策略对比
| 技术 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 客户端预测 | 低延迟感 | 需要服务器和解 | FPS/动作游戏 |
| 服务器确认 | 防作弊 | 有延迟感 | RTS/回合制 |
| 插值 | 平滑 | 有固定延迟 | 其他玩家表现 |
房间系统
extends Node
var rooms: Dictionary = {}
var next_room_id: int = 1
func create_room(host_id: int, room_name: String, max_players: int = 4) -> int:
var room_id = next_room_id
next_room_id += 1
rooms[room_id] = {
"id": room_id, "name": room_name, "host_id": host_id,
"max_players": max_players, "players": [host_id], "state": "waiting"
}
rpc("room_created", room_id, room_name, host_id, max_players)
return room_id
remote func join_room(room_id: int):
var sender_id = get_tree().get_rpc_sender_id()
if not room_id in rooms:
rpc_id(sender_id, "join_failed", "房间不存在")
return
var room = rooms[room_id]
if room["players"].size() >= room["max_players"]:
rpc_id(sender_id, "join_failed", "房间已满")
return
room["players"].append(sender_id)
rpc("player_joined_room", room_id, sender_id)
remote func start_game(room_id: int):
var sender_id = get_tree().get_rpc_sender_id()
if room_id in rooms and rooms[room_id]["host_id"] == sender_id:
for pid in rooms[room_id]["players"]:
rpc_id(pid, "game_start", room_id)
聊天系统
remote func send_chat_message(message: String):
var sender_id = get_tree().get_rpc_sender_id()
var player_name = get_player_name(sender_id)
if message.length() > 200:
message = message.substr(0, 200)
rpc("receive_chat_message", player_name, message)
puppet func receive_chat_message(sender_name: String, message: String):
$ChatDisplay.append_text("[%s]: %s" % [sender_name, message])
反作弊基础
# 服务器权威验证
master func submit_shoot(from_pos: Vector2, direction: Vector2):
var sender_id = get_tree().get_rpc_sender_id()
var player = get_player_node(sender_id)
if player == null:
return
# 验证位置
if player.global_position.distance_to(from_pos) > 50.0:
return # 位置异常
# 验证射击频率
var now = OS.get_ticks_msec()
if now - player.last_shoot_time < 100:
return # 射击过快
player.last_shoot_time = now
rpc("execute_shoot", sender_id, from_pos, direction)
master func submit_move(direction: Vector2, delta: float):
var sender_id = get_tree().get_rpc_sender_id()
if direction.length() > 1.1:
direction = direction.normalized() # 限制速度
常见作弊手段与对策
| 作弊手段 | 对策 |
|---|---|
| 加速移动 | 服务器验证移动速度上限 |
| 无冷却攻击 | 服务器计时,不信任客户端 |
| 修改伤害值 | 服务器计算伤害 |
| 透视/自瞄 | 服务器不发送不可见对象的数据 |
⚠️ 注意:反作弊的核心原则是 永远不信任客户端。所有影响游戏结果的计算都应该在服务器端完成。
扩展阅读
💡 总结:权威服务器 + RPC 是 Godot 3 多人联网的基础模式。
rpc_unreliable适合高频更新(位置),rpc适合关键事件(射击、伤害)。永远不要信任客户端。