Item Design in MMORPGs

Still hard at work hacking on Veure (the image to the right is a freighter, by the way). I tend to get up early in the morning so I can get a couple of hours in before turning to my primary contracts. Now I'm redesigning the item system and it's a slow, frustrating process because I have such limited time. And if there is one thing that frustrates many game designers, it's how to design items in games. Fortunately, I have a fairly clear approach, thanks to a comment Aristotle made a long time ago. I'm implementing a "web friendly" version of the Entity-Component-System pattern (ECS).

As you're traveling from star system to star system, you may encounter a number of people who aren't terribly friendly, so weapons, armor, medical items and other things will come in handy. One type of combat suit in Veure is called Reactive Armor. It's an armor designed to deflect kinetic energy attacks by discharging small explosives before they're about to hit (in case you're wondering, this is a real thing, but for tanks). Because getting into a fight with someone who wears Reactive Armor means multiple small explosions, you might hit them and get hurt yourself. However, that only happens if you're in close quarters combat (i.e., close enough to physically hit them), not if you're firing a pulse rifle at them.

So reactive armor is both armor and a weapon. And as a weapon, it can only damage the opponent if they're in hand-to-hand combat with you. That can be a fair chunk 'o stuff to model and how do you shove all of this into a single object?

Enter the ECS model. In this model, every "thing" (entity) in the game is comprised of a bunch of data. For example, you might have the following bits of data for the Reactive Armor:

  • Name
  • Description
  • Encumbrance
  • Damage
  • Accuracy
  • ArmorEnergy
  • ArmorKinetic

Of those attributes, the first three are general attributes which could apply to anything. The next two are "weapons" attributes and the last two are clearly armor related. So we have a generic "item" which also is classified as both a weapon and an armor. It's an "entity" comprised of "components." How does that work in gameplay? That's the "system" part.

In a traditional first-person shooter, you have a game loop. Here's a tremendously oversimplified version of that loop, in pseudo-code.

game.is_running(true)

while game.is_running() {
    game.get_user_input()
    game.update_state()
    game.draw()
}

That leaves a lot of stuff out (such as keeping a consistent frame rate, regardless of the CPU speed), but that's more or less what a traditional game loop looks like. It's the game.update_state bit which is really important for us. In that method call, we iterate over our list of entities and do stuff. Maybe it looks sort of like this:

method update_state() {
    hide_from_danger()
    forage_for_food()
    move()
    attack_enemies()
}

And the forage_for_food() method:

method forage_for_food() {
    foreach entity in entities() {
        next unless is_animal(entity) and needs_food(entity)
        search_for_food(entity)
    }
}

Here we're iterating over our big list of entities and we only take action if the entities have certain properties. Note that in this style of programming, it's the system which figures out what to do with the entities. The entities themselves only have state, not behavior. The system is then responsible for knowing if the entities can do a thing. Thus, almost any entity in the game can function as a weapon so long as the programmer adds weapon attributes.

There are a few problems with this approach. One is that it might not be scalable. Iterating over a list of 2 million entities every game tick probably isn't going to be very fast, particularly if you iterate over that list for every possible action (you can partition your entities to only iterate over likely candidates).

Second, Veure is a text MMORPG running in a browser; it doesn't have a traditional game loop. However, that actually works to our advantage here. I'm not drawing space stations or updating their positions -- they are simply something you can travel to and do things on. The same goes for jump gates, space ships, and so on. The primary items that I might want to use ECS for are items that your character might carry and use. I don't need to draw them, update their position, or worry about many other facets that a video game might. Thus, if you get into a fight with another character, I'm probably only iterating over your equipped items. And how do I handle the case of Reactive Armor? If you get hit in combat, it might look like this:

if defender.armor.is_weapon() {

    // that last item would ordinarily be blank
    // we'd default to equipped weapon
    if did_hit(defender, attacker, armor) {
        // the did_hit() method will, amongst other things, 
        // check attacker.weapon.is_hand_to_hand() and abort
        // if we're not close enough. Otherwise, calculate the odds
        // of hitting our attacker and calculate damage
    }
}

(If you're really paying attention, you might notice that the above suggests that other types of armor might also weapons.)

In my "web" version of ECS, any given action knows which entities will be available to inspect and can ask them if they have the appropriate attributes. Thus, items have no methods, but they have a lot of data.

So how do I create the items? I've previously discussed views in DBIx::Class. To test this idea (and so far it's working well), I have created an inventory view and the primary definition looks like this (I'm omitting field names to hide some game play aspects):

__PACKAGE__->result_source_instance->view_definition(<<'SQL');
         SELECT me.item_id,  ... -- global attributes
                ar.item_component_armor_id, ... -- armor attributes
                md.item_component_mod_id, ... -- mod attributes
                wp.item_component_weapon_id, ... -- weapon attributes
                it.name     AS primary_component,
                it.uri_name AS primary_component_uri,
                ci.quantity
           FROM item me
LEFT OUTER JOIN item_component_armor   ar ON ar.item_id      = me.item_id
LEFT OUTER JOIN item_component_mod     md ON md.item_id      = me.item_id
LEFT OUTER JOIN item_component_weapon  wp ON wp.item_id      = me.item_id
           JOIN item_primary_component pc ON pc.item_id      = me.item_id
           JOIN item_type              it ON it.item_type_id = pc.item_type_id
           JOIN character_item         ci ON ci.item_id      = me.item_id
           JOIN character              c  ON c.character_id  = ci.character_id
          WHERE c.character_id = ?
       ORDER BY it.uri_name, me.name
SQL

sub is_armor      { $_[0]->item_component_armor_id }
sub is_weapon     { $_[0]->item_component_weapon_id }
sub is_mod        { $_[0]->item_component_mod_id }

With the above, the left outer joins represent properties that components may or may not have.With proper indexing, that should be a relatively fast query, but unlike my previous DBIx::Class views, this is one which could benefit from a view. Particularly, a materialized view since the items are relatively static. As I add more item types, such as medical items), the above query will grow even more and adding a materialized view will really simplify things.

If you're familiar with roles, you probably see some similarities with the above: you can easily build new objects out of a variety of different parts. Because the objects (entities) have only data and not behavior, they're much easier to test. Already I'm ripping out earlier portions of my item system and replacing them with the above because it's making the code simpler.

4 Comments

entity-systems.wikidot.com has some interesting articles on ECS.

I've not heard of ECS before, but I love the concept. I used roles to build things like ships in Lacuna, but I can see how this could have been even better. Thanks!

A good thing to do, when designing items for your game, is to think both at the armor and at the weapons that will collide with the armor, in this way you can have many ideas of what the armor and weapon can or should do.

Leave a comment

About Ovid

user-pic Freelance Perl/Testing/Agile consultant and trainer. See http://www.allaroundtheworld.fr/ for our services. If you have a problem with Perl, we will solve it for you. And don't forget to buy my book! http://www.amazon.com/Beginning-Perl-Curtis-Poe/dp/1118013840/