Code Monkey home page Code Monkey logo

games's Introduction

@danprince/games

A library for building tiny 2D games.

Setup

import { start, canvas } from "@danprince/games";

function loop() {
  // will be called once per frame
}

// Start the game with a 100x200 canvas and add it to the DOM
start({ width: 100, height: 200, loop });

document.body.append(canvas);

Text

import { write } from "@danprince/games";

// Write "Hello, world!" at (10, 20) in red text, with a black shadow
write("Hello, world!", 10, 20, "red", "black");

Sprites

The draw functions expect to work with the sprites format from the TypeScript Sprites Aseprite extension.

import { preload, draw } from "@danprince/games";
import * as sprites from "./sprites";

// Ensure sprites are loaded before rendering
preload(sprites);

// Draw a sprite slice at 10, 20
draw(sprites.redBall, 10, 20);

Pointer Coordinates

import { pointer, write } from "@danprince/games";

let { x, y } = pointer();
write(`${x},${y}`, x, y);

Button State

import { down, pressed, released, Buttons } from "@danprince/games";

if (down("Enter")) {
  // The "Enter" key is currently down
}

if (pressed("Enter")) {
  // The "Enter" key went down during this frame
}

if (released("Enter")) {
  // The "Enter" key went up during this frame
}

if (down(Buttons.MouseLeft)) {
  // Mouse/tap is down
}

Views

import { view, end, write } from "@danprince/games";

// Create a 200x200 view at 100,100
view(100, 100, 200, 200);
// Everything here is relative to the view until `end()` is called
write("View", 0, 0);
end();

Local/Global Coords

view(100, 100, 200, 200);

// Convert global coordinates to local view coordinates
let p1 = local(100, 100); // { x: 0, y: 0 }

// Convert local coordinates to global view coordinates
let p2 = global(0, 0); // { x: 100, y: 100 };

end();

Immediate Mode UI

import { write, measure, over, down } from "@danprince/games";

function button(text: string, x: number, y: number): boolean {
  let { w, h } = measure(text);
  let hover = over(x, y, w, h)
  let color = hover ? "cyan" : "black";
  write(text, x, y, color);
  return hover && down();
}

if (button("click")) {
  // button is currently down
}

Tween

Using a tween to mutate an object over time.

import { tween, easeLinear, easeInOut, easeOutBack } from "@danprince/games";

// Tween player's x & y coordinates to (10, 10) over 1s of game time with
// various timing functions.
await tween(player, { x: 10, y: 10 }, 1000, easeLinear);
await tween(player, { x: 10, y: 10 }, 1000, easeInOut);
await tween(player, { x: 10, y: 10 }, 1000, easeOutBack);

games's People

Contributors

danprince avatar

Stargazers

 avatar  avatar

Watchers

 avatar  avatar  avatar

Forkers

ashsimmonds

games's Issues

create a font from a sprite

Sometimes useful to be able to manage the font inside a spritesheet rather than a separate file (consistent palette, colocated sprite editing etc).

Should make it possible to create a font from a sprite rectangle inside a sheet.

texture batching

Noticing in benchmarks that even with caching in place, rendering text is still much slower than rendering sprites. Seems happy rendering ~6k sprites at 60FPS, but only around ~1k words (both pretty arbitrary measures depending on size etc).

This is almost certainly because it's more expensive to keep sending different textures to the GPU (each unique piece of text is rendered to a separate canvas then cached).

Could try maintaining a main cache texture and have the others drawn into it instead.

Packing sprites into the texture cache efficiently seems like a non-starter (immediately sacrificing the performance gains to calculate layout) and you'd have to recalculate the layout each time the cache changed.

Maybe simpler to work with a horizontal strip instead. Each time we add an item to the cache we'd expand the strip's width/height to accommodate for the new canvas, then add it to the end.

use lru/weak caches

Noticed strange behaviour where text shadows would disappear after ~90s for no obvious reason. A bit of debugging showed that my benchmarking example was creating a new gradient fill every frame. The cache responsible for text was filling up (one new cached canvas per frame) and eventually it must have affected the rendering behaviour.

Can think of a couple of solutions here. The first would be to use a standard LRU cache with a fixed capacity. Frequently rendered text would stay in the cache with a guarantee that memory won't ever spiral out of control (even if I do something stupid, like recreating a gradient once per frame).

The other option would be to solve the leaking memory with a weak map. A cache built this way would automatically evict entries when there are no more references, whilst allowing the cache to hold as many active entries as necessary.

The tricky thing with weak maps here is that the cache keys are composite (a font, a text color/gradient/pattern, a shadow color/gradient/pattern, and the text itself). That means the cache would need be made of layered weak maps.

cache.get([font, color, shadow, text]);
// behind the scenes, every layer is a weak map
map.get(font)?.get(color)?.get(shadow)?.get(text)

// vs

cache.get(`${font.url}/${key(color)/${key(shadow)}/${text}`)
// behind the scenes it's a single lookup
object[`${font.url}/${key(color)/${key(shadow)}/${text}`]
Something like this?
export class WeakCompositeCache<Keys extends [], Value> {
  private map = new WeakMap();

  set(keys: Keys, value: Value) {
    let cache = this.map;

    for (let i = 0; i < keys.length - 1; i++) {
      let key = keys[i];
      let child = cache.get(key);
      if (!child) {
        child = new WeakMap();
        cache.set(key, child);
      }
      cache = child;
    }

    let key = keys[keys.length - 1];
    cache.set(key, value);
  }

  get(keys: Keys): Value | undefined {
    let cache = this.map;

    for (let i = 0; i < keys.length - 1; i++) {
      let key = keys[i];
      let child = cache.get(key);
      if (!child) return undefined;
    }

    let key = keys[keys.length - 1];
    return cache.get(key);
  }
}

I suspect the LRU approach has better performance and stricter guarantees for memory leaks.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.