Мультиплеер — 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: номер канала (для параллельной отправки разных типов сообщений).
В 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 NGO | Godot |
|---|---|---|
| Сетевая сущность | NetworkObject | Узел с authority |
| Спавн | Spawn() через NetworkObject | MultiplayerSpawner |
| Sync property | NetworkVariable<T> | MultiplayerSynchronizer |
| RPC (clent→server) | [ServerRpc] | @rpc("any_peer") + rpc_id(1, ...) |
| RPC (server→clients) | [ClientRpc] | @rpc("authority") |
| Транспорт | UnityTransport | ENetMultiplayerPeer |
| Relay | Unity Relay | Своё (или сторонние, например Nakama) |
| Match | Unity 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.
Подводные камни
- Не путайте локальный multiplayer с сетевым. Split-screen — другая задача, через viewports
и
PlayerInputисточники. call_local— забывают, в итоге локально метод не срабатывает. Если хотите, чтобы “и у себя выполнилось, и удалённо” — включайте.- MultiplayerSynchronizer требует, чтобы узел существовал у всех клиентов. Используйте совместно с Spawner.
- Order matters при спавне: если сервер
add_child(player)послеset_multiplayer_authority, то authority успеет реплицироваться. Иначе наоборот.
В следующей главе — загрузка ресурсов и Resource Loader.