React ECS is a framework for declaratively expressing the parts of an "entity component system".
Build your simulations and visualizations declaratively with re-usable components that react to state and can tap into the React ecosystem.
react-ecs
is a fully-fledged ECS and is renderer agnostic. (Use the DOM, react-three-fiber, babylonjs, etc)
Let's make a simple simulation that updates a ThreeJS cube based on a Spinning facet. (live demo). |
// define a facet that get attached to entities
class Spinning extends Facet<Spinning> {
rotation = new Vector3(0, 0, 0);
}
// define a system which processes entity facets
const SpinningSystem = () => {
// a query makes it easy to find entities the right facets
const query = useQuery(e => e.hasAll(ThreeView, Spinning));
// systems are basically just update callbacks with priorities
return useSystem((dt: number) => {
// iterate the entities with the ThreeView and Spinning facets
query.loop([ThreeView, Spinning], (e, [view, spin]) => {
// receive typed facets for each matching entity
const transform = view.object3d; // <ThreeView> Object3D
const rotation = spin.rotation // <Spinning> facet
.clone()
.multiplyScalar(dt);
// calculate new state
const newRotation = transform.rotation
.toVector3()
.add(rotation);
// mutate facets, state is automatically handled
transform.rotation.setFromVector3(newRotation);
});
});
};
export const SpinningCubeStory: FC = () => {
// declare the ECS instance
const ECS = useECS();
// drive the ECS with react-three-fiber's frame hook
useFrame((_, dt) => ECS.update(dt));
return (
{/* use ECS as context provider */}
<ECS.Provider>
{/* add systems to the simulation */}
<SpinningSystem />
{/* entities are their own context provider */}
<Entity>
{/* add facets to entities */}
<Spinning rotation={new Vector3(1, 1, 1)} />
{/* use integrations like react-three-fiber */}
<ThreeView>
<Box />
</ThreeView>
</Entity>
</ECS.Provider>
);
};
The ECS
instance is the central object to a simulation. It tracks all the systems, entities, facets and queries.
You can get an instance from the useECS
hook:
const ECS = useECS();
Update the ECS by calling it's update(dt)
method with a time-delta:
useAnimationFrame(dt => ECS.update(dt));
Use the context provider via the ECS's Provider
property:
<ECS.Provider>
<PhysicsSystem />
<Entity>
<Position />
<Velocity />
</Entity>
</ECS.Provider>
Requires <Entity>
Facet<T>
is the base class for all facets. Any class properties become the component's props:
class Motion extends Facet<Motion> {
velocity = new Vector3(0, 0, 0);
acceleration = new Vector3(0, 0, 0);
}
<Entity>
<Motion acceleration={new Vector3(0, -9.8, 0)}>
</Entity>
You must pass the new type to
Facet<T>
as a generic parameter:Motion extends Facet<Motion>
The following sections will explain the central pieces to react-ecs
:
To use react-ecs
start with the useECS
hook to create an ECS instance.
useECS(systems: Callback[] = [], entities: Entity[] = []): ECS
The hook can receive arrays of systems and entities that you wish to add imperatively.
See: ECS for more information.
Requires <ECS.Provider>
The useEngine
hook returns the underlying ECS Engine
instance:
useEngine(): Engine
Requires <ECS.Provider>
Returns the Entity
instance of the nearest <Entity>
context:
useEntity(): Entity
Requires <ECS.Provider>
Add a system Callback
function to the Engine
of the nearest ECS.Provider.
useSystem(callback: Callback, priority = 0): null
Requires <ECS.Provider>
Returns a new Query
and adds it to the Engine
of the nearest ECS.Provider.
useQuery(predicate: (Entity) => boolean, options?: QueryOptions): Query
The predicate
should return whether the passed entity is a result of the query.
The options
have the following properties:
{
added?: (Entity) => void, // called when an entity satisfies the query
removed?: (Entity) => void, // called when an entity ceases satisfying the query
}
See Query for more information.
Requires <Entity>
Returns a Facet instance for the Entity
instance of the nearest <Entity>
.
useFacet(type: Class<T extends Facet>): T | undefined