强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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任意一端另一端通用远程调用
masterpuppet 端master 端只有 master 才执行
puppetmaster 端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 适合关键事件(射击、伤害)。永远不要信任客户端。