astra-engine v0.1.59
astra-engine
Что это?
astra-engine - игровой веб-движок, написанный на NodeJS и использующий модуль socket.io
Установка
npm install astra-engineКак использовать
Запуск сервера на TypeScript:
import SocketIO from "socket.io";
import { AstraEngine, Lobby, Player } from "astra-engine";
// Наследуемся от базового класса лобби
class GameLobby extends Lobby {
// Метод вызывается когда команда прилетает от игрока в данном лобби
onCommand(player: Player, action: string, payload: any) {
// Если экшен команды "ping"
if(action === "ping") {
// Определяем задержку между игроком и сервером, которая является разницей во времени между ними
const ping = Date.now() - payload;
// И посылаем игроку задержку (пинг)
this.command(player, "pong", ping);
}
}
}
// Создаём сервер (может быть любым фреймворком, поддерживающим socket.io)
const io = SocketIO(3000);
// И экземпляр движка, передавая созданный socket.io сервер и класс лобби, используемый по умолчанию
const engine = new AstraEngine(io, GameLobby);Подключение клиента на JavaScript:
// EventEmitter не обязателен, но очень полезен для упрощения обработки команд
const { EventEmitter } = require("events");
const SocketIO = require("socket.io-client");
// Создаём экземпляр EventEmitter'а
const server = new EventEmitter();
// Привязываем к нему все обрабатываемые команды
server
// Когда игрок подключился (после аунтентификации)
.on("player.connected", ({ playerId }) => {
// Берём playerId из данных, пришедших от сервера (пейлода)
console.log(`player with id ${playerId} connected!`);
// И отправляем команду подсоединения к лобби, т.к. после подключения мы можем это сделать
io.emit("command", "lobby.join");
})
// Когда мы подключились к лобби - мы можем отправлять команды непосредственно в него
.on("lobby.joined", ({ playerId, lobbyId }) => {
// Выводим, что мы подключились в лобби
console.log(`player with id ${playerId} connected to lobby with id ${lobbyId}`);
// И, с интервалом в 500 мс, посылаем текущие время серверу
setInterval(() => io.emit("command", "ping", Date.now()), 500);
})
// Когда же сервер отвечает нам командой "pong"
.on("pong", ping => {
// Мы выводим пинг, полученный из аргумента, являющимся пришедшими данными от движка
console.log("pong!", `ping: ${ping} ms`)
})
// Создаём соединение, передавая в query строку username, являющейся ключём аутентификации в данный момент
// (поддержка токенов пока не реализована)
const io = SocketIO("http://localhost:3000", { query: { username: "1337player" } });
// И враппим данные о команде в наш эмиттер
io.on("command", (action, payload) => server.emit(action, payload));Классные фичи
Состояние лобби
У каждого лобби существует два типа объекта состояния:
lobbyState, глобальное, единое для всех игроковplayerState, уникальное для каждого игрока
Оба состояния можно менять и изменения отправлять клиентам, обмениваясь данными и синхронизируя их. Например, существует игра, цель которой нажмать на кнопку и получать очки, тогда лобби будет создано следующим образом:
class GameLobby extends Lobby {
createPlayerState = () => ({ score: 0 })
onCommand(player: Player, action: string) {
if(action !== "player.score") return;
const ps = this.getPlayerState(player);
const changes = ps.modify(s => ({ score: s.score + 1 })).apply();
this.command(player, "player.score", changes);
}
}Суть проста - функция createPlayerState возвращает объект, определяющий начальное состояние каждого подключенного в лобби игрока. Если она не объявлена, то состоянию присваивается пустой объект {}.
Метод onCommand вызывается при команде, не имеющей отношения к отношения к системному взаимодействию с лобби, посылаемой игроком находящимся в данный момент в лобби. Например, игрок, посылая команду game.ping, отправит её именно в то лобби, в котором он находится в данный момент и метод onCommand вызовется, передав игрока первым аргументом player. Вторым и третьим аргументами выступают наименование команды action и дополнительные данные payload, которые игрок мог передать. Если их нет - обработчику прилетает пустой объект {}.
В методе проверяется, является ли обрабатываемая команда player.score:
if(action !== "player.score") return;В ином случае, обработка команды завершается. Метод базового класса Lobby именуемый getPlayerState позволяет получить состояние любого игрока, находящегося (или находившегося когда-либо) в этом лобби. Для этого первым аргументом передаётся ссылка на экземпляр игрока player, в ответ возвращается экземпляр класса SyncState, позволяющий манипулировать данными состояния.
const ps = this.getPlayerState(player);Следующим шагом необходимо изменить состояние игрока, добавив ему очки. Данная операция осуществляется методом modify в объекте состояния. В его аргумент передаётся функция, в аргументах которой находятся:
state, текущее состояние;changes, все совершённые изменения над этим состоянием после использованияmodify;
Методом modify возвращается экземпляр объекта изменений, которые можно сохранить и получить, выполнив метод apply, возвращающий объект изменений, совершённых над состоянием.
const changes = ps.modify(s => ({ score: s.score + 1 })).apply();Изменения необходимо отправить клиенту и для этой цели используется метод базового класса Lobby именуемый command, имеющий следующие аргументы:
player, игрок, который должен получить данные;action, команда, которая придёт игроку с этими данными;payload, Сам объект данных, отправляемый игроку;
Прописав метод:
this.command(player, "player.score", changes);После вышеперечисленных операций игрок получит ответ с изменившимся состоянием, новым количеством очков, что и требовалось сделать.
Тесты
yarn testЛицензия
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago