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 🙏

© 2024 – Pkg Stats / Ryan Hefner

@browndragon/sg

v0.0.24

Published

More formal support for phaser3 groups and subclassed game objects

Downloads

22

Readme

@browndragon/sg: Phaser3 SingletonGroups

More formal support for phaser3 groups and subclassed game objects.

Motivation

Sometimes, you have a phaser3 game object whose subclass behavior exists entirely in terms of its interactions with other objects.

For instance, suppose that you have a few Phaser.GameObject.Sprite subclasses like Player, Enemy, Coin, and Bullet. Their interactions are straightforward: Players collect Coins (incrementing the score); Bullets that collide with Player or Enemys hurt that entity (and can be generated by either type), and Players and Enemys that intersect should separate (without hurting either).

In normal phaser code, this would be a lot of code in your Scene.create() to associate all of the collision information -- which is annoyingly distant from the code you want to write, because it's not in your Player.js file along with other information associated with playeritude (or whatever). But if you use this library, see Usage below!

Usage

npm i @browndragon/sg alongside your phaser ^3.5x install, and then in your code:

import Phaser from 'phaser';
import SG from '@browndragon/sg';

// Base class & SingletonGroup for player & enemy sprites.
class Mob extends SG.Member(
    Phaser.GameObjects.Sprite,
    class extends SG.PGroup {
        static get collides() { return [this] }  // Self collision: causes Mobs to bounce off of each other. Since Player and Enemy subclass Mob and include parent's singletongroups in their own, Enemies are in the Mob SG, and so bounce off of each other (and players against players, and players and enemies off of each other).
        // There is no need to define `collide(self, other)` because all we want to do is physics exclusion. If we wanted to have colliding with someone on the opposite faction hurt you, we could easily do that with `collide(self, other) { if (self.prototype != other.prototype) { self.getHurt(); other.getHurt(); } }` or similar.
        // Note that Mob.LastGroup (`==this`!) is referenced in Bullet, which collides with it.
    },
) {
    getHurt() { /* ... */ }  // Play a hurt animation and deduct health, destroying if dead.
    shootBullet() { /* ... */ }  // Spawn a bullet along the current facing, like `this.scene.add.existing(new Bullet(this))
}

// Collectable coin pickups.
class Coin extends SG.Member(
    Phaser.GameObjects.Image,
    class extends SG.PGroup {
        static get overlaps() { return [Coin.Collectors] }  // Run an overlap test with the `CoinCollectors.group(...)`.
        overlap(coin, collector) {  // And when they overlap, give the collector points and destroy the coin.
            collector.getScore(coin.value);
            coin.getCollected();
        }
    },
) { 
    constructor(scene, x, y, value) { /* ... */ }  // Hardcode image; value determines penny/nickel/dime/quarter.
    getCollected() { /* ... */ }  // Play a chime, animate away, then `this.destroy()`.
}
Coin.Collectors = class extends SG.PGroup {}  // Nothing in here; the Coin's anonymous singleton group collides with it, and Player is a member -- this just avoids any circularity of reference, and would let other things collect groups too if you wanted.

// Human-controlled sprite.
class Player extends Member(Mob, Coin.Collectors) {
    constructor(scene, x, y) { /* ... */ }  // Create a keyboard listener, hardcode image.
    getScore(value) { /* ... */ }  // Increase score; required by Coin.Collectors.
    preUpdate(time, delta) { /* ... */ }  // Listen to the keyboard.
}

// AI-controlled sprite.
class Enemy extends Mob {
    constructor(scene, x, y) { /* ... */ }  // Hardcode image.
    preUpdate(time, delta) { /* ... */ }  // Run AI; sometimes call `this.shootBullet` or `this.spawnCoin`.
    spawnCoin() { /* ... */ }  // Used in preUpdate.
}

// Projectile.
class Bullet extends SG.Member(
    Phaser.GameObjects.Image,
    class extends SG.PGroup {
        static get overlaps() { return [Mob.LastGroup] }  // Collide with the anonymous singletongroup created at Mob.
        overlap(bullet, victim) {
            victim.getHurt();
            bullet.destroy();
        }
    },
) {
    constructor(parent) { /* ... */ }  // Create with `parent.scene`; create bullet with physics body, set initial velocity from parent; Hardcode image.
}


new Phaser.Game({
    scene: class extends Scene {
        constructor() {
            super({key:'GameScene'});
        }
        create() {
            this.add.existing(new Player(this, 250, 250));
            this.add.existing(new Enemy(this, 32, 32));
            this.add.existing(new Enemy(this, 32, 480));
        }        
    },  /* other phaser config */ 
});

Another example has sprites which need to manage multiple child sprites; for instance, you might have a Rectangle sprite that spawns a 1-pixel probe at each corner in order to collide probes against the rest of the universe:

class Box extends Phaser.GameObjects.Rectangle {
    constructor(scene, x, y) {
        super(scene, x, y, 32, 32);
    }
    addedToScene() {
        super.addedToScene();
        this.scene.add.existing(this.a = new Probe(this, 0, 0));
        this.scene.add.existing(this.b = new Probe(this, 32, 0));
        this.scene.add.existing(this.c = new Probe(this, 0, 32));
        this.scene.add.existing(this.d = new Probe(this, 32, 32));
    }
    preUpdate(time, delta) {
        for (let p of [this.a, this.b, this.c, this.d]) {
            p.x = this.x + p.xoff;
            p.y = this.y + p.yoff;
        }
    }
}
class Probe extends SG.Member(
    Phaser.GameObjects.Image, 
    class extends SG.PGroup {
        overlaps() { return [this] }  // All probes can intersect. Could add other SGs based on what we're probing for; maybe they probe for Ground or something?
        overlap(aProbe, bProbe) { /* ... */ }  // Do something to the probes or the boxes they're attached to.
    },
) {
    constructor(parent, xoff, yoff) {
        super(parent.scene, parent.x + xoff, parent.y + yoff, 'blank1px.png');
        this.parent = parent;
        this.xoff = xoff;
        this.yoff = yoff;
    }
}

Given this setup, we can ensure the probes are always created for any Box just by creating the box; the rest of the wiring takes care of itself.