~2 мин чтения

Мультиплеер — MultiplayerAPI и RPC

ENet/WebSocket/WebRTC peers, RPC, MultiplayerSpawner и Synchronizer.

Godot имеет встроенный high-level мультиплеер: MultiplayerAPI + транспорты + узлы Spawner/Synchronizer. Это аналог Unity Netcode for GameObjects.

Архитектура

MultiplayerAPI работает поверх MultiplayerPeer. Транспорты:

  • ENetMultiplayerPeer — UDP, основной для нативных платформ.
  • WebSocketMultiplayerPeer — TCP/WebSocket, единственный путь для веб-таргета.
  • WebRTCMultiplayerPeer — peer-to-peer, NAT traversal через STUN/TURN.
  • OfflineMultiplayerPeer — заглушка для single-player.

Стандартная модель — authoritative server: один peer ID=1 (сервер), остальные — клиенты с ID >= 2.

Старт сервера и клиента

extends Node

const PORT := 7777
const MAX_PEERS := 4

func host_game() -> Error:
    var peer = ENetMultiplayerPeer.new()
    var err = peer.create_server(PORT, MAX_PEERS)
    if err != OK:
        return err
    multiplayer.multiplayer_peer = peer
    print("Server up on port ", PORT)
    return OK

func join_game(ip: String) -> Error:
    var peer = ENetMultiplayerPeer.new()
    var err = peer.create_client(ip, PORT)
    if err != OK:
        return err
    multiplayer.multiplayer_peer = peer
    print("Connecting to ", ip)
    return OK

multiplayer — встроенный геттер MultiplayerAPI на любом узле.

Сигналы MultiplayerAPI:

  • peer_connected(id) / peer_disconnected(id) — на сервере для каждого клиента.
  • connected_to_server / connection_failed / server_disconnected — на клиенте.
func _ready() -> void:
    multiplayer.peer_connected.connect(_on_peer_joined)
    multiplayer.peer_disconnected.connect(_on_peer_left)

func _on_peer_joined(id: int) -> void:
    print("Peer ", id, " joined")
    if multiplayer.is_server():
        spawn_player(id)

RPC — удалённые вызовы

GDScript использует annotation @rpc на функциях, которые могут вызываться удалённо:

extends Node3D

# from_who: any_peer | authority
# transfer: reliable | unreliable | unreliable_ordered
# call_local: вызывать ли локально при удалённом вызове
@rpc("any_peer", "reliable", "call_local")
func shoot(direction: Vector3) -> void:
    spawn_bullet(direction)

# Сервер сообщает всем (call → этот метод сработает на каждом клиенте)
@rpc("authority", "reliable")
func play_explosion_sound(at: Vector3) -> void:
    var player := AudioStreamPlayer3D.new()
    player.stream = preload("res://audio/explosion.ogg")
    player.global_position = at
    add_child(player)
    player.play()
    player.finished.connect(player.queue_free)

Вызов:

# Из клиента → серверу (если remote method имеет "authority"):
rpc("shoot", direction)

# Только на конкретный peer:
rpc_id(target_peer_id, "play_explosion_sound", pos)

# Конкретно на сервер:
rpc_id(1, "do_something")

Параметры @rpc:

  • From who: any_peer (любой клиент может вызвать) или authority (только владелец узла).
  • Transfer: reliable (TCP-стиль), unreliable (UDP без гарантий), unreliable_ordered.
  • Call local: вызвать ли локально кроме remote.
  • Channel: номер канала (для параллельной отправки разных типов сообщений).
Validate всё на сервере

В authoritative-модели сервер — источник правды. Не доверяйте параметрам RPC от клиента (любой клиент может прислать damage = 99999). Сервер должен валидировать: проверить расстояние выстрела, кулдаун, права.

Multiplayer Authority

У каждого узла есть authority — peer ID, который “владеет” узлом. По умолчанию — 1 (сервер). Можно передать клиенту:

# На сервере: дать peer'у управление его игроком
var player = player_scene.instantiate()
player.name = str(peer_id)  # имя для уникальности
add_child(player, true)
player.set_multiplayer_authority(peer_id)

Внутри скрипта:

func _physics_process(delta: float) -> void:
    if not is_multiplayer_authority():
        return  # только владелец двигает себя
    # ... handle input

MultiplayerSpawner

Узел, который автоматически реплицирует спавн дочерних узлов от сервера к клиентам. Не нужно вручную слать RPC “create_enemy_at”.

World
├── Players (Node)         ← дети спавнятся для каждого игрока
│   └── MultiplayerSpawner ← spawn_path = "../Players"
│                            spawnable_scenes = [Player.tscn]

На сервере вы делаете players.add_child(player). Спавнер ловит это и отправляет всем клиентам, которые создают такой же узел у себя локально.

MultiplayerSynchronizer

Декларативная синхронизация properties. Прикрепляете к узлу и в Inspector указываете список properties (например, position, rotation, hp). MultiplayerAPI автоматически шлёт изменения от authority к остальным.

Player (CharacterBody3D, authority = peer_id)
├── ... 
└── MultiplayerSynchronizer
    Replication Config:
       :position           [Always]
       :rotation:y         [Always]
       hp                  [On Change]
       state               [On Change]

Replication Mode:

  • Always — каждый тик.
  • On Change — при изменении значения.
  • Never — не реплицировать (для visibility-фильтрации).

Это убирает 80% кода ручной синхронизации, который пришлось бы писать на RPC. Аналог Unity NetworkVariable, но декларативный.

Сравнение с Unity Netcode

КонцепцияUnity NGOGodot
Сетевая сущностьNetworkObjectУзел с authority
СпавнSpawn() через NetworkObjectMultiplayerSpawner
Sync propertyNetworkVariable<T>MultiplayerSynchronizer
RPC (clent→server)[ServerRpc]@rpc("any_peer") + rpc_id(1, ...)
RPC (server→clients)[ClientRpc]@rpc("authority")
ТранспортUnityTransportENetMultiplayerPeer
RelayUnity RelayСвоё (или сторонние, например Nakama)
MatchUnity LobbyСвоё

Веб

Только через WebSocketMultiplayerPeer:

var peer = WebSocketMultiplayerPeer.new()
peer.create_server(8080)
# или peer.create_client("ws://example.com:8080")
multiplayer.multiplayer_peer = peer

ENet не работает в браузере. WebRTC требует STUN/TURN-серверов для NAT traversal.

Подводные камни

  1. Не путайте локальный multiplayer с сетевым. Split-screen — другая задача, через viewports и PlayerInput источники.
  2. call_local — забывают, в итоге локально метод не срабатывает. Если хотите, чтобы “и у себя выполнилось, и удалённо” — включайте.
  3. MultiplayerSynchronizer требует, чтобы узел существовал у всех клиентов. Используйте совместно с Spawner.
  4. Order matters при спавне: если сервер add_child(player) после set_multiplayer_authority, то authority успеет реплицироваться. Иначе наоборот.

В следующей главе — загрузка ресурсов и Resource Loader.