Entity-Component-System For React JS

Not long ago, Clevyr was hired to write an interactive content creator for the web. This project would need to be able to create static images as well as videos out of simple elements like text and images. We had to have second-by-second control of what would be displayed on the screen and we would have to support animations of, and user interactions with, the displayed elements. Because of these requirements, we decided we would need to 

I had used Entity-Component-System (ECS) frameworks in the past to write HTML games, so I looked around to see if any good, open source, javascript ECS frameworks existed. Unfortunately, all that I found were far out of date and poorly (if at all) documented. Since the principles of ECS are quite simple, I decided to write my own. This was fortunate, because in the end, my ECS code would need to support the somewhat novel concept of controlling ReactJS components. I’ll discuss this more after giving a brief introduction to ECS.

A simple breakdown of ECS starts with the concept of entities (the E in ECS). At its core, an entity is just an object with a unique id that represents a single item or element. Each entity is given meaning by attaching components (the C in ECS) to it. Each component defines an adjective or group of adjectives. For example, a position component might contain X and Y coordinates. By attaching that position component to an entity, you give it those coordinates and therefore a position. But positions can change over time, and to change components or attach or remove them from entities, we use systems (the S in ECS). Systems analyse all the entities every game loop and perform any necessary changes. For example, if we attach a gravity component to an entity, it will start being pulled on the y-axis. A positioning system might check for the existence of a gravity component on every entity. If it has one, update the entity’s Y coordinate value in its position component. In this way, the entity will “fall” down the y-axis. This combination of entities, components, systems, and a game loop (or timer) makes up the basics of ECS.

Initially, we thought of using HTML canvas to display the various elements on screen. However, we realized that because most of our elements resemble simple HTML elements, if we used HTML and CSS instead of canvas, we could save a lot of time and effort. In addition, since we are rendering the rest of the application using ReactJS, we would be able to use ReactJS components as well.

Set Up

So here is how the game system would be set up so far. The database would load up a list of entities with their associated components and feed them into the ECS. A timer based on requestAnimationFrame also needs to be loaded into the ECS. A visibility system would update an entity’s visible component based on the timer and the entity’s time component. Each entity has an element component to determine which HTML element / React component would be displayed to represent the entity. A position and size component would determine where and how large each element will be displayed. The question we came across at this point is how are we going to get React to properly render and update these entities?

MobX is a library that makes the management of external states very easy in React. So we decided to use it to power the communication between our ECS and React. To do this, we fed each entity into MobX before giving it to the ECS. This makes all our entities what MobX calls observables and makes the list of entities an observable map. The ECS framework can (for the most part) interact with these observables in the same way they would interact with normal javascript objects. We then pull this list of entities into React, loop through them, and display special React components based on the element component.

Since we are using CSS, each component needs to be positioned absolutely and we use top, left, width, height, and visibility to position and size it. Whenever the ECS framework updates a value in an entity component, MobX will automatically detect it and tell React to re-render the appropriate display components. At this point, we now have a simple working game model, but there is one major thing missing. How are we going to update the ECS system from the rendered DOM?

Implementation

My first implementation was to simply expose the list of entities to React and allow it to mutate any entities it wanted. Although this did work, it was a bad idea because we no longer had a one-way data flow that is ideal for complicated logic systems. Taking cues from frameworks like Flux and Redux, I added a concept of action components. These components are always added to new entities (action entities) that are then added directly to the ECS framework. The ECS systems can detect action components and mutate other entities in some way. Then, all the active components and their entities are purged at the end of each game loop. In this way, the ECS systems have full control over all entity and component manipulation. We no longer have to search the whole codebase to track down where entity mutations might be occurring.

Another benefit of the ECS-React approach is that it can easily be adapted to use SVG elements and even HTML canvas where needed. You just have to display those inside React and send any applicable information from ECS to them. For example, we used SVG elements to display rectangles and circles, but we also could have rendered a Canvas element and draw the shapes directly onto it.

Combining an ECS framework, ReactJS, and Mobx, we were able to build a very stable and flexible content creation system for our client. Soon, we are planning on open sourcing our ECS implementation to allow others to easily start experimenting with React powered games and game-like applications. Thanks for reading!

Jon White is a Senior Software Engineer of Clevyr, Inc. Clevyr makes software using all the buzzwords like AI, Machine Learning, Augmented Reality and Virtual Reality.

Schedule a Free Consultation with Clevyr today to discuss your software needs! https://clevyr.com/contact-us/