If you care about software, you’ve probably heard of design models: MVC, interior designers, factories, etc. But there’s an awesome design model that’s perfect for modeling complex behavior, but it’s not known outside of the gaming industry.

We have built a storytelling sci-fi MMORPG called Tau Station. Like many developers, we quickly hit the development of complex products. At first it seemed like we wanted to create Item class and charge for it.

But what if you want to create a military-level battle suit? Yes, it acts as an armor. But it can shoot back, so it acts as a weapon. And it can heal small wounds, so it also acts as a medic.

Have you ever wondered how games handle complex objects with multiple patterns of behavior? Many older game articles recommended several legacies despite known problems. Today, games often use the Entity-Component-System (ECS) system. It’s ridiculously powerful and you will often find that creating a new, complex class simply adds a few entries to the database.

Entity-component system

ECS was first developed for the award-winning game Thief: A dark project. It is an efficient way to implement complex object behavior without complicated programming. It has three parts:

  • Entities – Weapons, cars, trees and so on.
  • Components – Armor, a weapon, a medical device or anything can change your behavior
  • System – A system that responds to components of entities

Note that in ECS you are dealing with data, not objects. It is very important to remember.

Now imagine building a game for kids where they photograph animals in the woods. Each “unit” can be an animal, a plant, a trap, or any combination (Venus’s fly line can be both a plant and a trap). The core game loop might look like this (code examples in Perl):

my $game = My::Awesome::Game->new(%parameters);
while ( $game->is_running ) {
    $game->get_user_input;
    $game->update_state;
    $game->draw;
}

update_state the method might look like this:

sub update_state($self) {
    $self->hide_from_danger;
    $self->forage_for_food;
    $self->move;
    $self->photograph_available_animals;
}

And finally forage_for_food the method may look like this:

sub forage_for_food ($self) {
    foreach my $entity ( $self->local_entities ) {
        if ( is_animal($entity) ) {
            $self->search_for_food($entity);
        }

        # we can't use "elsif" here because more than
        # one entity type might apply to the entity
        if ( is_plant($entity) ) {
            $self->react_to_sunlight($entity);
        }

        if ( is_trap($entity) ) {
            $self->trigger_trap($entity);
        }
    }
}

Entities are just data packets and helper functions, such as is_animal, is_plantand is_trap identify whether the entity has a particular component.

But we are an online game. Our game is controlled by the citizens of Tau Station by clicking on the links, not the game loop. So how do we handle this?

ECS network

Database

Tau Station doesn’t have a game loop, but it’s certainly not a problem. Instead, we review behavior as needed, and the main concern is how we create our entity. To do this, we have a simple view that pulls all the related data into one SQL query:

         SELECT me.item_id, me.name, me.mass, ...   -- global
                ar.component_armor_id,   ...        -- armor
                md.component_medical_id, ...        -- med
                wp.component_weapon_id,  ...        -- weapon
           FROM items me
LEFT OUTER JOIN component_armors   ar ON ar.item_id = me.item_id
LEFT OUTER JOIN component_medicals md ON md.item_id = me.item_id
LEFT OUTER JOIN component_weapons  wp ON wp.item_id = me.item_id
          WHERE me.slug = ?

There are a few things to consider about SQL. First, each item has a unique, human-readable snail that we use in the URL. For example, the “Combat Suit” advantage is combat-suit. It is used in the SQL parameter (question mark).

LEFT OUTER JOIN parts mean that the component columns are NULL if the line is not found.

Finally, for many objects, this SQL can be very inefficient. However, all of our products are unchanged, which means that the results of the above query are cached, and player lists contain “object occurrences” that may have more information (such as damage level).

Code

The access code for the information above is quite simple. Our strategy has global characteristics such as “name”, “mass” and so on. It’s ours items table. Each product may have an armor component (component_armors table), weapon component (component_weapons table) and so on. We have Item category (it should have been named Entity) and although it is a class, it has read-only privileges to retrieve object properties and predicate methods to test components:

sub is_armor  ($self) { defined $self->component_armor_id  }
sub is_weapon ($self) { defined $self->component_weapon_id }
sub is_med    ($self) { defined $self->component_med_id    }

Because we used to LEFT OUTER JOINs in our code, is_armor the predicate method returns false if component_armor_id Is NULL. This means, finally, that if someone wants to equip their armor, we can easily check this:

sub equip_armor($self, $item_slug) {

    # logs and throws an exception if item not found
    my $item = $self->resolve_entity($item_slug);

    if ( $item->is_armor ) {
        # equip it
    }
    else {
        # don't equip it
    }
}

It is a pity that ECS is not known outside the gaming industry, but when I spoke about the status of Amsterdam Tau, one London finance company realized that it was the perfect way to model complex financial instruments. I have seen a lot of code databases where ECS would offer tremendous benefits. It is a “composition of heritage” and it is knowledge-based. That is a huge victory.

If you want to learn more about Tau Station and how to build it, here is a presentation I gave in Amsterdam.

.

LEAVE A REPLY

Please enter your comment!
Please enter your name here