Как создать многопользовательскую игру в реальном времени с нуля
Опубликовано: 2022-03-10По мере того, как пандемия продолжалась, внезапно удаленная команда, с которой я работаю, становилась все более лишенной возможности играть в настольный футбол. Я думал о том, как играть в настольный футбол в удаленной обстановке, но было ясно, что просто воссоздать правила настольного футбола на экране будет не очень весело.
Что весело, так это пинать мяч с помощью игрушечных машинок. Я осознал это, когда играл со своим 2-летним ребенком. В ту же ночь я приступил к созданию первого прототипа игры, которая впоследствии стала Autowuzzler .
Идея проста : игроки управляют виртуальными игрушечными машинками на арене с видом сверху, которая напоминает настольный футбол. Побеждает команда, первой забившая 10 голов.
Конечно, идея использования автомобилей для игры в футбол не уникальна, но две основные идеи должны выделить Autowuzzler : я хотел воссоздать внешний вид и ощущения от игры на физическом настольном футболе, и я хотел убедиться, что это действительно так. как можно проще пригласить друзей или товарищей по команде в быструю казуальную игру.
В этой статье я опишу процесс создания Autowuzzler , какие инструменты и фреймворки я выбрал, а также поделюсь некоторыми деталями реализации и извлеченными уроками.
Первый рабочий (ужасный) прототип
Первый прототип был построен с использованием игрового движка с открытым исходным кодом Phaser.js, в основном для включенного физического движка и потому, что у меня уже был некоторый опыт работы с ним. Стадия игры была встроена в приложение Next.js, опять же потому, что я уже хорошо разбирался в Next.js и хотел сосредоточиться в основном на игре.
Поскольку игра должна поддерживать несколько игроков в режиме реального времени , я использовал Express в качестве брокера WebSockets. Однако здесь становится сложно.
Поскольку расчеты физики производились на клиенте в игре Phaser, я выбрал простую, но явно ущербную логику: первый подключившийся клиент имел сомнительную привилегию выполнять расчеты физики для всех игровых объектов, отправляя результаты на экспресс-сервер, который, в свою очередь, передавал обновленные позиции, углы и силы клиентам другого игрока. Затем другие клиенты применяли изменения к игровым объектам.
Это привело к тому, что первый игрок мог видеть физику в реальном времени (в конце концов, это происходит локально в его браузере), в то время как все остальные игроки отставали как минимум на 30 миллисекунд (скорость трансляции, которую я выбрал). ), или — если сетевое соединение первого игрока было медленным — значительно хуже.
Если вам это кажется плохой архитектурой — вы абсолютно правы. Тем не менее, я принял этот факт в пользу быстрого получения чего-то играбельного, чтобы выяснить, действительно ли в игру интересно играть.
Подтвердите идею, выбросьте прототип
Какой бы ущербной ни была реализация, в нее можно было пригласить друзей на первый тест-драйв. Отзывы были очень положительными , и неудивительно, что основной проблемой была производительность в реальном времени. Среди других неотъемлемых проблем была ситуация, когда первый игрок (помните, тот, кто отвечает за все ) вышел из игры — кто должен взять на себя управление? На тот момент была только одна игровая комната, поэтому любой мог присоединиться к одной и той же игре. Меня также немного беспокоил размер пакета, представленного библиотекой Phaser.js.
Пришло время отказаться от прототипа и начать с новой установки и четкой цели.
Настройка проекта
Очевидно, что подход «первый клиент правит всем» необходимо заменить решением, в котором игровое состояние живет на сервере . В своих исследованиях я наткнулся на Colyseus, который показался мне идеальным инструментом для работы.
Для других основных строительных блоков игры я выбрал:
- Matter.js в качестве физического движка вместо Phaser.js, потому что он работает в Node, а Autowuzzler не требует полной игровой среды.
- SvelteKit в качестве фреймворка для приложений вместо Next.js, потому что в то время он только что вышел в публичную бета-версию. (Кроме того: мне нравится работать со Svelte.)
- Supabase.io для хранения игровых PIN-кодов, созданных пользователями.
Давайте рассмотрим эти строительные блоки более подробно.
Синхронизированное централизованное состояние игры с помощью Colyseus
Colyseus — это многопользовательская игровая среда, основанная на Node.js и Express. По своей сути он обеспечивает:
- Синхронизация состояния между клиентами авторитетным способом;
- Эффективная связь в режиме реального времени с использованием WebSockets путем отправки только измененных данных;
- Многокомнатные установки;
- Клиентские библиотеки для JavaScript, Unity, Defold Engine, Haxe, Cocos Creator, Construct3;
- Крючки жизненного цикла, например создание комнаты, присоединение пользователя, выход пользователя и т. д.;
- Отправка сообщений в виде широковещательных сообщений всем пользователям в комнате или одному пользователю;
- Встроенная панель мониторинга и инструмент для нагрузочного тестирования.
Примечание . Документация Colyseus упрощает начало работы с базовым сервером Colyseus, предоставляя сценарий npm init
и репозиторий примеров.
Создание схемы
Главной сущностью приложения Colyseus является игровая комната, в которой хранится состояние отдельного экземпляра комнаты и всех ее игровых объектов. В случае Autowuzzler это игровая сессия с:
- две команды,
- конечное количество игроков,
- один мяч.
Схема должна быть определена для всех свойств игровых объектов, которые должны быть синхронизированы между клиентами . Например, мы хотим, чтобы мяч синхронизировался, поэтому нам нужно создать схему для мяча:
class Ball extends Schema { constructor() { super(); this.x = 0; this.y = 0; this.angle = 0; this.velocityX = 0; this.velocityY = 0; } } defineTypes(Ball, { x: "number", y: "number", angle: "number", velocityX: "number", velocityY: "number" });
В приведенном выше примере создается новый класс, расширяющий класс схемы, предоставленный Colyseus; в конструкторе все свойства получают начальное значение. Положение и движение мяча описываются с помощью пяти свойств: x
, y
, angle
, velocityX,
X, velocityY
Y . Кроме того, нам нужно указать типы каждого свойства . В этом примере используется синтаксис JavaScript, но вы также можете использовать немного более компактный синтаксис TypeScript.
Типы свойств могут быть примитивными типами:
-
string
-
boolean
-
number
(а также более эффективные типы integer и float)
или сложные типы:
-
ArraySchema
(аналогично Array в JavaScript) -
MapSchema
(аналог Map в JavaScript) -
SetSchema
(аналогично Set в JavaScript) -
CollectionSchema
(аналогична ArraySchema, но без контроля над индексами)
Приведенный выше класс Ball
имеет пять свойств типа number
: его координаты ( x
, y
), его текущий angle
и вектор скорости ( velocityX
, velocityY
).
Схема для игроков аналогична, но включает еще несколько свойств для хранения имени игрока и номера команды, которые необходимо указать при создании экземпляра Player:
class Player extends Schema { constructor(teamNumber) { super(); this.name = ""; this.x = 0; this.y = 0; this.angle = 0; this.velocityX = 0; this.velocityY = 0; this.teamNumber = teamNumber; } } defineTypes(Player, { name: "string", x: "number", y: "number", angle: "number", velocityX: "number", velocityY: "number", angularVelocity: "number", teamNumber: "number", });
Наконец, схема для Room
Autowuzzler соединяет ранее определенные классы: экземпляр одной комнаты имеет несколько команд (хранится в ArraySchema). Он также содержит один мяч, поэтому мы создаем новый экземпляр Ball в конструкторе RoomSchema. Игроки хранятся в MapSchema для быстрого поиска по их идентификаторам.
Многокомнатная установка («Match-Making»)
Любой может присоединиться к игре Autowuzzler , если у него есть действующий игровой PIN-код. Наш сервер Colyseus создает новый экземпляр комнаты для каждой игровой сессии, как только первый игрок присоединяется к ней, и удаляет комнату, когда ее покидает последний игрок.
Процесс назначения игроков в желаемую игровую комнату называется «матчмейкинг». Colyseus упрощает настройку, используя метод filterBy
при определении новой комнаты:
gameServer.define("autowuzzler", AutowuzzlerRoom).filterBy(['gamePIN']);
Теперь все игроки, присоединившиеся к игре с одним и тем же gamePIN
-кодом (позже мы увидим, как «присоединиться»), окажутся в одной и той же игровой комнате! Любые обновления состояния и другие широковещательные сообщения доступны только игрокам, находящимся в одной комнате.
Физика в приложении Colyseus
Colyseus предлагает множество готовых решений, позволяющих быстро начать работу с авторитетным игровым сервером, но оставляет на усмотрение разработчика создание реальной игровой механики, включая физику. Phaser.js, который я использовал в прототипе, не может выполняться в небраузерной среде, но интегрированный физический движок Phaser.js Matter.js может работать на Node.js.
С Matter.js вы определяете физический мир с определенными физическими свойствами, такими как его размер и гравитация. Он предоставляет несколько методов для создания примитивных физических объектов, которые взаимодействуют друг с другом, придерживаясь (симулированных) законов физики, включая массу, столкновения, движение с трением и так далее. Вы можете перемещать объекты, применяя силу , как в реальном мире.
«Мир» Matter.js лежит в основе игры Autowuzzler ; он определяет, насколько быстро движутся машины, насколько упругим должен быть мяч, где расположены ворота и что произойдет, если кто-то забьет гол.
let ball = Bodies.circle( ballInitialXPosition, ballInitialYPosition, radius, { render: { sprite: { texture: '/assets/ball.png', } }, friction: 0.002, restitution: 0.8 } ); World.add(this.engine.world, [ball]);
Упрощенный код для добавления игрового объекта «мяч» на сцену в Matter.js.
Как только правила определены, Matter.js может работать с фактическим рендерингом чего-либо на экране или без него. Для Autowuzzler я использую эту функцию для повторного использования кода физического мира как для сервера, так и для клиента — с несколькими ключевыми отличиями:
Физический мир на сервере :
- получает пользовательский ввод (события клавиатуры для управления автомобилем) через Colyseus и применяет соответствующую силу к игровому объекту (автомобилю пользователя);
- выполняет все расчеты физики для всех объектов (игроков и мяча), включая обнаружение столкновений;
- передает обновленное состояние для каждого игрового объекта обратно в Colyseus, который, в свою очередь, передает его клиентам;
- обновляется каждые 16,6 миллисекунд (= 60 кадров в секунду), инициируется нашим сервером Colyseus.
Мир физики на клиенте :
- не манипулирует игровыми объектами напрямую;
- получает обновленное состояние для каждого игрового объекта от Colyseus;
- применяет изменения положения, скорости и угла после получения обновленного состояния;
- отправляет пользовательский ввод (события клавиатуры для управления автомобилем) в Colyseus;
- загружает игровые спрайты и использует средство визуализации для рисования физического мира на элементе холста;
- пропускает обнаружение столкновений (используя опцию
isSensor
для объектов); - обновления с использованием requestAnimationFrame, в идеале со скоростью 60 кадров в секунду.
Теперь, когда все волшебство происходит на сервере, клиент обрабатывает только ввод и выводит на экран состояние, полученное от сервера. За одним исключением:
Интерполяция на клиенте
Поскольку мы повторно используем один и тот же физический мир Matter.js на клиенте, мы можем улучшить опытную производительность с помощью простого трюка. Мы не только обновляем положение игрового объекта, но и синхронизируем его скорость . Таким образом, объект продолжает двигаться по своей траектории, даже если следующее обновление с сервера занимает больше времени, чем обычно. Таким образом, вместо того, чтобы перемещать объекты дискретными шагами из положения А в положение В, мы изменяем их положение и заставляем их двигаться в определенном направлении.
Жизненный цикл
В классе Autowuzzler Room
обрабатывается логика, связанная с различными фазами комнаты Colyseus. Colyseus предоставляет несколько методов жизненного цикла:
-
onCreate
: при создании новой комнаты (обычно при подключении первого клиента); -
onAuth
: как хук авторизации для разрешения или запрета входа в комнату; -
onJoin
: когда клиент подключается к комнате; -
onLeave
: когда клиент отключается от комнаты; -
onDispose
: когда комната сбрасывается.
Комната Autowuzzler создает новый экземпляр физического мира (см. раздел «Физика в приложении Colyseus») сразу после его создания ( onCreate
) и добавляет игрока в мир при подключении клиента ( onJoin
). Затем он обновляет физический мир 60 раз в секунду (каждые 16,6 миллисекунды) с помощью метода setSimulationInterval
(наш основной игровой цикл):
// deltaTime is roughly 16.6 milliseconds this.setSimulationInterval((deltaTime) => this.world.updateWorld(deltaTime));
Физические объекты не зависят от объектов Колизея, что оставляет нам две перестановки одного и того же игрового объекта (например, мяча), то есть объект в физическом мире и объект Колизея, который можно синхронизировать.
Как только физический объект изменится, его обновленные свойства необходимо будет применить обратно к объекту Колизея. Мы можем добиться этого, прослушав событие afterUpdate afterUpdate
и установив оттуда значения:
Events.on(this.engine, "afterUpdate", () => { // apply the x position of the physics ball object back to the colyseus ball object this.state.ball.x = this.physicsWorld.ball.position.x; // ... all other ball properties // loop over all physics players and apply their properties back to colyseus players objects })
Есть еще одна копия объектов, о которых нам нужно позаботиться: игровые объекты в игре, ориентированной на пользователя .
Клиентское приложение
Теперь, когда у нас есть приложение на сервере, которое выполняет синхронизацию состояния игры для нескольких комнат, а также физические расчеты, давайте сосредоточимся на создании веб-сайта и фактического игрового интерфейса . Перед интерфейсом Autowuzzler стоят следующие задачи:
- позволяет пользователям создавать и обмениваться игровыми PIN-кодами для доступа к отдельным комнатам;
- отправляет созданные игровые PIN-коды в базу данных Supabase для сохранения;
- предоставляет необязательную страницу «Присоединиться к игре», где игроки могут ввести игровой PIN-код;
- проверяет игровые PIN-коды, когда игрок присоединяется к игре;
- размещает и отображает реальную игру по общедоступному (т.е. уникальному) URL-адресу;
- подключается к серверу Colyseus и обрабатывает обновления состояния;
- предоставляет целевую («маркетинговую») страницу.
Для реализации этих задач я выбрал SvelteKit вместо Next.js по следующим причинам:
Почему SvelteKit?
Я хотел разработать еще одно приложение с использованием Svelte с тех пор, как создал neolightsout. Когда SvelteKit (официальная платформа приложений для Svelte) перешла в общедоступную бета-версию, я решил создать Autowuzzler с его помощью и принять любые головные боли, связанные с использованием новой бета-версии — радость от использования Svelte явно компенсирует это.
Эти ключевые особенности заставили меня выбрать SvelteKit, а не Next.js для фактической реализации игрового интерфейса:
- Svelte — это инфраструктура пользовательского интерфейса и компилятор, поэтому он содержит минимальный код без клиентской среды выполнения;
- Svelte имеет выразительный язык шаблонов и систему компонентов (личные предпочтения);
- Svelte включает в себя глобальные хранилища, переходы и анимацию по умолчанию, что означает: отсутствие усталости от принятия решений при выборе инструментария глобального управления состоянием и библиотеки анимации;
- Svelte поддерживает CSS с ограниченной областью действия в однофайловых компонентах;
- SvelteKit поддерживает SSR, простую, но гибкую маршрутизацию на основе файлов и маршруты на стороне сервера для создания API;
- SvelteKit позволяет каждой странице запускать код на сервере, например, для получения данных, которые используются для отображения страницы;
- Макеты общие для маршрутов;
- SvelteKit можно запускать в бессерверной среде.
Создание и хранение игровых PIN-кодов
Прежде чем пользователь сможет начать играть в игру, ему сначала необходимо создать игровой PIN-код. Поделившись PIN-кодом с другими, все они могут получить доступ к одной и той же игровой комнате.
Это отличный вариант использования конечных точек SvelteKits на стороне сервера в сочетании с функцией Sveltes onMount: конечная точка /api/createcode
генерирует PIN-код игры, сохраняет его в базе данных Supabase.io и выводит PIN-код игры в качестве ответа . Этот ответ извлекается, как только монтируется компонент страницы «создать»:
Хранение игровых PIN-кодов с помощью Supabase.io
Supabase.io — это альтернатива Firebase с открытым исходным кодом. Supabase позволяет очень легко создать базу данных PostgreSQL и получить к ней доступ либо через одну из ее клиентских библиотек, либо через REST.
Для клиента JavaScript мы импортируем функцию createClient
и выполняем ее, используя параметры supabase_url
и supabase_key
, которые мы получили при создании базы данных. Чтобы сохранить игровой PIN-код , который создается при каждом вызове конечной точки createcode
, все, что нам нужно сделать, это запустить этот простой запрос на insert
:
import { createClient } from '@supabase/supabase-js' const database = createClient( import.meta.env.VITE_SUPABASE_URL, import.meta.env.VITE_SUPABASE_KEY ); const { data, error } = await database .from("games") .insert([{ code: 123456 }]);
Примечание . supabase_url
и supabase_key
хранятся в файле .env. Из-за Vite — инструмента сборки, лежащего в основе SvelteKit — необходимо добавлять к переменным среды префикс VITE_, чтобы сделать их доступными в SvelteKit.
Доступ к игре
Я хотел, чтобы присоединиться к игре Autowuzzler было так же просто, как перейти по ссылке. Следовательно, каждая игровая комната должна иметь собственный URL-адрес на основе ранее созданного игрового PIN-кода , например https://autowuzzler.com/play/12345.
В SvelteKit страницы с параметрами динамического маршрута создаются путем помещения динамических частей маршрута в квадратные скобки при именовании файла страницы: client/src/routes/play/[gamePIN].svelte
. Затем значение параметра gamePIN
станет доступным в компоненте страницы (подробности см. в документации SvelteKit). В процессе play
нам нужно подключиться к серверу Colyseus, создать экземпляр физического мира для рендеринга на экране, обрабатывать обновления игровых объектов, прослушивать ввод с клавиатуры и отображать другой пользовательский интерфейс, например счет, и так далее.
Подключение к Colyseus и обновление состояния
Клиентская библиотека Colyseus позволяет нам подключить клиента к серверу Colyseus. Во-первых, давайте создадим новый Colyseus.Client
, указав его на сервер Colyseus ( ws://localhost:2567
в разработке). Затем присоединитесь к комнате с именем, которое мы выбрали ранее ( autowuzzler
) и игровым PIN- gamePIN
из параметра маршрута. Параметр gamePIN
гарантирует, что пользователь присоединится к правильному экземпляру комнаты (см. «сватовство» выше).
let client = new Colyseus.Client("ws://localhost:2567"); this.room = await client.joinOrCreate("autowuzzler", { gamePIN });
Поскольку SvelteKit изначально отображает страницы на сервере, нам необходимо убедиться, что этот код запускается на клиенте только после завершения загрузки страницы. Опять же, мы используем функцию жизненного цикла onMount
для этого варианта использования. (Если вы знакомы с React, onMount
похож на хук useEffect
с пустым массивом зависимостей.)
onMount(async () => { let client = new Colyseus.Client("ws://localhost:2567"); this.room = await client.joinOrCreate("autowuzzler", { gamePIN }); })
Теперь, когда мы подключены к игровому серверу Colyseus, мы можем начать прослушивать любые изменения в наших игровых объектах.
Вот пример того , как прослушивать игрока, присоединяющегося к комнате ( onAdd
), и получать последовательные обновления состояния этого игрока:
this.room.state.players.onAdd = (player, key) => { console.log(`Player has been added with sessionId: ${key}`); // add player entity to the game world this.world.createPlayer(key, player.teamNumber); // listen for changes to this player player.onChange = (changes) => { changes.forEach(({ field, value }) => { this.world.updatePlayer(key, field, value); // see below }); }; };
В методе updatePlayer
физического мира мы обновляем свойства одно за другим, потому что onChange onChange
предоставляет набор всех измененных свойств.
Примечание . Эта функция работает только в клиентской версии физического мира, так как игровые объекты управляются только косвенно через сервер Colyseus.
updatePlayer(sessionId, field, value) { // get the player physics object by its sessionId let player = this.world.players.get(sessionId); // exit if not found if (!player) return; // apply changes to the properties switch (field) { case "angle": Body.setAngle(player, value); break; case "x": Body.setPosition(player, { x: value, y: player.position.y }); break; case "y": Body.setPosition(player, { x: player.position.x, y: value }); break; // set velocityX, velocityY, angularVelocity ... } }
Та же процедура применяется и к другим игровым объектам (мяч и команды): слушайте их изменения и применяйте измененные значения к физическому миру клиента.
Пока никакие объекты не двигаются, потому что нам все еще нужно прослушивать ввод с клавиатуры и отправлять его на сервер . Вместо того, чтобы напрямую отправлять события при каждом нажатии клавиши, мы сохраняем карту нажатых в данный момент клавиш и отправляем события на сервер keydown
в цикле 50 мс. Таким образом, мы можем поддерживать одновременное нажатие нескольких клавиш и уменьшать паузу, возникающую после первого и последующих keydown
нажатия клавиши, когда клавиша остается нажатой:
let keys = {}; const keyDown = e => { keys[e.key] = true; }; const keyUp = e => { keys[e.key] = false; }; document.addEventListener('keydown', keyDown); document.addEventListener('keyup', keyUp); let loop = () => { if (keys["ArrowLeft"]) { this.room.send("move", { direction: "left" }); } else if (keys["ArrowRight"]) { this.room.send("move", { direction: "right" }); } if (keys["ArrowUp"]) { this.room.send("move", { direction: "up" }); } else if (keys["ArrowDown"]) { this.room.send("move", { direction: "down" }); } // next iteration requestAnimationFrame(() => { setTimeout(loop, 50); }); } // start loop setTimeout(loop, 50);
Теперь цикл завершен: слушайте нажатия клавиш, отправляйте соответствующие команды на сервер Colyseus, чтобы манипулировать физическим миром на сервере. Затем сервер Colyseus применяет новые физические свойства ко всем игровым объектам и передает данные обратно клиенту для обновления пользовательского экземпляра игры.
Мелкие неприятности
Оглядываясь назад, на ум приходят две вещи из категории « никто-мне-не-говорил-но-кто-то-должен» :
- Хорошее понимание того, как работают физические движки , полезно. Я потратил значительное количество времени на тонкую настройку физических свойств и ограничений. Несмотря на то, что я создал небольшую игру с Phaser.js и Matter.js раньше, мне пришлось много проб и ошибок заставить объекты двигаться так, как я себе представлял.
- Работать в реальном времени сложно, особенно в играх, основанных на физике. Незначительные задержки значительно ухудшают работу, и, хотя синхронизация состояния между клиентами с Colyseus работает отлично, она не может устранить задержки вычислений и передачи.
Подводные камни и предостережения относительно SvelteKit
Поскольку я использовал SvelteKit, когда он только что вышел из бета-печи, было несколько ошибок и предостережений, на которые я хотел бы обратить внимание:
- Потребовалось некоторое время, чтобы понять, что переменные среды должны иметь префикс VITE_, чтобы использовать их в SvelteKit. Теперь это правильно задокументировано в FAQ.
- Чтобы использовать Supabase, мне пришлось добавить Supabase как в список
dependencies
, так и в списокdevDependencies
package.json. Я считаю, что это уже не так. - Функция
load
SvelteKits работает как на сервере, так и на клиенте! - Чтобы включить полную замену модуля в горячем режиме (включая сохранение состояния), вам необходимо вручную добавить строку комментария
<!-- @hmr:keep-all -->
в компоненты вашей страницы. См. часто задаваемые вопросы для более подробной информации.
Многие другие фреймворки также отлично подошли бы, но я не жалею, что выбрал SvelteKit для этого проекта. Это позволило мне работать над клиентским приложением очень эффективно — в основном потому, что Svelte сама по себе очень выразительна и пропускает большую часть шаблонного кода, а также потому, что в Svelte встроены такие вещи, как анимация, переходы, CSS с ограниченной областью действия и глобальные хранилища. SvelteKit предоставил все необходимые мне строительные блоки (SSR, маршрутизация, маршруты серверов), и хотя он все еще находится в стадии бета-тестирования, он кажется очень стабильным и быстрым.
Развертывание и хостинг
Первоначально я разместил сервер Colyseus (Node) на экземпляре Heroku и потратил много времени на то, чтобы заставить работать WebSockets и CORS. Как оказалось, производительности крошечного (бесплатного) динамометрического стенда Heroku недостаточно для сценария использования в реальном времени. Позже я перенес приложение Colyseus на небольшой сервер в Linode. Клиентское приложение развертывается и размещается на Netlify через адаптер-netlify SvelteKits. Никаких сюрпризов: Netlify отлично сработал!
Заключение
Начать с очень простого прототипа, чтобы проверить идею, очень помогло мне понять, стоит ли следить за проектом и в чем заключаются технические проблемы игры. В окончательной реализации Colyseus взял на себя всю тяжелую работу по синхронизации состояния в режиме реального времени между несколькими клиентами, распределенными по нескольким комнатам. Впечатляет, как быстро с помощью Colyseus можно создать многопользовательское приложение реального времени — стоит только понять, как правильно описать схему. Встроенная панель мониторинга Colyseus помогает устранять любые проблемы с синхронизацией.
Что усложняло эту настройку, так это физический уровень игры, потому что он вводил дополнительную копию каждого игрового объекта, связанного с физикой, который необходимо было поддерживать. Сохранение игровых PIN-кодов в Supabase.io из приложения SvelteKit было очень простым. Оглядываясь назад, я мог бы просто использовать базу данных SQLite для хранения игровых PIN-кодов, но пробовать новые вещи — это половина удовольствия при создании сторонних проектов.
Наконец, использование SvelteKit для создания внешнего интерфейса игры позволило мне двигаться быстро — и время от времени с улыбкой на лице.
А теперь вперед и пригласите своих друзей на раунд Autowuzzler!
Дальнейшее чтение в журнале Smashing Magazine
- «Начните с React, создав игру Whac-A-Mole», Джей Томпкинс.
- «Как создать многопользовательскую игру виртуальной реальности в реальном времени», Элвин Ван
- «Написание многопользовательского текстового приключенческого движка на Node.js», Фернандо Доглио
- «Будущее мобильного веб-дизайна: дизайн видеоигр и повествование», Сюзанна Скакка.
- «Как создать бесконечный раннер в виртуальной реальности», Элвин Ван