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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@like2d/scene

v0.1.0

Published

Scene management system for Like2D

Readme

@like2d/like-scene

Scene management system for LÏKE.

Scenes are a modular component of LÏKE based on setting like.handleEvent. The scene system is simple and powerful, once understood.

It's a bit like running LÏKE inside of LÏKE.

For devs using the built-in callback pattern, scenes can stack functionality on to the current project such as gamepad mapping or debug overlays.

For multi-scene games, they codify a common state-management pattern based on switching between (or nesting) event handler callbacks. It's a lot better than switch-casing on each handler, or manually setting/clearing handler functions on each transition.

Using scenes for your game also replaces the need to pass around global like or sceneManager wherever it is used.

Getting started

Install like-scene:

npm install @like2d/scene
# or
deno add jsr:@like2d/scene

Do this once to enable scenes:

import { createLike } from "@like2d/like";
import { SceneManager } from "@like2d/scene";

const like = createLike(document.body);
const sceneMan = new SceneManager(like);

This will overwrite like.handleEvent with sceneMan.handleEvent, but so far the game behaves as if nothing changed.

This handler serves as a router to calling handleEvent on the active scene as opposed to like.

This is when we can push our scene:

const myScene: Scene = (like, sceneMan) => {
    const frogImage = like.gfx.newImage("tinyfrog.svg");
    const scene = {}

    scene.draw = function () {
        like.gfx.draw(frogImage, [0, 0]);
        like.gfx.print("fill", "YOU JUST GOT FROGGED (Q to quit)", [20,20]);
    }
    
    scene.keypressed = function (code) {
        if (code == "KeyQ") {
            sceneManager.pop();
        }
    }
    
    return scene;
}

sceneMan.push(myScene, true);

Notice how scenes have the same structure as the base like event handler callbacks.

Included scenes

The prefab/ dir contains built-in utility scenes.

startScreen Lets you create a simple click-to-start screen, which can be useful for defeating autoplay and getting user focus. It's polite.

mapGamepad Is an essential companion to gamepad auto-mapping, which allows you to bind ambiguous inputs.

fadeTransition Is an example of how to transition between scenes. It can be used directly, but copy-and-modify is a good idea, too.

Scene Management

Graph pattern

For arbitrary scene management (non stack based), use sceneMan.set() which switches out the stack top. This is called the "graph" pattern: any scene can transition to any other.

set also allows you to pass an instance as the second argument, so that preloading becomes possible.

Stack pattern

Use sceneMan.push and sceneMan.pop to manage a scene stack.

It may be logical to lay a game state out with a stack, such as:

title => overworld => battle => battleMenu

For example, we can pop from a battle to get back to overworld (the battle ended), then we can push a menu from the overworld to enter a new state:

title => overworld => overworldMenu

Or, let's say we have a battle test feature on the title screen.

title => battle

The battle doesn't have to know that it was called from the title in order to return to it. It can simply pop and return to the previous state.

Notice how the function of the stack is not primarily to visually overlay scenes, but to manage logical game state.

However, with sceneMan.get(-2), a scene can see lower scenes and even pass events to them by setting their own handleEvent callback. This is a generic approach, and [composing scenes](#Composing scenes) is often preferred.

If using stack, it is wise to push the title screen scene in the root like.load function so that we can clear the stack and return to it:

while (sceneMan.pop())

Otherwise, an empty scene stack without callbacks will result in a broken game.

Stopping the Scene manager

To get rid of scene functionality entirely, simply set it back to default. It is good practice to pop the whole scene stack in order to deinit them all, first.

while (sceneMan.pop());
like.handleEvent = undefined;

Preserving handleEvent

The SceneManager overwrites {@link index.LikeHandlers.handleEvent | like.handleEvent} to its own {@link SceneManager.handleEvent}.

The code

const sceneMan = new SceneManager(like);

is equivalent to:

const sceneMan = new SceneManager(like, {nobind: true});
like.handleEvent = sceneMan.handleEvent.bind(sceneMan);

So if you want to layer middleware onto the scene system, use nobind: true and connect things as intended.

Save/Load the entire stack

Use one SceneManager per stack and simply switch handleEvent from one to the other.

Understanding Scene lifecycle

A Scene consists of a function that creates a scene instance:

type Scene = (like: Like, scenes: SceneManager) => SceneInstance

When we call sceneMan.push or sceneMan.set, the scene is put on the stack without an instance, then instantiated. The scene fuction is called, and then load is fired.

Now, a few things can happen:

If a scene calls sceneMan.pop or sceneMan.set, it will have quit called and subsequently be removed from the stack. If there is no other reference, the scene will be Garbage Collected eventually.

If a scene calles sceneMan.push(newScene, true), it will have quit called and be unloaded, but reinstantiated when the upper scene is popped. This is good for resource-heavy scenes that can be safely re-instantiated without losing game state. If you need the upper and lower scenes to communicate, consider {@link Scene | using composition} instead. Otherwise, consider storing save data in localStorage.

If a scene calls sceneMan.push(newScene, false), it will neither have quit called nor be unloaded. However, load will be called when the scene is once again at stack top (due to the upper scene calling pop). This is good for overlay scenes, or resource-light scenes made to be resumable. In the most intense cases (state-heavy AND resource-heavy scenes), an effort will have to be made: Unload heavy resources before calling pop, and reload them in load.

Creating your own scenes

Scenes are a function that receives Like and SceneManager and returns a SceneInstance, which is basically a table of the optional event handlers of like, sans modules.

Converting from Callbacks

When you first pick up the scene pattern, you might try this:

// Before (callbacks)
like.update = function(dt) { player.update(dt); }
like.draw = () => { player.draw(like.gfx); }

// After (scene)
scenes.set((like, scenes) => {
  const scene: SceneInstance = {}
  scene.update = function (dt) { player.update(dt); },
  scene.draw = () => { player.draw(like.gfx); }
  return scene;
});

Closure-based scenes

It is reccommended to use a function that returns a Scene, for configurability.

Example:

const myScene = (options: { speed: number }): Scene =>
  (like: Like, scenes: SceneManager) => {

    const playerImage = like.gfx.newImage('player.png');
    let x = 0, y = 0;

    return {
      update(dt) {
        x += options.speed * dt;
      },
      draw() {
        like.gfx.draw(playerImage, [x, y]);
      }
      mousepressed() {
        // exit this scene when user clicks
        scene.pop();
      }
    };
  };

Class-based scenes

Of course classes are also usable.

class ThingDoer extends SceneInstance {
  constructor(like, scenes) {...}
  ...
}

const thingDoerScene: Scene =
  (like, scenes) => new ThingDoer(like, scenes);

Or a configurable class:

class ThingDoer extends SceneInstance {
  constructor(like, scenes, options) {...}
  ...
}

const thingDoerScene = (options): SceneEx<ThingDoer> =>
  (like, scenes) => new ThingDoer(like, scenes, options);

Composing scenes

This is the most powerful scene pattern, and highly reccommended.

Just like the like object, scenes have handleEvent on them. So, you could layer them like this, for example:

A parent scene contains a child scene, calls it, and handles lifecycle via instantiate / deinstance.

// Composing scenes lets us know about the children.
// This allows communication, for example:
type UISceneInstance = SceneInstance & {
  // Sending events to child scene
  buttonClicked(name: string): void;
  // Getting info from child scene
  getStatus(): string;
};
type UIScene = SceneEx<UISceneInstance>;

const uiScene = (game: UIScene): Scene =>
  (like, scenes) => {
    const childScene = scenes.instantiate(game);
    return {
      handleEvent(event) {
          // Block mouse events in order to create a top bar.
          // Otherwise, propogate them.
          const mouseY = like.mouse.getPosition()[1];
          if (!event.type.startsWith('mouse') || mouseY > 100) {
              // Use likeDispatch so that nested handleEvent can fire,
              // if relevant.
              likeDispatch(childScene, event);
          }
          // Then, call my own callbacks.
          // Using likeDispatch here will result in an infinite loop.
          callOwnHandlers(this, event);
      },
      mousepressed(pos) {
          if (buttonClicked(pos)) {
              childScene.buttonClicked('statusbar')
          }
      },
      draw() {
          drawStatus(like, childScene.getStatus());
      }
    };
  }

const gameScene = (level: number): UIScene =>
  (like, scene) => ({
    update() { ... },
    draw() { ... },
    // mandatory UI methods from interface
    buttonClicked(name) {
      doSomething(),
    },
    getStatus() {
      return 'all good!';
    }
  });

like.pushScene(uiScene(gameScene);

The main advance of composing scenes versus the stack-overlay technique is that the parent scene knows about its child. Because there's a known interface, the two scenes can communicate.

This makes it perfect for reusable UI, level editors, debug viewers, and more.

Scene stacking

You might assume that the purpose of a scene stack is visual: first push the BG, then the FG, etc.

Actually, composing scenes (above) is a better pattern for that, since it's both explicit and the parent can have a known interface on its child. Here, the upper scene only knows that the lower scene is a scene.

That's the tradeoff. Overlay scenes are good for things like pause screens or gamepad overlays. Anything where the upper doesn't care what the lower is, and where the upper scene should be easily addable/removable.

Using like.getScene(-2), the overlay scene can see the lower scene and choose how to propagate events.

Remember to call like.push(someScene(), false) in the lower scene in order to keep its instance alive for the uuper one.

License

MIT