@ldlework/react-ecs
TypeScript icon, indicating that this package has built-in type declarations

0.0.1-a.2 • Public • Published

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

What's an ECS?

An ECS, or "Entity Component System" is a design pattern popular in game development. It eschews rich objects for simple Entities that compose data-only Components, or "Facets" as react-ecs calls them (to avoid confusion with React Components).

Logic is then handled by small update functions called Systems that operate upon the Facets related to it:

Docs Index

Types

ECS

The ECS instance is the central context for a given simulation. It holds the Engine and registered system Callbacks.

You can get the current 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>

Engine

The Engine implements the entity component system. It is currently based on Tick-knock by mayakwd.

Documentation can be found here: https://github.com/mayakwd/tick-knock#engine

Note: You normally shouldn't need to interact with Engine directly.

System

type System = (dt: number) => void

A System is a callback function that you can register with the ECS. It will be called each time the ECS itself is updated.

Systems can be registered with the useSystem hook.

Systems can utilize the useQuery hook to create a Query to find entities to process.

Note: See useSystem for more information.

Entity

Entities represent the constitutents of your simulation. They exist as a collection of data Facets which are processed by Systems. They are typically created by utilizing the <Entity /> component.

Currently, Entity is provided by Tick-knock by mayakwd.

Documentation can be found here: https://github.com/mayakwd/tick-knock#entity

Note: See <Entity /> for more information.

Facet

Requires <Entity />

Facets are small, data-only objects that represent aspects or "facets" of your simulation's entities.

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

Placing the Facet within an <Entity /> associates it with the Entity instance.

<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>

Query

Query is helpful for easily tracking Entities which have a specific set of Facets.

The useQuery is an easy way to create and register queries.

Query.loop

loop(Class<Facet>[], (entity: Entity, facets: Facet[]) => void)

Call loop() with an array of Facet types and a callback. The callback should recieve an Entity and its instances of the requested facets.

The callback will be called for every Entity that has the right Facets.

Hooks

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

useECS

useECS(systems: Callback[] = [], entities: Entity[] = []): ECS

To use react-ecs start with the useECS hook to create an ECS instance.

The hook can receive arrays of systems and entities that you wish to add imperatively.

See: ECS for more information.

useEngine

Requires <ECS.Provider>

useEngine(): Engine

The useEngine hook returns the underlying ECS Engine instance:

useSystem

Requires <ECS.Provider>

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

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

useEntity

Requires <ECS.Provider>

useEntity(): Entity

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

useFacet

Requires <Entity>

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

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

useQuery

Requires <ECS.Provider>

useQuery(predicate: (Entity) => boolean, options?: QueryOptions): Query

Returns a new Query and adds it to the Engine of the nearest ECS.Provider.

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.

Components

<ECS.Provider />

The ECS instance returned by the useECS hook has a Provider property which can be used as a React Context provider.

<ECS.Provider>
    // your systems and entities go here
</ECS.Provider>

The ECS.Provider component is required for all other react-ecs components.

<Entity />

Requires <ECS.Provider />

The <Entity /> component declares a new Entity within your simulation.

Within it you may place Facet components to associate them with the entity.

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

Readme

Keywords

none

Package Sidebar

Install

npm i @ldlework/react-ecs

Weekly Downloads

1

Version

0.0.1-a.2

License

MIT

Unpacked Size

147 kB

Total Files

93

Last publish

Collaborators

  • ldlework