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

react-three-game

v0.0.109

Published

high performance 3D game engine built in React

Readme

react-three-game

Prefab Editor

JSON-first prefab mounting and authoring for React Three Fiber.

Built on top of three.js, @react-three/fiber, and @react-three/drei.

  • 🧱 Prefabs - Save prefabs as serializable JSON and load them on their own or inside larger app worlds.
  • 🎬 Prefab Editor - Edit prefabs visually with hierarchy, inspector, transform gizmos, and play mode.
  • 🧩 Components - Build prefabs from reusable GameObject + component composition.
  • 🔧 Direct Runtime Access - Get native Object3D, runtime handles, and authored prefab mutation APIs without a parallel engine API.
  • ⚡ R3F Native - Use normal React Three Fiber components whenever runtime behavior is clearer in code.

Documentation

  • Website: https://prnth.com/react-three-game
  • Editor: https://prnth.com/react-three-game/editor
  • Starter template: https://github.com/prnthh/react-three-game-starter

Install

npm install react-three-game @react-three/drei @react-three/fiber three

Usage

Here is a minimal example that renders a prefab inside a normal R3F app:

import { GameCanvas, PrefabRoot, ground } from "react-three-game/viewer";

const prefab = {
  id: "starter-scene",
  name: "Starter Prefab",
  root: {
    id: "root",
    children: [
      ground({ size: 50, color: "#3a3" }),
      {
        id: "ball",
        components: {
          transform: {
            type: "Transform",
            properties: {
              position: [0, 5, 0],
              rotation: [0, 0, 0],
              scale: [1, 1, 1],
            },
          },
          geometry: {
            type: "Geometry",
            properties: { geometryType: "sphere", args: [0.5, 32, 32] },
          },
          material: {
            type: "Material",
            properties: { color: "#f66" },
          },
        },
      },
    ],
  },
};

export default function App() {
  return (
    <GameCanvas>
      <ambientLight intensity={0.8} />
      <PrefabRoot data={prefab} />
    </GameCanvas>
  );
}

This example renders a simple authored prefab with a ground plane and mesh content.

Prefab Editor

In addition to the runtime renderer, there is a visual editor for authoring prefabs.

import { PrefabEditor } from "react-three-game/editor";

export default function App() {
  return <PrefabEditor initialPrefab={prefab} onChange={console.log} />;
}

Open the hosted editor here:

  • https://prnth.com/react-three-game/editor

Prefabs And Mounted Objects

Prefab is the serializable pure data format.

That means authored content stays as a prefab, and the same prefab can be:

  • edited directly in PrefabEditor
  • rendered directly with PrefabRoot
  • loaded inside another prefab or app scene as reusable content

PrefabRoot keeps the rendering model narrow and compositional:

  • Transform is the renderer-owned outer transform
  • Geometry or BufferGeometry + Material become the primary mesh content
  • non-instanced Model becomes the node's primary content
  • every other component View wraps the current subtree

Custom component Views use normal React Three Fiber composition with children.

When you import or decompose a .glb or .gltf model, mesh names can opt into imported Crashcat colliders:

  • MeshName_col keeps the mesh visible and adds a fixed CrashcatPhysics trimesh collider.
  • MeshName_colonly adds the same collider but hides the decomposed mesh render.

This mirrors the common Blender authoring workflow: export helper collision meshes in the GLB, then edit the generated CrashcatPhysics properties if that body needs a different motion type or collider shape.

For agent-authored custom meshes, use BufferGeometry with flat numeric arrays:

{
  "id": "triangle",
  "components": {
    "bufferGeometry": {
      "type": "BufferGeometry",
      "properties": {
        "positions": [0, 0, 0, 1, 0, 0, 0, 1, 0],
        "indices": [0, 1, 2],
        "computeVertexNormals": true
      }
    },
    "material": {
      "type": "Material",
      "properties": { "color": "#ff8844" }
    }
  }
}

Prefab Format

interface Prefab {
  id?: string;
  name?: string;
  root: GameObject;
}

interface GameObject {
  id: string;
  name?: string;
  disabled?: boolean;
  locked?: boolean;
  components?: Record<string, { type: string; properties: any }>;
  children?: GameObject[];
}

Runtime Mutation

Use the editor or root ref for scene-native object access, and the Scene mutation methods for authored data changes.

import { useEffect, useRef } from "react";
import { PrefabEditor, type PrefabEditorRef } from "react-three-game/editor";

function RaiseBall() {
  const editorRef = useRef<PrefabEditorRef>(null);

  useEffect(() => {
    editorRef.current?.update("ball", (node) => ({
      ...node,
      components: {
        ...node.components,
        transform: {
          type: "Transform",
          properties: {
            ...node.components?.transform?.properties,
            position: [0, 8, 0],
          },
        },
      },
    }));
  }, []);

  return <PrefabEditor ref={editorRef} initialPrefab={prefab} />;
}

For live Three.js access, use mounted objects directly:

const ball = editorRef.current?.getObject("ball");
ball?.rotateY(0.5);

For runtime integrations that need to react to authored scene changes, subscribe through the prefab store:

import { usePrefabStoreApi } from "react-three-game/editor";

const store = usePrefabStoreApi();
const stop = store.subscribe(
  (s) => s.nodesById,
  (next, prev) => console.log("scene changed", next, prev),
);

stop();

For runtime-owned imperative state, register node-local handles instead of reaching for ad hoc globals:

import { useEffect } from "react";
import { useAssetRuntime, useNode, useNodeHandle } from "react-three-game/viewer";

function SpinnerView({ children }: { children?: React.ReactNode }) {
  const { nodeId } = useNode();
  const { registerHandle } = useAssetRuntime();

  useEffect(() => {
    const handle = {
      setSpeed(next: number) {
        console.log("speed", next);
      },
    };

    registerHandle(nodeId, "spinner", handle);
    return () => registerHandle(nodeId, "spinner", null);
  }, [nodeId, registerHandle]);

  return <>{children}</>;
}

function SpinnerStatus() {
  const spinnerRef = useNodeHandle<{ setSpeed: (next: number) => void }>("spinner");

  useEffect(() => {
    spinnerRef.current?.setSpeed(2);
  }, [spinnerRef]);

  return null;
}

Mounted node metadata is mirrored onto the canonical Three.js wrapper object:

  • GameObject.id -> object.userData.prefabNodeId
  • GameObject.name -> object.name and object.userData.prefabNodeName
  • Data.properties.data -> merged into object.userData

That gives you a stable authored id for traversal-based integrations, while still making Three.js name lookup convenient:

const playerByName = editorRef.current?.root?.getObjectByName("Player");
const playerById = editorRef.current?.root?.getObjectByProperty("userData.prefabNodeId", "player");

Treat names as a convenience surface, with stable ids as the primary lookup key:

  • editorRef.current?.getObject(id) is the clearest stable authored-node lookup
  • names are not guaranteed unique
  • traversal metadata is applied to the prefab node transform object — the inner mesh or model child is one level deeper

You can author extra userData from the editor with a Data component:

{
  "data": {
    "faction": "enemy",
    "health": 100,
    "loot": { "table": "crate" }
  }
}

Custom component Views use normal React and R3F behavior — useFrame, refs, and native Three.js APIs.

Useful Exports

  • GameCanvas, PrefabRoot, PrefabEditor, PrefabEditorMode
  • Prefab, GameObject, ComponentData, PrefabNode, PrefabEditorRef, Scene
  • registerComponent, Component, ComponentViewProps, FieldDefinition
  • useScene, useEditorRef, useEditorContext
  • useNode, useNodeObject, useNodeHandle, useAssetRuntime
  • usePrefabStore, usePrefabStoreApi
  • gameEvents, useGameEvent, useClickEvent
  • loadJson, saveJson, loadFiles, loadModel, loadTexture, loadSound
  • exportGLB, exportGLBData, regenerateIds, computeParentWorldMatrix
  • ground, soundManager
  • FieldRenderer, Vector3Field, NumberField, StringField, BooleanField, SelectField, ColorField

Development

npm run dev
npm run build
npm run release

License

PFYL / VPL