Entity-Component game programming using JRuby and libGDX – part 2

Introduction

In Part 1 we explored EC nomenclature to set the stage for this part: learning how to work with entities, components and systems using an Entity Manager. (And if you are finding this post via Google or another entry, you can find the index to the full series in Part 1.)

As you’ll recall, your game-world “things” (Entities) are not traditional OO classes. EC Entities are skinny beings that exist merely to have a unique ID. Only after the Components are “attached” to an entity does it attain any personality.

Entities

Are you ready for your first entity? OK, here goes:

2acca0fb–2725–4c3d–9347-b202b012ef75

Don’t look at me with that dubious expression. You’re wondering, where’s the entity? That is it right there: a unique ID (in the form of a UUID in this case, but it could even just be an integer if you wanted). That’s a Tank, or Blue Player Base, or an Alien.

I can imagine you thinking, “All right wise guy, where’s the Entity class? Surely there’s an entity class to hold the ID attribute, right? After all, how can you expect to instantiate instances of the very game objects without an entity class?”

I remind you again, this is not OO, this is EC. In EC there is no need — no need — for an Entity class. Entities exist purely as IDs within the EntityManager.

The Entity Manager

The EntityManager is a class that governs the EC system. It is the fundamental kernel of EC. Its responsibilities include:

  • Create and kill entities
  • Maintain a list of all known entities
  • Map entities to their components
  • Retrieve entities’ component functionality on demand

In other words, the EntityManager is a bookkeeping device that serves as the master authority of all entities and their functionality.

[Note: yes, the EntityManager itself is a class, as are Components and Systems. I did promise that EC was a departure from OO, and no, using OO for the foundation framework doesn’t make me a liar.]

Here is a very basic start of our EntityManager:

class EntityManager
  def create_basic_entity
    uuid = java.util.UUID.randomUUID().to_s
    return uuid
  end
end

You do actually instantiate an EntityManager; in fact, you are not limited in how many you instantiate, but you must have one. We only need one for our basic game but the flexibility is there if your game’s complexity demands it.

@entity_manager = EntityManager.new
element=@entity_manager.create_basic_entity

Now, let’s agree that for convenience sometimes it’s nice to be able to grab a chunk of entities that are similar. It would be nice if we had a way to “tag” entities in some way, yes? We might want all the entities of a particular genre, or on a certain side. Let’s add this functionality. The tag is simply a free-form text you can use for entity grouping. That’ll come in handy.

class EntityManager
  def initialize(game)
    @id = java.util.UUID.randomUUID().to_s
    @ids_to_tags = Hash.new
    @tags_to_ids = Hash.new
  end
  def create_basic_entity
    uuid = java.util.UUID.randomUUID().to_s
    return uuid
  end
  def create_tagged_entity(human_readable_tag)
    raise ArgumentError, "Must specify tag" if human_readable_tag.nil?
    uuid=create_basic_entity
    @ids_to_tags[uuid]=human_readable_tag
    if @tags_to_ids.has_key? human_readable_tag
      @tags_to_ids[human_readable_tag]<<uuid
    else
      @tags_to_ids[human_readable_tag]=[uuid]
    end
    return uuid
  end
end

You’ll notice that so far we are creating entities but the EntityManager isn’t managing or maintaining them at all. For this we need to establish a data structure to hold them. It’ll be a hash-of-hashes called @component_stores.

Component Storage

The Component Stores data structure is a hash-of-hashes. Each inner hash is called a component store. In aggregate, component stores.

This is one of those situations where a picture is worth a thousand words:

ECS presentation.011

The outer hash — the hash with the purple braces — is keyed by component class. In this contrived example there are two Components that an entity could have: Gun, and GravitySensitive. As you can see, this outer hash could allow us to discover which entities have a particular component by querying on that component’s class name.

Each component store itself — the hash with the gray braces, circled in the shaded oval — maps the entities to their associated components. Remember, each entity is an ID, so it would be tautological to say “entity IDs” here, and certainly incorrect to refer to them as “entity instances”. (Recall that entities are not classes and therefore are not instantiable. While you are learning EC you can be forgiven for thinking of them as “entity instances” if it helps make the transition, but try to discard that habit as soon as you can. Repeat to yourself the mantra: entities are IDs and nothing else.)

On the other hand, components (which are simple unintelligent bags of data, you’ll recall) are instantiable.

Consulting the picture you can easily see that Entity 12456 has two Gun components (two gun instances) whereas Entity 23456 has but one. The actual gun data itself — shots per second, damage per hit, accuracy, name, type — all live inside the Gun component instances. The Component Stores structure merely links components to their entities.

Expanding the Entity Manager

Let’s expand our EntityManager to utilize a component_stores data structure.

class EntityManager
  def initialize(game)
    @id          = java.util.UUID.randomUUID().to_s
    @game        = game
    @ids_to_tags = Hash.new
    @tags_to_ids = Hash.new
    @component_stores = Hash.new
  end

  # [ Snip... ]

  def add_component(entity_uuid, component)
    raise ArgumentError, "UUID and component must be specified" if entity_uuid.nil? || component.nil?

    # Get the store for this component class.
    # If it doesn't exist, make it.
    store = @component_stores[component.class]
    if store.nil?
      store = Hash.new
      @component_stores[component.class]=store
    end

    if store.has_key? entity_uuid
      store[entity_uuid] << component unless store[entity_uuid].include? component
    else
      store[entity_uuid] = [component]
    end
  end

  def has_component_of_type(entity_uuid, component_class)
    raise ArgumentError, "UUID and component class must be specified" if entity_uuid.nil? || component_class.nil?

    store = @component_stores[component_class]
    if store.nil?
      return false # NOBODY has this component type
    else
      return store.has_key?(entity_uuid) && store[entity_uuid].size > 0
    end
  end

  def has_component(entity_uuid, component)
    raise ArgumentError, "UUID and component must be specified" if entity_uuid.nil? || component.nil?

    store = @component_stores[component.class]
    if store.nil?
      return false # NOBODY has this component type
    else
      return store.has_key?(entity_uuid) && store[entity_uuid].include?(component)
    end
  end

We have expanded our EntityManager functionality. At creation time we instantiate a blank component_stores instance variable. We can now link a component with an entity. Furthermore, we can now query whether a particular entity has a specific component or a component of a certain type (e.g. Gun).

It is worth noting that this data structure is extremely fast to work with. The lookups are speedy and inexpensive. The entity keys are small, being mere ID values, and the components are agreeably small too since they are simple data.

Concluding this post, consider this parting thought: the EntityManager data structure represents the entirety of your game state. Let that sink in for a moment, and marry that with your knowledge of Ruby’s built-in data serialization. Saving and loading game state just became trivial, a topic that I will address in a later post of this series…

Coming Up

In the next installment we’ll learn about components…

About these ads

2 thoughts on “Entity-Component game programming using JRuby and libGDX – part 2

  1. Hi, I’ve read up to part 4 of this article series and am now writing a basic game with E-C, but I’ve come over a small bump :

    What is the “game” variable to you pass the EntityManager constructor?

    • You’re absolutely right – that is clearly unnecessary and is just leftover cruft from an earlier iteration. I just pushed the cleanup to Github.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s