@ldlework/react-ecs v0.0.1-a.2
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?
// 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 withEngine
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>