7 minutes
Framework Base Classes
In this article we're going to quickly write the base classes for our ECS framework. Very simple classes that set up our interface for all the game code that we're going to write.
New Files
Let's organize our ECS-related code within the src directory. Create a new folder named ECS inside src/lib.
In the terminal, cd into your project-folder and start making some files with the following commands.
This will give you the following directory structure within your src folder:
index.ts will be the root so we can import each class in a slightly cleaner way. It's simple and looks like this.
Now, let's dive into the code for each base class.
Entities
Entity: a thing with distinct and independent existence
Entities are Nouns. Be it a Player an Enemy, the Ground they walk on, the Bullets they fire.
Our base Entity class will provide the fundamental contract for all entities in our game world. It will manage a collection of Components. It will be the only thing that can manage it's own components.
Open src/lib/ECS/Entity.ts and paste the following code:
The add/remove/get and hasComponent methods should be seen as simple wrappers to the native Javascript Map class methods. They can be used to manage which components an Entity has, creating distinct entities from their components.
Why use wrapper methods?
You might be wondering why we'd have such small functions just to wrap a regular ol javascript Map. Can't we just .get and .set the Map directly. The reason is we're setting up our contract with every other entity. This is how we do it. We're only one way to do it. The components Map is marked private for that reason.
Should Entity extend Mesh?
You may have noticed that the base Entity class itself extends the Mesh class. This is a design choice I came to after much trial and error. It's certainly possible it could be a TransformNode or not extend anything and rely on Components. But I found that regardless of what each Component does, they generally need a parent node in the system. Something that you can actually parent other Meshes. The only 2 classes that offer parenting are Mesh and TransformNode. Mesh also provides a few other methods that are quite useful, so I use that. As we'll see in the Assets article later
Use
The Component
Components in our framework are simply javascript objects which store specific data about one aspect of a specific Entity.
They are the single source of truth for that one piece of information.
For example the Component could store the entity's Position and Velocity. Compnents should hold the data they might need to access most often and basically "stick to doing one thing well." In this case Postition and Velocity sould like part of a MovementComponent.
The component objects are meant to be easily added and removed during gameplay. They can also be enabled or disabled as a way to add and remove without losing the data the component is tracking. A component should only know and manipulate it's own entity. It can't access other entities.
We leaving more shared game information to the World Entity or the individual systems.
Our base Component class will be very simple, providing a common foundation for all our game components.
Open src/lib/ECS/Component.ts and paste the following code:
More Complex Data
In addition to the input data. Components are a great place to hold other game objects related to an entity but not defined in the data files, which can be referenced later that don't neccessarily have to be the simple serializeable data.
These objects aren't serialized to any JSON file, but recreated for each entity on load.
For instance, if we needed a new instance of a StateMachine class we're using, for each Player, we could save it in the Component class as another property.
In this simplified example:
-
We're calling
superwhich will take the data object, which may contain astartingPositionvector and save the data it to the object automatically, because thesuper()function runs theconstructor()function in the parent class. -
We're adding a custom object that can have it's own internal code to the component because when we access this component later we want to be able to reference the state machine through
.state.
We'll get more into Components and StateMachines later.
The System Class
Systems are singleton classes that process entities containing their matching component. I prefer to keep both System and Component classes in the same file as they should be kept fairly short and sweet, being the glue to other game classes, and are reliant on each others existance.
In other words, a System will never process and Entity that doesn't contain it's own Component.
This System base class will be extended by other classes in your games. In Object-Oriented Programming, having a base class to extend is called a Contract. It attempts to standardize the way the program will be built though these parent classes being extended once.
This allows us to create a unified architecture to make sure Systems and Components work together, know how to get information from other components and use a naming scheme that's consistent throughout.
The public abstract functions are meant to be overwritten by a sub-class implementing the specfics. Even though the system classes are instanced and created like the entities and component classes. There is only ever one instance of each system in the game. In this way saving more global information to the System acts like a global singleton.
In Sum
We've gone over a lot of code, all to organize the Game around an ECS coding pattern. It should becoming clear that in ECS we use OOP principles a lot. Now that we have the base classes written. (Small, aren't they?) We can work on loading them in a World class.