An ECS hook for React to make games or other interactive components
React Entity Component System
Entity Component System for React to make games or other interactive components.
yarn add react-entity-component-system
Why
It's fun to build games with React, and people have become successful at it. The ECS pattern is well known and battle tested for game development. This library is a loose implementation for React. You can check out the Breakout storybook story to see a fairly complex example.
Usage
import React from 'react'
import { useEntityComponentSystem } from 'react-entity-component-system'
ECS in general has three basic concepts:
Entities
represent things in a sceneComponents
(not React components) are data structures, composed to create entities.Systems
are functions that operate on entities during every update
In this implementation, an entity is defined as a plain object with at least a Renderer
property (a React component). Other properties are components
that will be passed as props to the Renderer
:
const counterEntity = {
Renderer: props => <h4>{props.count}</h4>,
count: 0,
}
A system
is just a function that operates on entities
in the scene each frame. Systems
are allowed to mutate the entities' components
(thanks to immer!):
function frameCounterSystem({ entities }) {
entities.forEach(entity => entity.count++)
}
The useEntityComponentSystem
hook manages the CRUD (creating, rendering, updating and destroying the entities). Pass it some initial entities and systems, receive the renderable result and an updater
function:
const initialEntities = [counterEntity]
const systems = [frameCounterSystem]
export default function BasicECS() {
const [entities, updater] = useEntityComponentSystem(initialEntities, systems)
return (
<div>
<button onClick={() => updater()}>Next Frame</button>
{entities}
</div>
)
}
When the updater
is called, all the systems
are called with the following object as the first argument:
const systemArgs = {
entities: Object.values(entitiesDraft),
entitiesMap: entitiesDraft,
createEntity,
destroyEntity,
...userArgs,
}
The updater
takes and object or function (which should return an object) and passes it to the systems (...userArgs
as shown above) so they can have access to many other things. For example, the provided useKeysDown
hook:
const keysDown = useKeysDown()
updater({
keysDown,
})
Typically, the updater
is called inside a loop via the provided useGameLoop
hook. Be sure the function you pass is not redefined during every render. In most cases, you should wrap the updater
call in React.useCallback
:
function Game(scene) {
const [entities, updater] = useEntityComponentSystem(...scene)
const update = useCallback(elapsedTime => updater({ elapsedTime }), [updater])
useGameLoop(update)
return <>{entities}</>
}
See this example and more in the stories folder
Storybook
While react-entity-component-system
is in development, you can check out the storybook to get a better sense of how things work.
git clone https://github.com/mattblackdev/react-entity-component-system.git
cd react-entity-component-system
yarn
yarn start
API
useEntityComponentSystem
useGameLoop
useGameEvents
useKeysDown
Debug
Contributing
I welcome any ideas and would really love some help with:
- Adding Typescript types
- Performance benchmarking and optimizations
- More game engine API like:
- Keeping track of "entity filters" for systems
- MatterJS or other physics lib integration