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
Лицензия
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago