Мультиплеер
Пусть несколько человек находятся в одной сцене одновременно. Отправляйте сообщения, делитесь значениями, создавайте объекты для всех и показывайте, как плавно двигаются другие игроки.
Эти вызовы работают, только когда в проекте включён мультиплеер и открыта комната. В остальное время они ничего не делают. Поэтому тот же скрипт работает и в предпросмотре на одного человека, и двух версий делать не нужно.
Кто в комнате
ctx.net.id; // id этого участника
ctx.net.peers(); // все, кто сейчас в комнате
ctx.net.onJoin((peer) => {});
ctx.net.onLeave((peer) => {});
Что синхронизируется само
Только положение физических тел с настройкой shared. Всё остальное вы отправляете сами.
Так задумано. Ящик, дверь или мяч должны быть одним объектом, который все видят в одном и том же месте. А персонаж игрока общим быть не должен: иначе, когда идёт один человек, двигаются аватары всех. → кому принадлежит тело
Сообщения
Сообщения нужны для того, что происходит: кто-то набрал очки, начался раунд, открылась дверь.
ctx.net.send('score', { points: 10 });
ctx.net.on('score', ({ from, data }) => {
/* … */
});
Сообщение уходит всем, кроме вас. Подтверждения в ответ не приходит.
Общее состояние
Общее состояние нужно для значений, о которых все должны договориться. Это простой словарь, и у каждого есть его копия. Если два человека запишут один и тот же ключ, останется последняя запись.
ctx.net.state.set(ctx.net.id, { name, colour, ready: true });
ctx.net.state.get(somePeerId);
ctx.net.state.all();
ctx.net.state.on(ctx.net.id, (value) => {});
Чтобы хранить значения для каждого игрока, используйте ключом ctx.net.id. Каждый пишет свою
запись и читает записи остальных.
Создать объект для всех
const id = ctx.net.spawn(props.bullet, { position, rotation });
ctx.net.despawn(id);
id вы получаете сразу, а объект появляется чуть позже. id у всех одинаковый.
Удалить объект может только тот, кто его создал. Когда человек уходит из комнаты, созданные им объекты тоже удаляются.
Плавное движение других игроков
Здесь чаще всего что-то идёт не так, поэтому для этого есть отдельный вызов.
ctx.net.follow(otherPlayerObject, { position, rotation });
ctx.net.unfollow(otherPlayerObject);
Вызывайте follow с каждым полученным положением. Десять–двадцать раз в секунду — это нормально.
Дальше объект движется плавно, немного отставая от последнего положения.
Повторный вызов follow с тем же положением ничего не стоит. Так что можно вызывать его каждый
кадр с последним положением, которое у вас есть.
Обновления приходят гораздо реже, чем кадры, поэтому объект стоит, а потом прыгает: другие игроки не идут, а двигаются рывками. Плавное приближение к последнему положению тоже не помогает. Между обновлениями двигаться не к чему, поэтому объект останавливается, а потом дёргается.
Схема, которая работает
Если начинаете с нуля, этой схемы хватит надолго:
- Каждый игрок управляет своим персонажем — локальным телом, которое двигается от его ввода.
- Его положение рассылается через
net.stateили сообщения, десять–двадцать раз в секунду. - Персонажи всех остальных двигаются через
follow. - Объекты мира — общие тела, поэтому физика сама держит их в синхронизации.
- Счёт и состояние раунда хранятся в
net.state, и записывает их тот, кто отвечает за это событие.
Дальше: Решение проблем — что делать, если что-то не работает.