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

@game-vir/entity

v15.1.1

Published

Entity system for game development.

Readme

@game-vir/entity

An entity system for Pixi.js graphics that lends itself well to game use. Also included are some helpful entity maths.

Reference docs: https://electrovir.github.io/game-vir/entity/

Install

npm i @game-vir/entity

Usage

Use defineEntitySuite to get a defineEntity method for defining entities and an entityStore instance for storing entity instances.

  • defineEntity: use this as the super class of a new entity class definition.

    import {Graphics} from 'pixi.js';
    import {defineEntitySuite, entityPositionParamsShape} from '@game-vir/entity';
    
    const {defineEntity} = defineEntitySuite<{
        /** Optionally provide a Context type. */
        movementSpeed: number;
    }>();
    
    export class Block extends defineEntity({
        key: 'Block',
        paramsShape: entityPositionParamsShape,
        paramsMap: {
            hitbox: {
                x: true,
                y: true,
            },
            view: {
                x: true,
                y: true,
            },
        },
    }) {
        public override update(): void {
            this.params.x += this.context.movementSpeed;
            this.params.y += this.context.movementSpeed;
        }
    
        public override createView() {
            /**
             * View and hitbox position don't need to be manually set their counterparts from params;
             * they will be updated to match them because of the above `paramsMap` definition.
             */
            return {
                view: new Graphics().rect(0, 0, 100, 100).fill('red'),
                /** Hitboxes are optional. */
                hitbox: this.hitboxSystem.createBox({}, 100, 100),
            };
        }
    }
  • entityStore is primarily interacted with via the addEntity and updateAllEntities methods.

    • addEntity: construct a new instance of the given entity class and adds it to the entity store. This is the easies way to construct new entities.

      import {createPixi, defineEntitySuite} from '@game-vir/entity';
      import {Block} from './define-entity.example.js';
      
      const {EntityStore} = defineEntitySuite<{movementSpeed: number}>();
      
      const entityStore = new EntityStore({
          pixi: await createPixi(),
          context: {movementSpeed: 6},
          registeredEntities: [Block],
      });
      
      entityStore.addEntity(Block, {x: 15, y: 20});
    • updateAllEntities: update all entities. This calls the update() method on every entity instance currently within the entity store. This should be called on every game tick or animation frame.

      import {createPixi, defineEntitySuite} from '@game-vir/entity';
      
      const {EntityStore} = defineEntitySuite<{movementSpeed: number}>();
      
      const entityStore = new EntityStore({
          pixi: await createPixi(),
          context: {movementSpeed: 6},
          registeredEntities: [],
      });
      
      entityStore.pixi.ticker.add(() => {
          entityStore.updateAllEntities();
      });

Example

Here's a full usage example. This can be seen in action through the following steps:

  1. Clone the repo.
  2. Run npm ci..
  3. Run cd packages/entity.
  4. Run npm start.
  5. Open the browser link logged to your console.
import {assertWrap} from '@augment-vir/assert';
import {and, defineShape} from 'object-shape-tester';
import {Graphics, GraphicsContext} from 'pixi.js';
import {Angle, createPixi, defineEntitySuite, entityPositionParamsShape, Vector} from '@game-vir/entity';

/** Create an entity suite. */
const {defineEntity, defineLogicEntity, EntityStore} = defineEntitySuite<{movementSpeed: number}>();

/** Define entities. */

/** Define a standard entity (with a view) that bounces back and forth. */
class Block extends defineEntity({
    key: 'Block',
    paramsShape: defineShape(
        and(entityPositionParamsShape, {
            direction: 1,
        }),
    ),
    paramsMap: {
        view: {
            x: true,
            y: true,
        },
    },
}) {
    public static readonly bonkCount = 20;
    public static readonly graphicContext = new GraphicsContext()
        .rect(0, 0, 10, 10)
        .fill('magenta');

    public override update(): void {
        this.params.x += this.context.movementSpeed * this.params.direction;
        this.params.y += this.context.movementSpeed * this.params.direction;

        if (!this.isInBounds({entirely: true})) {
            this.params.direction = -1 * this.params.direction;
            this.createBonk();
        }
    }

    protected createBonk() {
        const degreesPerBonk = 360 / Block.bonkCount;

        for (let i = 0; i < Block.bonkCount; i++) {
            const move = new Vector(
                BlockBonk.moveSpeed,
                new Angle({degrees: degreesPerBonk * i}, {digits: 4}),
                {digits: 4},
            ).toComponents();
            /** Entities can easily create more entities. */
            this.addEntity(BlockBonk, {
                move,
                ticksSinceCreation: 0,
                x: this.view.x,
                y: this.view.y,
            });
        }
    }

    public override createView() {
        const graphic = new Graphics(Block.graphicContext);

        graphic.x = this.params.x;
        graphic.y = this.params.y;

        return {
            view: graphic,
        };
    }
}

/** Define a standard entity (with a view) that emits from Block when it bounces. */
class BlockBonk extends defineEntity({
    key: 'BlockBonk',
    paramsShape: defineShape(
        and(entityPositionParamsShape, {
            move: {
                x: -1,
                y: -1,
            },
            ticksSinceCreation: -1,
        }),
    ),
    paramsMap: {
        view: {
            x: true,
            y: true,
        },
    },
}) {
    public static readonly moveSpeed = 4;
    public static readonly maxLife = 20;
    public static readonly graphicContext = new GraphicsContext().rect(0, 0, 4, 4).fill('yellow');

    public override update(): void {
        this.params.ticksSinceCreation++;

        if (this.params.ticksSinceCreation > BlockBonk.maxLife) {
            /** Automatically clean up the bounce particles when they reach their end of life. */
            this.destroy();
            return;
        }
        this.view.alpha = Math.min(
            1,
            (BlockBonk.maxLife + 3 - this.params.ticksSinceCreation) / BlockBonk.maxLife,
        );

        this.params.x += this.params.move.x;
        this.params.y += this.params.move.y;
    }

    public override createView() {
        const graphic = new Graphics(BlockBonk.graphicContext);

        graphic.x = this.params.x;
        graphic.y = this.params.y;

        return {
            view: graphic,
        };
    }
}

/** Define a logic entity which doesn't have a Pixi.js view. */
class Fps extends defineLogicEntity({
    key: 'Fps',
    paramsShape: undefined,
}) {
    protected fpsCounts: number[] = [];

    public override update(): void {
        this.fpsCounts.push(this.pixi.ticker.FPS);
        if (this.fpsCounts.length > 100) {
            const averageFps = Math.round(
                this.fpsCounts.reduce((a, b) => a + b) / this.fpsCounts.length,
            );
            this.fpsCounts = [];
            assertWrap.instanceOf(document.body.querySelector('.fps'), HTMLElement).innerText =
                String(averageFps);
        }
    }
}

/** Create the view */

const entityStore = new EntityStore({
    pixi: await createPixi({
        background: 'black',
        height: 500,
        width: 500,
    }),
    context: {
        movementSpeed: 6,
    },
    registeredEntities: [
        Block,
        Fps,
        BlockBonk,
    ],
});
document.body.append(entityStore.pixi.canvas);

/** Add entities to the view. */
entityStore.addEntity(Block, {direction: 1, x: 0, y: 0});
entityStore.addEntity(Block, {direction: -1, x: 490, y: 240});
entityStore.addEntity(Block, {direction: 1, x: 2, y: 252});
entityStore.addEntity(Fps);

/** Start updates. */
entityStore.pixi.ticker.add(() => {
    entityStore.updateAllEntities();
});

Common footguns

  1. When updating an entity's position, update it by modifying this.view.x or this.view.y. Do not update the position this.hitbox (unless you really know what you're doing and you set preventAutomaticHitboxUpdates to true).
  2. Do not set