-
Notifications
You must be signed in to change notification settings - Fork 114
Manual
- Introduction
- Internals and small history
- World
- Entity
- Component
- ComponentMapper
- Managers
- Aspect
- EntitySystem
- Advanced topics
- Complex systems for physics, rendering AI and scripting, etc.
- Parallelism/Multi-threading
- Data driven and RDBMS connection
- Bag and ImmutableBag
- Utils
Artemis is one of the first proper implementation based on the Entity System concept described by Adam Martin in a blog series. The intention with Artemis was to experiment and showcase the Entity System approach in games.
By itself Artemis isn't heavyweight, it's a small library, but it's primarily a doorway to a better and more productive design. Hopefully this manual, although incomplete, can explain these design aspects and get you started.
The goals of Artemis were to create a small framework that was easy to learn, to use, and without sacrificing performance, as other Entity System implementations have done by using badly chosen datastructures internally, such as HashMap.
Other entity/component concepts stored all the components inside each of the entity, but the Entity System concept revolves around the entity being a mere identifier which meant we couldn't simply store the components there, but storing them elsewhere and having to look them up meant performance issues. The performance issues stem from trying to process a list of entities based on their aspects, and having to look up for each entity their components in some HashMap.
We then got an inspiration from Ted Brown who had implemented a simpler Entity System in Processing, but didn't have the same capabilities as Adam Martin discussed, as his systems only processed a single type of component. But he used an interesting way of looking up the components for each of the entity, he used the entity identifier as an index into an array of components of same type, meaning he managed to retrieve the component without a decrease in performance.
This we borrowed and extended on. We assigned an index to each type of component, and used that index as a reference into an array that contained an array of same-type components. That nested array contained all the instances of the components, but to retrieve a component for an entity from that array we use the id of the entity as index. In short, it meant we could do something like:
MyComponent myComponent = Components[4][25];
The index 4 is the type of the component, and index 25 is the id of the entity. If you were to visually see the data-structure behind Artemis it would look like a table, columns would be components, and rows entities, and some of the cells would be empty and some would be filled with an instance of the component for that entity. To make sure we don't run out of memory we re-use the id's of entities, that way the number of rows in this table never exceeds the number of active entities, also the number of types of components is usually quite low.
From this we managed to create systems that process a set of components and enabling the developer to easily look up any component of any entity very fast.
The World instance is an easily accessible context to manage all your entities, retrieve systems, components and managers.
The most pure approach to Entity Systems would simply have Entity as an integer or some sort of unique identifier, which components would refer to as it's owner thus binding them together, very similar as in RDBMS. In Artemis we decided to create the class Entity and add some helper methods and fields in it.
Entity contains several methods, such as for adding or removing components, and for adding or deleting the entity itself from the world.
It also contains an UUID (Universally Unique Identifier) instance, as well as the more important id that is used as an index for the components internally. If an entity is deleted from the world its id will be reused for future created entities.
You do not extend the Entity class, it is sealed as final to prevent that.
Components are pure data classes, they do not have any logic in them other than that for setting/getting its data, and perhaps some helper methods. An example component would be Position, containing only X and Y coordinate values, and an allowed set method would be Position.setPosition(x,y), or even Position.getDistanceTo(Position).
All components extend the Component class, which the framework relies on to differentiate them from other classes.
Component mappers provide a fast way to access entities’ components. Although you can retrieve components using the getComponent method of the Entity class, you should NOT do this while processing entities continuously, as using component mappers is a faster way of doing it.
An example could be retrieving a Position component from your entity. The first thing you need to do is to declare the mapper in your entity system with the @Mapper annotation:
@Mapper ComponentMapper<Position> positionMapper;
The @Mapper annotation will assign the correct mapper to the positionMapper at runtime, but this only works for entity system classes.
Now, given an Entity e, you can get the desired component using the mapper:
Position position = positionMapper.get(e);
At this point, you will be able to access all the component’s attributes at the optimal speed.
You can retrieve component mappers from the world instance as well:
ComponentMapper<Position> positionMapper = world.getMapper(Position.class);
or from the ComponentMapper class directly:
ComponentMapper<Position> positionMapper = ComponentMapper.getFor(Position.class, world);
Managers sit on the sidelines and help you manage and organize your entities. There are two primary managers that are core to Artemis, they are the EntityManager and ComponentManager. All other managers are add-on or "custom" managers that can be added into your world instance.
Pending.
Pending.
The framework comes bundled with a few "custom managers" that must be added into the world for use in your game.
You can create your own managers, that extend the Manager class, and add to the world.
Here's an example of the GroupManager being added into the world:
World world = new World();
world.setManager(new GroupManager());
You can look it up anywhere by doing:
GroupManager groupManager = world.getManager(GroupManager.class);
You can use GroupManager to group entities in your game. This manager is useful to manage entities that have something in common. An example could be a "MONSTERS" group. When creating an entity you need to register it into the group:
Entity bullet = world.createEntity();
World.getManager(GroupManager.class).register(bullet, "MONSTERS");
You can now retrieve an ImmutableBag of all the entities marked as "MONSTERS" using:
ImmutableBag<Entity> monsters = world.getManager(GroupManager.class).getEntities("MONSTERS");
Every entity can be assigned to multiple groups. By the way, keep in mind that in an entity framework the proper way to process entities through systems is by defining Aspects the system will work on, and this should suffice in most cases: groups should not become a way to substitute Aspects.
You can use the TagManager to manage entities that, because of their nature, can be labeled with a particular, unique tag (usually a string). A classical example could be the “player” tag: the player is, in many cases, only one, and it could be necessary to retrieve the corresponding entity into systems. TagManager helps you doing this easily. When the entity is created, you have to register it to the TagManager:
Entity e = world.createEntity();
world.getManager(TagManager.class).register("PLAYER", e);
You can now access the player entity anywhere:
Entity player = world.getManager(TagManager.class).getEntity("PLAYER");
It is also possible to unregister the entity from the TagManager:
world.getManager(TagManager.class).unregister("PLAYER");
Pending.
Pending.
All managers implement the EntityObserver interface, which means they receive events like added(), changed(), deleted(), disabled() or enabled(). Adding a manager into the world automatically makes it a listener for those events.
Aspects are used by the systems as a matcher against entities and its components, to figure out if an entity should be inserted into a system. They are primarily used in the constructor of each entity system, and can be composed in a few ways.
They define the type of entities the system is interested in. Instead of thinking of systems processing entities, they rather process aspects of entities.
When creating an entity system you'll need to pass an instance of Aspect up to the parent class. The standard way of doing this is simply:
public class MovementSystem extends EntityProcessingSystem {
public MoveEntitiesSystem() {
super(Aspect.getAspectForAll(Position.class, Velocity.class));
}
}
You can compose aspects by using three types of conditions. The first one is the default, the AND conditions, where an entity must possess all of the specified component types.
The Aspect will require the entity to possess A and B and C:
Aspect.getAspectForAll(A, B, C)
The Aspect will require the entity to possess at least one of X or Y or Z:
Aspect.getAspectForOne(X, Y, Z)
The Aspect will require the entity to possess at least one of X or Y or Z:
Aspect.getAspectForAll(A, B, C).exclude(U, V)
Or all entities possessing (A,B,C) and one of (X,Y,Z) but none of (U,V):
Aspect.getAspectForAll(A, B, C).one(X, Y, Z).exclude(U, V)
You can consider the matching as: (AND mix) && (OR mix) && !(OR mix)
These are really the most important aspect of the framework. Entity Systems are meant to process certain aspects of entities, or entities possessing certain components.
Each system goes through a certain execution flow, from start to end. You can override methods like initialize, begin, and end to help you execute certain game logic.
Initialize is called first and only once, when you do World.initialize(). There you can create your ComponentMappers and look up managers or other systems.
Begin is called at the beginning of processing of the system, before the entities are processed.
End is called at the end of processing of the system, after the entities have been processed.
If you wish to disable your system for some reason, e.g. if it's a debugging system that is only supposed to display debugging information when enabled, you can override a method called checkProcessing, and make it return false, or true if you wish to enable it again.
You can override added and removed methods in each system. There you will be notified about entities that were either added into or removed from the system.
Entities or components do not communicate between themselves, but systems can communicate with other systems. You can retrieve other systems within your system by using the World instance. You can define whatever methods you deem necessery for inter-system communication.
The framework comes with a three types of EntitySystem implementations, and for each of the three an implementation that processes a single entity a time, which is typically what you want to do most of the time.
The most raw entity system. It should not typically be used, but you can create your own entity system handling by extending this. It is recommended that you use the other provided entity system implementations.
Use this system as your default one. It will process entities one at a time.
A system that processes entities at a interval in milliseconds. If you wish to fine tune your game for optimal performance you can set some systems to be processed with a longer interval to save resources.
Typical usage would be a collision system, where you would only want to check for collisions every 100 milliseconds, or a physics system that can run at 10 millisecond intervals.
They can also be used at longer intervals, seconds or minutes, to do certain tasks, like checking for game state in multiplayer game, checking if the player has killed all the monsters, etc.
This system will do the same as IntervalEntitySystem, but provides you with a way of processing a single entity at a time.
The purpose of this class is to allow systems to execute at varying intervals. An example system would be an ExpirationSystem, that deletes entities after a certain lifetime. Instead of running a system that decrements a timeLeft value for each entity, you can simply use this system to execute in a future at a time of the shortest lived entity, and then reset the system to run at a time in a future at a time of the shortest lived entity, etc.
Another example system would be an AnimationSystem. You know when you have to animate a certain entity, e.g. in 300 milliseconds. So you can set the system to run in 300 ms. to perform the animation. This will save CPU cycles in some scenarios.
There are three methods you must implement, processDelta(..) means you have to decrement the delta for the specified entity, getRemainingDelay(..) must return the delay that remains for the specified entity, and processExpired(..) will be called when an specified entity expires.
This system has an empty aspect so it processes no entities, but it still gets invoked. You can use this system if you need to execute some game logic and not have to concern yourself about aspects or entities.
Pending.
Pending.
Thanks to Alessandro Bricalli for providing help writing the manual.
- Overview
- Concepts
- Getting Started
- Using
- More guides
- Plugins
- Game Gallery
- Tools and Frameworks
- API reference