npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

astra-engine

v0.1.59

Published

game web-engine

Readme

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