You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CHANGELOG.md
+12-2Lines changed: 12 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -8,11 +8,21 @@
8
8
## [Unreleased]
9
9
10
10
### Планируется
11
-
- Система инвентаря
12
11
- Продвинутые UI компоненты
13
-
- Система частиц
14
12
- Мобильная поддержка
15
13
14
+
## [1.3.3]
15
+
16
+
### Added
17
+
-**docs/networking.md**: раздел «Лучшие практики» — примитивы JSON (что можно отправлять), позиция списком `list(pos)` и приём через `data.get("pos", ...)`, значения по умолчанию, троттлинг.
18
+
-**docs/networking.md**: таблица полей контекста, «Кто такой этот процесс», «Отображаемое имя», «Как отличить своего игрока от чужих».
19
+
20
+
### Changed
21
+
-**Позиция по сети**: во всех примерах и курсе — отправка `{"pos": list(pos)}`, приём `remote_pos[:] = data.get("pos", ...)`; убраны лишние `float()` и поля `x`/`y`.
22
+
-**Лобби (урок 4)**: при отправке `join` добавляем себя в `players` локально (`players.add(name)`), чтобы хост и все клиенты видели полный список (реле не отдаёт сообщение отправителю).
23
+
-**Демо и курс**: упрощена обработка `sender_id` (без лишних try/except и int()), компактное получение `other_id = data.get("sender_id")` где уместно.
-`run()` не работает в интерактивной консоли — нужен файл.
214
214
- Если `poll()` не вызывается, сообщения копятся в очереди.
215
215
216
+
## Лучшие практики
217
+
218
+
### Что можно отправлять по сети (примитивы JSON)
219
+
220
+
Сообщения сериализуются в **JSON**. В `data` можно передавать только то, что умеет JSON:
221
+
222
+
-**Числа** — `int`, `float`
223
+
-**Строки** — `str`
224
+
-**Списки и словари** — вложенные структуры из перечисленного
225
+
-**Булевы** — `True` / `False`
226
+
-**Пусто** — `None` (в JSON будет `null`)
227
+
228
+
Объекты вроде `Vector2`, спрайтов, классов — **нельзя** отправлять как есть. Конвертируйте в примитивы: позиция → `list(pos)` или `[pos.x, pos.y]`, состояние → `dict` с полями. На приёме вы получаете уже числа/списки/словари — приводить к `float()` не нужно.
229
+
230
+
### Позиция удалённого игрока: один список, обновление на месте
231
+
232
+
Один список на удалённую позицию и обновление через срез — не пересоздаём объект и не теряем ссылку для `set_position()`. Позицию удобно отправлять списком: на приёме одно присваивание. `get_world_position()` возвращает `Vector2`; он итерируем, поэтому подойдёт `list(pos)`:
По умолчанию при отсутствии ключа можно подставить предыдущее значение: `data.get("pos", remote_pos)` — тогда при пропуске пакета позиция не дёрнется. Вариант с полями `{"x": pos.x, "y": pos.y}` тоже допустим; на приёме тогда `remote_pos[:] = [data.get("x", 0), data.get("y", 0)]`.
248
+
249
+
### Данные из JSON — уже нужные типы
250
+
251
+
После `json.loads` поля приходят как числа, строки, списки, словари. Приводить к `float()` или `int()` вручную не нужно — используйте значения как есть: `data.get("x", 0)`, `data.get("sender_id")`.
252
+
253
+
### Значения по умолчанию при разборе payload
254
+
255
+
-`data.get("key", default)` — если ключа нет или событие пришло без поля, подставится `default`. Для позиции удобны `0` или текущее значение (`remote_pos[0]`).
256
+
- Для вложенных структур: `data.get("scores", self.scores)` — не перезаписываем локальное состояние, если в сообщении пусто.
257
+
- Если вы сами всегда отправляете полный payload (например, `pos` с `x`, `y`, `sender_id`), на приёме можно не проверять на `None` — присваивайте `other_id = data.get("sender_id")` и при отображении обработайте `other_id is None` (например, показать «?»).
258
+
259
+
### Троттлинг позиции
260
+
261
+
Используйте `ctx.send_every("pos", {"pos": list(pos)}, interval)` вместо `ctx.send("pos", ...)` в каждом кадре — так вы ограничите частоту отправки (например, 20 раз/сек при `interval=0.05`) и снизите нагрузку на сеть.
-`client_id` (0 для хоста, для клиентов назначается сервером по порядку);
253
-
-`role` и `is_host`:
254
-
-`role="host"` — процесс, который поднимает сервер (host‑режим), `is_host=True`,
255
-
-`role="client"` — обычный клиент,
256
-
-`role="server"` — сервер без клиента (в этом режиме контекст не создаётся).
257
-
Значение роли задаётся `run()` через параметры запуска (`--host_mode`, `--quick`, `--server`).
258
-
-`state` для глобальных значений;
259
-
-`seed` и `random` для детерминированного случайного генератора;
260
-
- методы `send()`, `poll()`, `send_every()`.
297
+
### Кто такой «этот процесс» (наш компьютер)
298
+
299
+
Код `multiplayer_main` выполняется **в одном из процессов** — в том же окне, что открылось при запуске. Этот процесс и есть «наш компьютер»: мы не «получаем» его с сервера, мы в нём уже находимся. Чтобы понять, хост мы или клиент и какой у нас номер, используйте поля контекста после `init_context()`.
300
+
301
+
### Поля контекста и возможные значения
302
+
303
+
| Поле / метод | За что отвечает | Возможные значения |
|`ctx.client_id`| Числовой ID этого участника. Уникален в сессии. |`0` — хост; `1`, `2`, … — клиенты (назначаются сервером по порядку подключения). |
306
+
|`ctx.role`| Роль процесса в сети. |`"host"` — процесс с поднятым сервером (одно окно); `"client"` — обычный клиент; `"server"` — только сервер без игры (контекст не создаётся). |
307
+
|`ctx.is_host`| Является ли этот процесс хостом. |`True` — мы хост, `False` — мы клиент. Удобно для ветвления логики («если хост — рассылаю roster»). |
308
+
|`ctx.state`| Произвольный словарь для глобального состояния. | Любой dict, общий на весь процесс. |
309
+
|`ctx.seed` / `ctx.random`| Детерминированный рандом для мультиплеера. | См. раздел «Детерминированный рандом». |
310
+
|`ctx.send(event, data)`| Отправка сообщения в сеть. | — |
311
+
|`ctx.poll()`| Получение входящих сообщений. | Список dict с полями `event`, `data`. |
312
+
|`ctx.send_every(event, data, interval)`| Отправка не чаще чем раз в `interval` секунд. | — |
313
+
314
+
Роль задаётся при запуске: `run()` передаёт в `multiplayer_main` аргумент `role` в зависимости от режима (`--host_mode`, `--quick`, `--server`). Контекст только отражает уже выбранную роль.
315
+
316
+
### Отображаемое имя участника (name)
317
+
318
+
В примерах часто встречается переменная вроде `name = "host" if ctx.is_host else f"client_{client_index + 1}"`. Это **не** «получение сервера» и не данные с сети: это **локально выбранное отображаемое имя** для этого процесса (чтобы в лобби показывать «host», «client_1», «client_2»). Номер клиента для имени можно взять из `os.environ.get("SPRITEPRO_NET_INDEX", "0")` (индекс окна клиента) или строить логику по `ctx.client_id` (0 у хоста, 1 и выше у клиентов). Сервер имён не выдаёт — имя задаётся в коде под текущую роль и ID.
319
+
320
+
### Как отличить своего игрока от чужих (цвет, камера)
321
+
322
+
«Наш» экземпляр игры — это **этот процесс**: мы не получаем его по сети, мы в нём. В этом процессе один спрайт обновляется от **локального ввода** (клавиатура, мышь) и его позиция **отправляется** в сеть — это «я». Остальные спрайты обновляются из `ctx.poll()` — это «другие». Цвет задаём в коде: свой — один (например синий), чужие — другой (красный).
323
+
324
+
```python
325
+
ctx = s.multiplayer_ctx
326
+
me = s.Sprite("", (40, 40), (200, 300)) # двигаем мы, шлём позицию в сеть
327
+
other = s.Sprite("", (40, 40), (600, 300)) # позицию получаем из ctx.poll()
328
+
329
+
# В этом процессе «я» всегда один и тот же — тот, кого мы контролируем.
330
+
MY_COLOR= (70, 120, 220) # синий
331
+
OTHER_COLOR= (220, 70, 70) # красный
332
+
me.set_color(MY_COLOR)
333
+
other.set_color(OTHER_COLOR)
334
+
335
+
# В игровом цикле: движение me от s.input, отправка ctx.send("pos", ...);
336
+
# other.set_position(...) из данных, пришедших в ctx.poll().
337
+
```
338
+
339
+
У каждого участника в своём окне «я» синий, «другие» красные — так и задумано: в своём экземпляре игры свой персонаж выделен. Для нескольких чужих игроков храните словарь `others[client_id]` и задайте им один цвет «чужой» или разные по `client_id`.
0 commit comments