0.1.59 • Published 5 years ago

astra-engine v0.1.59

Weekly downloads
4
License
MIT
Repository
github
Last release
5 years ago

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

Лицензия

MIT

0.1.59

5 years ago

0.1.58

5 years ago

0.1.57

5 years ago

0.1.56

5 years ago

0.1.55

5 years ago

0.1.54

5 years ago

0.1.52

5 years ago

0.1.51

5 years ago

0.1.5

5 years ago

0.1.45

5 years ago

0.1.44

5 years ago

0.1.43

5 years ago

0.1.42

5 years ago

0.1.41-a

5 years ago

0.1.41

5 years ago

0.1.4

5 years ago

0.1.39

5 years ago

0.1.38

5 years ago

0.1.37

5 years ago

0.1.36-b

5 years ago

0.1.36-a

5 years ago

0.1.36

5 years ago

0.1.35

5 years ago

0.1.34

5 years ago

0.1.33

5 years ago

0.1.32-a

5 years ago

0.1.32

5 years ago

0.1.3

5 years ago

0.1.2-b

5 years ago

0.1.2-a

5 years ago

0.1.2

5 years ago

0.1.12

5 years ago

0.1.11

5 years ago

0.1.1

5 years ago

0.1.0

5 years ago