@ldlework/react-ecs2

An Entity Component System for React

Usage no npm install needed!

<script type="module">
  import ldleworkReactEcs2 from 'https://cdn.skypack.dev/@ldlework/react-ecs2';
</script>

README

React ECS

React ECS is a framework for declaratively expressing the parts of an "entity component system".

Why?

Build your simulations and visualizations declaratively with re-usable components that react to state and can tap into the React ecosystem.

Does it have limitations?

react-ecs is a fully-fledged ECS and is renderer agnostic. (Use the DOM, react-three-fiber, babylonjs, etc)

What does it look like?

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>
    );
};

Index

Classes

ECS

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();

Updating the ECS

Update the ECS by calling it's update(dt) method with a time-delta:

useAnimationFrame(dt => ECS.update(dt));

Using the Context Provider

Use the context provider via the ECS's Provider property:

<ECS.Provider>
    <PhysicsSystem />
    <Entity>
        <Position />
        <Velocity />
    </Entity>
</ECS.Provider>

Facet

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>

Note:

You must pass the new type to Facet<T> as a generic parameter: Motion extends Facet<Motion>

Hooks

The following sections will explain the central pieces to react-ecs:

useECS

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.

useEngine

Requires <ECS.Provider>

The useEngine hook returns the underlying ECS Engine instance:

useEngine(): Engine

useEntity

Requires <ECS.Provider>

Returns the Entity instance of the nearest <Entity> context:

useEntity(): Entity

useSystem

Requires <ECS.Provider>

Add a system Callback function to the Engine of the nearest ECS.Provider.

useSystem(callback: Callback, priority = 0): null

useQuery

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.

useFacet

Requires <Entity>

Returns a Facet instance for the Entity instance of the nearest <Entity>.

useFacet(type: Class<T extends Facet>): T | undefined