Gamasutra: The Art & Business of Making Gamesspacer
View All     RSS
November 19, 2018
arrowPress Releases
  • Editor-In-Chief:
    Kris Graft
  • Editor:
    Alex Wawro
  • Contributors:
    Chris Kerr
    Alissa McAloon
    Emma Kidwell
    Bryant Francis
    Katherine Cross
  • Advertising:
    Libby Kruse






If you enjoy reading this site, you might also want to check out these UBM Tech sites:


 

Object Design for Web-based Games

by Curtis Poe on 09/12/18 10:00:00 am   Featured Blogs

2 comments Share on Twitter    RSS

The following blog post, unless otherwise noted, was written by a member of Gamasutra’s community.
The thoughts and opinions expressed are those of the writer and not Gamasutra or its parent company.

 

We've built a narrative sci-fi MMORPG named Tau Station. Like most indie devs, we were quickly hit with the problem of developing complex items. If your weapons are only weapons, or your armor is only armor, you can create separate classes for them, inheriting from a common "Item" class having properties such as name, description, mass, and so on. Your subclasses would define item-specific behaviors and be populated with data from the database. It's simple and easy.

Combat Suit in Tau StationBut what if you want to create a military grade combat suit? Yes, it functions as armor. But it can shoot back, so it functions as a weapon. And it can patch up minor wounds, so it also functions as a medkit.

How do you handle building complex objects with multiple behaviors?

Some people recommend multiple inheritance, but then you're not only dealing with the well-known problems of multiple inheritance, but you're also creating a class for only one item. If you have many complex items, this also doesn't scale. Having to create a special class for every item, write tests for it (you do write tests, don't you?), and verifying that it doesn't have unintended consequences on gameplay is not an easy thing to do. It's time-consuming and, more importantly, expensive.

You want your game out there now, not in six months after you've written all of your custom classes.


The Entity-Component-System

If you've been developing games long enough, you've heard of the Entity-Component System (ECS). First developed for the excellent game Thief: The Dark Project, the ECS is a powerful way of introducing complex object behavior without complex programming. We have three parts to it:

  1. Entities—Guns, Cars, Trees, and so on.
  2. Components—Armor, Weapon, Medical, or whatever things might change behavior
  3. System—The system that handles the components of entities

Note that in ECS, you're dealing with data, not objects. That's very important to remember.

Now imagine you're building a game for children where they are photographing animals in the forest. Each "entity" might be an animal, a plant, a trap, or any combination (a Venus Fly Trap could 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;
}

The update_state 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, the forage_for_food method might 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 each entity type might apply
        # to a given entity
        if ( is_plant($entity) ) {
            $self->react_to_sunlight($entity);
        }

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

Entities are merely bundles of data and the helper functions, such as is_animal, is_plant, and is_trap identify if an entity has a particular component.

But we're a web-based game. Our game is driven by Tau Station citizen's clicking on links, not on a game loop. So how do we handle this?


ECS for the Web

The Database

We don't have a game loop, but that's not really a problem. Instead, we check behavior "as needed" and the main concern is how we create our entities. For that, we have a simple view which pulls all related data in a single 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 item me
LEFT OUTER JOIN component_armor   ar ON ar.item_id = me.item_id
LEFT OUTER JOIN component_medical md ON md.item_id = me.item_id
LEFT OUTER JOIN component_weapon  wp ON wp.item_id = me.item_id
          WHERE me.slug = ?

There are a few things to note about the SQL. First, all items have a unique, human-readable "slug" that we use in the URL. The "Combat Suit" slug, for example, is combat-suit. That's used in the bind parameter (the question mark) in the SQL.

The LEFT OUTER JOIN parts mean that the related columns will all be NULL if the row is not found.

Finally, for many items, this SQL could be extremely inefficient. However, all of our items are immutable, meaning that we cache the results of the above query and player inventories contain "item instances" which might have more information (such as damage level).

The Code

The code to use the above data is fairly simple. In our strategy, we have "global properties" such as "name", "mass", and so on. That's in our items table. Each item might have an armor component (component_armor table), a weapon component (component_weapon table), and so on. We have an Item class (it should have been named Entity, but I digress) and though it's a class, it only has read-only accessors for fetching item properties, and predicate methods for testing 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 LEFT OUTER JOINs in our code, the is_armor predicate method will return false if the component_armor_id is NULL. This means, finally, that if someone wants to equip 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 ( $armor->is_armor ) {
        # equip it
    }
    else {
        # don't equip it
    }
}

And there you have it. A simple, straight-forward way of using ECS for web-based games (or really, any game that isn't driven by an event loop).

Feel free to leave questions in the comments and I'll try to answer them.


Related Jobs

Monomi Park
Monomi Park — San Mateo, California, United States
[11.16.18]

Senior Game Engineer
Cold Iron Studios
Cold Iron Studios — San Jose, California, United States
[11.15.18]

Console Gameplay Engineer
Cold Iron Studios
Cold Iron Studios — San Jose, California, United States
[11.15.18]

Infrastructure Engineer
Cold Iron Studios
Cold Iron Studios — San Jose, California, United States
[11.15.18]

Site Reliability Engineer





Loading Comments

loader image