Актуальный сетевой стек (TCP, JSON,
s.run(..., multiplayer=True),MultiplayerContext, лобби) описан в networking_guide.md. Этот файл — справка по модулюspritePro.multiplayerи связанным типам; при расхождении с поведением кода ориентируйтесь наnetworking_guideи исходники.
Модуль multiplayer.py предоставляет функциональность для создания многопользовательских игр с поддержкой сетевого взаимодействия.
Система мультиплеера позволяет:
- Создавать игровые серверы
- Подключать клиентов к серверу
- Синхронизировать состояние игры между игроками
- Обрабатывать события подключения/отключения
┌─────────────┐ ┌─────────────┐
│ Server │◄───────►│ Client │
│ (Host) │ TCP │ (Player) │
└─────────────┘ └─────────────┘
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ Game State │◄───────►│ Game State │
│ (Server) │ Sync │ (Local) │
└─────────────┘ └─────────────┘
from spritePro.multiplayer import MultiplayerManager
mp = MultiplayerManager()Запуск сервера (хоста).
Параметры:
port(int) — порт для подключенияmax_players(int) — максимальное количество игроков
mp.host(port=5000, max_players=4)Подключение к серверу как клиент.
Параметры:
address(str) — IP адрес сервераport(int) — порт сервера
mp.join("192.168.1.100", port=5000)Отключение от сервера или остановка хоста.
mp.disconnect()Отправка данных другим игрокам.
Параметры:
data— данные для отправки (dict, list и т.д.)reliable(bool) — надёжная доставка (TCP)
mp.send_data({
"type": "player_move",
"x": player.x,
"y": player.y
})Широковещательная рассылка данных всем игрокам.
mp.broadcast({"type": "chat", "message": "Привет!"})Обновление сетевого состояния.
Полная остановка мультиплеера.
| Свойство | Тип | Описание |
|---|---|---|
is_host |
bool | Является ли текущий клиент хостом |
is_connected |
bool | Установлено ли соединение |
players |
dict | Словарь подключенных игроков |
player_id |
int | ID текущего игрока |
latency |
float | Задержка в миллисекундах |
from spritePro.multiplayer import NetworkPlayer
player = NetworkPlayer(player_id=1, name="Игрок1")| Свойство | Тип | Описание |
|---|---|---|
id |
int | Уникальный ID игрока |
name |
str | Имя игрока |
x |
float | Позиция по X |
y |
float | Позиция по Y |
is_ready |
bool | Готов ли игрок |
ping |
int | Пинг игрока |
def on_player_joined(player):
print(f"Игрок {player.name} подключился")
def on_player_left(player):
print(f"Игрок {player.name} отключился")
def on_data_received(player_id, data):
print(f"Получены данные от игрока {player_id}: {data}")mp.on_player_joined = on_player_joined
mp.on_player_left = on_player_left
mp.on_data_received = on_data_receivedНачиная с версии 3.9.0, в SpritePro внедрена система сетевых декораторов, которая позволяет максимально упростить написание мультиплеерного кода. Эта система вдохновлена Unity Mirror.
Больше не нужно вручную читать self.ctx.poll() и писать длинные пакеты if msg.get("event") == ....
Указывает, что функция вызывается на Клиенте, но отправляется и исполняется только на Сервере (Хосте). Это идеальное место для проверки правил игры (спавн пули, покупка предмета).
import spritePro as s
@s.Command
def request_spawn_bullet(sender_id, pos, direction):
# sender_id 자동으로 подставляется системой (это ID клиента)
print(f"Клиент {sender_id} хочет выстрелить из {pos}")
# Сервер решает, что можно стрелять, и говорит всем клиентам:
do_spawn_bullet_rpc(pos, direction)Указывает, что функция вызывается на Сервере (Хосте), но отправляется и исполняется на всех Клиентах. Служит для визуальных эффектов: создание пули, проигрывание звука, обновление счета у всех на экранах.
@s.ClientRpc
def do_spawn_bullet_rpc(pos, direction):
# Этот код сработает на каждом компьютере
bullet = s.Sprite("bullet.png", pos=pos)
bullet.velocity = directionУниверсальный слушатель. Перехватывает любые сообщения с указанным именем (отправленные через ctx.send_every или ctx.net.send).
Обычно используется для частых обновлений, вроде координат.
@s.NetEvent("sync_pos")
def on_player_move(sender_id, pos):
print(f"Игрок {sender_id} передвинулся в {pos}")При подключении сервер автоматически ведет список всех игроков. Получить доступ к информации об игроках можно через s.multiplayer_ctx.players.
Это словарь, где ключ — client_id (int), а значение — словарь с информацией (например, name).
# Если мы Хост, выведем список всех, кто в лобби
if s.multiplayer_ctx.is_host:
for cid, info in s.multiplayer_ctx.players.items():
print(f"ID: {cid}, Ник: {info.get('name')}")from spritePro import SpritePro from spritePro.multiplayer import MultiplayerManager, NetworkPlayer
class NetworkGame(SpritePro): def init(self): super().init("Сетевая Игра", 800, 600) self.mp = MultiplayerManager() self.players = {} self.local_player = None
def host_game(self):
self.mp.host(port=5000, max_players=4)
self.setup_handlers()
self.create_local_player(1, "Хост")
def join_game(self, address):
self.mp.join(address, port=5000)
self.setup_handlers()
def setup_handlers(self):
self.mp.on_player_joined = self.on_player_joined
self.mp.on_player_left = self.on_player_left
self.mp.on_data_received = self.on_data_received
def on_player_joined(self, player):
self.players[player.id] = player
print(f"{player.name} присоединился")
def on_player_left(self, player):
if player.id in self.players:
del self.players[player.id]
print(f"{player.name} покинул игру")
def on_data_received(self, player_id, data):
if player_id in self.players:
player = self.players[player_id]
if data["type"] == "position":
player.x = data["x"]
player.y = data["y"]
def on_update(self, dt):
self.mp.update(dt)
if self.local_player and self.mp.is_connected:
self.mp.send_data({
"type": "position",
"x": self.local_player.x,
"y": self.local_player.y
})
### Синхронизация состояния
```python
class GameState:
def __init__(self):
self.players = {}
self.projectiles = []
self.items = []
def serialize(self):
return {
"players": {
pid: {
"x": p.x,
"y": p.y,
"health": p.health
}
for pid, p in self.players.items()
},
"projectiles": self.projectiles,
"items": self.items
}
def apply_state(self, state):
self.players = {
int(pid): data for pid, data in state["players"].items()
}
self.projectiles = state["projectiles"]
self.items = state["items"]
class ServerGame(MultiplayerManager):
def __init__(self):
super().__init__()
self.game_state = GameState()
def broadcast_state(self):
state = self.game_state.serialize()
self.broadcast({
"type": "state_sync",
"state": state
})
def on_data_received(self, player_id, data):
if data["type"] == "player_action":
self.handle_player_action(player_id, data)
self.broadcast_state()class GameEvent:
PLAYER_JOIN = "player_join"
PLAYER_LEAVE = "player_leave"
PLAYER_MOVE = "player_move"
PLAYER_ACTION = "player_action"
CHAT_MESSAGE = "chat_message"
GAME_STATE = "game_state"def send_player_move(self, x, y):
self.mp.send_data({
"event": GameEvent.PLAYER_MOVE,
"x": x,
"y": y
})
def send_chat(self, message):
self.mp.broadcast({
"event": GameEvent.CHAT_MESSAGE,
"sender": self.mp.player_id,
"message": message
})class InterpolatedPlayer:
def __init__(self):
self.current_pos = (0, 0)
self.target_pos = (0, 0)
self.interpolation_speed = 10
def update(self, dt, new_target):
self.target_pos = new_target
def draw(self, surface):
self.current_pos = lerp(
self.current_pos,
self.target_pos,
self.interpolation_speed * dt
)
# Отрисовкаclass NetworkPrediction:
def predict_local_player(self, input_data):
predicted_state = self.current_state.copy()
for action in input_data:
predicted_state = self.apply_action(predicted_state, action)
return predicted_state
def reconcile(self, server_state):
if self.predicted_state != server_state:
self.current_state = server_state
self.replay_inputs()class DeltaCompression:
def __init__(self):
self.last_state = {}
def compress(self, current_state):
delta = {}
for key, value in current_state.items():
if key not in self.last_state or self.last_state[key] != value:
delta[key] = value
self.last_state = current_state.copy()
return deltadef safe_send(self, data, player_id=None):
try:
if player_id:
self.send_to(player_id, data)
else:
self.broadcast(data)
except ConnectionError:
self.handle_disconnect()
except TimeoutError:
self.handle_timeout(player_id)- Ограничивайте частоту обновлений — не отправляйте данные каждый кадр
- Используйте Delta Compression — отправляйте только изменения
- Применяйте клиентскую авторизацию — доверяйте данным с сервера
- Обрабатывайте отключения — корректно удаляйте игроков
- Тестируйте с задержкой — симулируйте плохие условия сети