Three patterns for going beyond the built-in 14 components: custom component registries, viz panels for debugging, and writing components that follow the same conventions.
Custom component registries
ArcadeGame accepts a componentRegistry option that merges with the built-in 14. Use it to add components from another package, a sibling repo, or your own project.
Per-instance
After construction
Useful for plugin-style integrations where components are discovered after the game has started.
What goes in a resolver
A resolver is () => Promise<Module>. The module must export <Name>Component (required) and <Name>System (optional):
Match the naming convention exactly — the loader strips the "EnemySpawner" key from the JSON, looks for mod["EnemySpawner" + "Component"] and mod["EnemySpawner" + "System"].
Writing your own component
Every component in this package follows the same surface. Use it as a template.
Required exports: MyThingComponent (data class) and MyThingSystem (logic). Optional: MyThingEvents / MyThingInputEvents for constants.
Once you have this file, hook it into your game:
Viz panels
Every built-in component ships with a .viz.tsx Solid.js debug panel — live readouts of the component's state. Wire them during development; peel them off for production.
Import from the /viz subpath
The /viz subpath ships TypeScript source (not pre-built JS) because Solid's JSX transform isn't standard react-jsx. Your bundler must have vite-plugin-solid or an equivalent JSX compiler. solid-js is declared as an optional peer dependency — install it only if you use viz.
Why source, not built JS
Solid's JSX compiler (babel-preset-solid) turns JSX into reactive primitives — not plain React.createElement calls. tsc can't do this transform alone. The choices are:
- Ship a heavy bundler config inside the package
- Ship source and let consumers' bundlers handle it
The package picks option 2. If you're using Vite or any modern framework with Solid support, it Just Works.
Building your own viz panel
For your custom components, follow the convention used by the built-ins:
The vizStore is a Solid store updated each frame by an internal viz System. Subscribe to it and the panel re-renders only when relevant fields change.
When to write fresh components vs. add custom resolvers
- Fresh component for one project — keep it next to your game source, hand-roll the resolver in your
componentRegistry. - Shared across multiple projects — publish as its own package, expose it via
componentRegistry. - Bundle a set of components — start a sibling package (e.g.,
@my-org/arcade-combat) that exports aCOMBAT_COMPONENT_REGISTRYobject the way@babylonjsmarket/arcadeexportsARCADE_COMPONENT_REGISTRY. Consumers merge both registries:
The arcade resolvers are auto-included; the combat ones merge on top. Both are lazy.
Where to next
- The package's GitHub source — read each component file as a worked example
- The Scene JSON Format section — when you need to remind yourself what the loader actually does
- The underlying @babylonjsmarket/ecs docs — for ECS concepts that aren't covered here