In my previous post, Entity/Component Game Design: A Primer », I reviewed some of the main differences between traditional object-oriented game design and the emerging paradigm of entity/component game design. I deliberately avoided talking about Artemis, and focused instead on the weaknesses of the alternative approaches, because I believe Artemis is able to resolve many of those problems.
Here I want to talk about the Artemis framework for a while. I keep saying I’ll write about it, but I wanted to get a little more familiar with it first. Well, I’ve been playing with it pretty solidly for about three weeks now, and I feel like I’ve got enough experience to form some kind of an opinion. Although there’s a brief overview on it’s homepage, I want to present my own perspective of it, and talk about some of my reasons for falling in love with it.
Big thanks to the authors Arni Arent (appel) and Tiago Costa (Spiegel) for all their hard work and support!
The Artemis approach
The Artemis framework inherits the concept of Entities representing bags of Components, but it does away with the OO concept of encapsulating behaviour within the components. Instead, it adopts a data-driven design involving the separation of the data and logic of each component.
In essence, the Components serve as little more than bags of data. They have no update function, no internal logic, just getter and setter functions to expose their current values.
The logic is instead encapsulated in the Systems that accompany the entities and components. A ‘System’ is simply an object that reads and updates the data in any relevant components. You could say it’s simply the update function of the relevant components refactored into its own object.
I say “relevant components” because of how the systems do their processing. Instead of the game iterating imperatively through all of the entities and updating each in turn, the systems take more of a functional programming approach. Each system specifies a set of components that it’s interested in, and then in each frame it processes only those entities that contain all specified components.
Furthermore, each system exists to perform a specific role, updating all instances of the relevant components before passing control to the next system. You could say that the components are processed in cross-sections across all entities at once, rather than entities (and all of their child components) being processed in chunks.
That’s more or less how Artemis goes about its business, but without having used it, it may not be immediately obvious why this is a Good Thing. Following are a few reasons why I believe it’s a Very Good Thing. Of course, you can only truly appreciate the details after working with it, so I’ll try to keep the discussion at a high level.
The magic of systems is that they have no reservations in accessing multiple types of component at a time. The systems compose the communication between related components so that the components themselves don’t need to. Systems typically process one entity of interest at a time, and for the period in which they’re processing, they can combine any of the components within each entity however they like, with no structural side-effects.
For example, a MovementSystem could process all entities with Position and Velocity components, and each frame it would update the values of each Position component given the associated Velocity component. A RenderSystem could process all entities with Position and RenderableMesh components, and it would draw each mesh to the screen given the values from the updated Position component. In neither case do the components require any knowledge of each other.
Refactoring the logic this way essentially gives you two layers of components – data components (Components) and logic components (Systems) which read/write the data components. Since the Components are pure data, they should never need to change. A 3D Velocity vector will always have X, Y and Z components, and so will be perfectly reusable in any 3D scenario.
The logic in the systems will depend on the rules of the game world, and they can be interchanged as needed. Since each system is typically self-sufficient enough to be autonomous, changing the behaviour of a system will not necessitate a change in the behaviour of any other system.
Say you want/have to change your game from a 3D free-roaming game to a 3D side-scroller. For side-scrolling movement, all you need to do is plug in a side-scrolling MovementSystem (it could be exactly the same, but simply discard the X component of the velocity), and it will automatically apply to all entities with Position and Velocity components. No components or other systems need to change as a result of the change of MovementSystem, since no components or systems even know it exists. (You may then need to update the CameraSystem and ControlSystem as well in this example, but the same process applies.)
Because the entities are simple bags of components, and the components are simple bags of data, as a developer you really only need to concern yourself with crafting the systems. The systems can easily take care of composing the relevant components within each entity, and beyond that they have no restrictions in how they process things. This means you can focus on constructing effective systems to solve problems in creative ways.
For example, I have a single CollisionSystem which detects and records all collisions in a single pass, after which I have an OO hierarchy of collision handling classes down which collisions are passed to be handled appropriately. I have an asynchronous TimerSystem which, together with a Timer component and external handler class, lets me attach and listen for delayed events with registered callbacks on individual entities with a single static function call. This may not be the “right” way to do it (and there often is no “right” way), but that’s up to me as the developer. I have the freedom to experiment, and to choose what works best, without sacrificing any part of the framework.
All Artemis processing occurs within a managed World object. For convenience, the world object contains four managers that make your job almost too easy:
- EntityManager manages the creation and removal of entities and their components, and allows components to be retrieved from entities by type.
- GroupManager allows entities to be assigned to unique groups (string IDs, such as “ENEMIES”) and retrieved as a group at any time in any system.
- TagManager allows individual entities to be assigned uniquely identifying tags (again, string IDs, such as “PLAYER”) and retrieved individually, similarly to groups.
- SystemManager stores all of your systems, allowing them to be retrieved individually by type if necessary (for instance, the RenderSystem may need to communicate with the CameraSystem).
The Artemis library itself is very lightweight (for some sense of comparison, Artemis is 30KB compared to the ~1.5MB of Slick + LWJGL), and the internals are built for performance at larger scales. I haven’t yet reached the point where performance becomes and issue, so I can’t comment on quite how far that is, but that alone is an endorsement. Author testing has managed to create/remove a few thousand rendered entities per second, a number which goes up to millions without rendering.
On top of that, the functional programming approach to component processing has inherent potential for performance gains, if taken advantage of. I haven’t read too much into this yet as it hasn’t been necessary, but I’ll talk about it at a later date.
Despite appearances, this is just a static outline of how the Artemis framework fits together and some of the things it does well by itself. I’m not going to dive into all the different things Artemis allows you to do; I’ll try to cover some of those in separate posts in the near future.
I will however say that when using Artemis, the development process changes radically (for the better). You can tackle the implementation of one aspect of your game at a time in isolation. You have a clear implementation path to follow. As your library of components grows, adding new entity types to the game is as simple as creating a new Entity object and adding a different set of Components (or just modifying parameters), and they are automatically handled by the relevant Systems that you already have in place. At that point, it’s almost trivial to read in all your entities from a text file and be truly data-driven.
It’s a pleasure to work with, and every day I’m thinking of new ways to do things that make it even easier. If this article piqued any curiosity or excitement in your inquisitive mind, then I strongly encourage you to investigate using Artemis with Slick. Start by downloading the demo game, StarWarrior, and browsing through the source code. Then try making some small changes to transform it into a different game; perhaps something like Artemoids? Where you take it next is up to you and your imagination.
Just remember to keep an open mind, and have fun! I’m no expert, but I’m more than happy to give whatever advice I can, and the Slick forums are always friendly.
- Artemis Entity System Framework – The official homepage of the Artemis project. It’s quite new (about a month old), and rather small as I mentioned. From the homepage you can get the compiled JAR, source (JAR, SVN, Git), JavaDoc, and the demo StarWarrior game (inspired by Space Invaders). There’s also an unofficial C# port going on.
- Artemis thread in the Slick forums – Artemis was originally developed for Slick, and so that’s where most of the discussion is so far. I should mention however that it’s not dependent on Slick, and appel is already using it with libgdx.
- For those of you interested in what I’ve been discussing, I joined in on page 12.
- Entity Systems are the future of MMOG development (T=Machine) – A very thorough application to MMOs, and a primary inspiration for Artemis.
- Anything on this site with the “artemis” tag – The count of this content will be increasing consistently in the future, because Artemis is a lot of fun to talk about.