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

r2d

v0.0.1

Published

Experimental 2d game tools for personal use.

Downloads

2

Readme

r2d

Experimental 2d game tools for personal use.

This really isn't ready for anyone yet, but it might be nice to get feedback on the general direction, so here's some barebones docs:

Input

The library exports a keyboard which stores active keys, so you can always just pull in keyboard and use its methods to get input. Nothing for mouse yet. It has types for the keys I care about, will need to flesh that out more.

// is the key down right now?
keyboard.getKey(' ');
// was the key pressed just this frame?
keyboard.getKeyDown(' ');
// was the key released just this frame?
keyboard.getKeyUp(' ');

In order to have this thing work you need to render the exported <KeyboardInput /> component in your tree. It subscribes to useFrame to ensure the up/down values are accurate to the game loop.

Physics

planck-js is a peer dependency. r2d uses a lot of it internally. Maybe it would be better to just bake it in? You'll end up importing a lot of stuff from planck-js yourself to pass into the library.

To get physics setup just render <Physics> wrapping the rest of your tree (maybe just below Canvas).

Using behaviors

This library's primary function is to facilitate using and creating behaviors. It currently exports one behavior: useBody.

To use the behavior you need to have a mesh reference. To do that just do a useRef and pass it to your <mesh> or equivalent.

useBody is a hook you call and pass in the mesh and some physics config:

import * as p from 'planck-js';
import { useBody } from 'r2d';

export function Floor({ position, width = 10, height = 1, angle }) {
  const meshRef = React.useRef(null);
  useBody({
    mesh: meshRef.current,
    body: {
      type: 'static',
      userData: {
        tags: [Tag.Wall],
      },
    },
    fixture: p.Box(width / 2, height / 2),
    position,
    angle,
  });

  return (
    <Box ref={meshRef} args={[width, height, 0]}>
      <meshBasicMaterial attach="material" />
    </Box>
  );
}

You should pass position and angle to your useBody, not to your mesh. It'll get overwritten on the mesh.

Handling collisions

This is a WIP, but you can use another hook to handle collisions with the body:

useCollisions(bodyBehavior, {
  onBeginContact: ({ other, contact }) => {
    const otherBody = other.getBody();
    // etc
  },
  onEndContact: ({ other, contact }) => {},
});

Having a separate hook may seem like overkill, but it's actually really useful to have it separate at the moment, especially because of how behaviors generally work together...

Creating behaviors

Besides just useBody, the point is you can create your own behaviors. Right now useBody is actually so small you might just want to do it yourself?

Behaviors are roughly inspired by Unity. They include an update callback which is run each frame, they have a local state object you can use to store state, and they can expose a public API. They're also hooks.

The code below implements a simple jump behavior. I'm not totally happy with it yet (especially the awkwardness of referencing the bodyBehavior as a passed-in arg), but it's the general idea I'm going for.

import { Vec2 } from 'planck-js';
import { useBehavior, BodyBehavior } from 'r2d';
import { useInputStore } from '../store/useInputStore';

export function useJump({
  bodyBehavior,
  jumpPower = 30,
}: {
  bodyBehavior: BodyBehavior;
  jumpPower?: number;
}) {
  return useBehavior({
    onUpdate: () => {
      if (keyboard.getKey(' ')) {
        bodyBehavior.applyLinearImpulse(Vec2(0, jumpPower), {
          wake: true,
        });
      }
    },
    initialState: {},
    // this is purely hypothetical, there's no real reason for
    // an API on this behavior - but just to demonstrate.
    makeApi: (state) => ({
      logFoo: () => console.log('foo!'),
    }),
  });
}

To use your behavior you call it, passing in any args you specified. Here's how you might use useJump:

function Player() {
  const meshRef = useRef(null);
  const bodyBehavior = useBody({
    mesh: meshRef.current,
    body: {
      type: 'dynamic',
      fixedRotation: false,
      angularDamping: 5,
    },
    fixture: {
      shape: p.Circle(1),
      density: 10,
      friction: 20,
    },
    position,
  });

  // use our behavior!
  const jumpBehavior = useJump({
    bodyBehavior,
    jumpPower = 10,
  });

  // invoke our API!
  useEffect(() => {
    jumpBehavior.logFoo();
  }, [jumpBehavior]);

  return (
    <mesh ref={meshRef}>
      <meshBasicMaterial color="blue" />
    </mesh>
  );
}

So, basically, this kind of composition / interaction is why it's nice to have small discrete hook behaviors right now. For example, having the collisions in a separate hook from useBody is nice because you'd want useBody early in your component (probably first thing), but then you may want to define some custom behaviors which would care about collisions - and then, you call useCollisions and invoke the APIs of your custom behaviors from the callbacks. Here's an example from the game I'm testing this library with:

const meshRef = React.useRef(null);
const bodyBehavior = useBody({
  mesh: meshRef.current,
  body: {
    type: 'dynamic',
    fixedRotation: false,
    angularDamping: 5,
  },
  fixture: {
    shape: p.Circle(1),
    density: 10,
    friction: 20,
  },
  position,
});

// behavior that makes the camera follow this object
useFollowCamera({ mesh: meshRef.current });

// behavior to handle player movement - perhaps we only want to allow
// jumping when they are in contact with a wall and the normal of that
// contact is upward - so we need to know about current collisions!
const controls = usePlayerControls({
  bodyBehavior,
});

useCollisions(bodyBehavior, {
  onBeginContact: ({ other, contact }) => {
    const otherBody = other.getBody();
    if (otherBody.getUserData().tags.includes(Tag.Wall)) {
      // our controls behavior exposes a way to add contacts
      // to a Set it stores in its internal state. It then uses
      // the contents of this Set each frame to see what objects
      // this player is in contact with, and what angle of contact,
      // etc.
      controls.addWallContact(contact);
    }
  },
  onEndContact: ({ other, contact }) => {
    const otherBody = other.getBody();
    if (otherBody.getUserData().tags.includes(Tag.Wall)) {
      controls.removeWallContact(contact);
    }
  },
});