views:

180

answers:

7

I am programming a simple role playing game (to learn and for fun) and I'm at the point where I'm trying to come up with a way for game objects to interact with each other. There are two things I am trying to avoid.

  1. Creating a gigantic game object that can be anything and do everything
  2. Complexity - so I am staying away from a component based design like you see here

So with those parameters in mind I need advice on a good way for game objects to perform actions on each other.

For example

  • Creatures (Characters, Monsters, NPCs) can perform actions on Creatures or Items (weapons, potions, traps, doors)
  • Items can perform actions on Creatures or Items as well. An example would be a trap going off when a character tries to open a chest

What I've come up with is a PerformAction method that can take Creatures or Items as parameters. Like this

PerformAction(Creature sourceC, Item sourceI, Creature targetC, Item targetI)
// this will usually end up with 2 null params since
// only 1 source and 1 target will be valid

Or should I do this instead?

PerformAction(Object source, Object target)
// cast to correct types and continue

Or is there a completely different way I should be thinking about this?

+3  A: 

This sounds like a case for polymorphism. Instead of taking Item or Creature as an argument, make both of them derive (or implement) from ActionTarget or ActionSource. Let the implementation of Creature or Item determine which way to go from there.

You very rarely want to leave it so open by just taking Object. Even a little information is better than none.

Jon
A: 

What about having a method on your Actors (creatures, items) that Perform the action on a target(s). That way each item can act differently and you won't have one big massive method to deal with all the individual items/creatures.

example:

public abstract bool PerformAction(Object target);  //returns if object is a valid target and action was performed
Malfist
+1  A: 

in the Zork model, each action one can do to an object is expressed as a method of that object, e.g.

door.Open()
monster.Attack()

something generic like PerformAction will end up being a big ball of mud...

Steven A. Lowe
+4  A: 

This is a "double dispatch" problem. In regular OO programming, you "dispatch" the operation of a virtual method call to the concrete type of the class implementing the object instance you call against. A client doesn't need to know the actual implementation type, it is simply making a method call against an abstract type description. That's "single dispatch".

Most OO languages don't implement anything but single-dispatch. Double-dispatch is when the operation that needs to be called depends on two different objects. The standard mechanism for implementing double dispatch in OO languages without direct double-dispatch support is the "Visitor" design pattern. See the link for how to use this pattern.

David Gladfelter
BTW, an interesting variation is the "Acyclic Visitor." You can read more about it here: http://www.objectmentor.com/resources/articles/acv.pdf
David Gladfelter
A: 

I've had a similar situation to this, although mine wasn't Role playing, but devices that sometimes had similar characteristics to other devices, but also some characteristics that are unique. The key is to use Interfaces to define a class of actions, such as ICanAttack and then implement the particular method on the objects. If you need common code to handle this across multiple objects and there's no clear way to derive one from the other then you simply use a utility class with a static method to do the implementation:

public interface ICanAttack { void Attack(Character attackee); }
public class Character { ... }
public class Warrior : Character, ICanAttack 
{
    public void Attack(Character attackee) { CharacterUtils.Attack(this, attackee); }
}
public static class CharacterUtils 
{
    public static void Attack(Character attacker, Character attackee) { ... }
}

Then if you have code that needs to determine whether a character can or can't do something:

public void Process(Character myCharacter)
{
    ...
    ICanAttack attacker = null;
    if ((attacker = (myCharacter as ICanAttack)) != null) attacker.Attack(anotherCharacter);
}

This way, you explicitly know what capabilities any particular type of character has, you get good code reuse, and the code is relatively self-documenting. The main drawback to this is that it is easy to end up with objects that implement a LOT of interfaces, depending on how complex your game is.

Michael Bray
+2  A: 

I think you're examining too small a part of the problem; how do you even determine the arguments to the PerformAction function in the first place? Something outside of the PerformAction function already knows (or somehow must find out) whether the action it wants to invoke requires a target or not, and how many targets, and which item or character it's operating upon. Crucially, some part of the code must decide what operation is taking place. You've omitted that from the post but I think that is the absolute most important aspect, because it's the action that determines the required arguments. And once you know those arguments, you know the form of the function or method to invoke.

Say a character has opened a chest, and a trap goes off. You presumably already have code which is an event handler for the chest being opened, and you can easily pass in the character that did it. You also presumably already ascertained that the object was a trapped chest. So you have the information you need already:

// pseudocode
function on_opened(Character opener)
{
  this.triggerTrap(opener)
}

If you have a single Item class, the base implementation of triggerTrap will be empty, and you'll need to insert some sort of checks, eg. is_chest and is_trapped. If you have a derived Chest class, you'll probably just need is_trapped. But really, it's only as difficult as you make it.

Same goes for opening the chest in the first place: your input code will know who is acting (eg. the current player, or the current AI character), can determine what the target is (by finding an item under the mouse, or on the command line), and can determine the required action based on the input. It then simply becomes a case of looking up the right objects and calling the right method with those arguments.

item = get_object_under_cursor()
if item is not None:
    if currently_held_item is not None:
        player_use_item_on_other_item(currently_held_item, item)
    else
        player.use_item(item)
    return

character = get_character_under_cursor()
if character is not None:
    if character.is_friendly_to(player):
        player.talk_to(character)
    else
        player.attack(character)
    return

Keep it simple. :)

Kylotan
+1  A: 

You can try mixing the Command pattern with some clever use of interfaces to solve this:

// everything in the game (creature, item, hero, etc.) derives from this
public class Entity {}

// every action that can be performed derives from this
public abstract class Command
{
    public abstract void Perform(Entity source, Entity target);
}

// these are the capabilities an entity may have. these are how the Commands
// interact with entities:
public interface IDamageable
{
    void TakeDamage(int amount);
}

public interface IOpenable
{
    void Open();
}

public interface IMoveable
{
    void Move(int x, int y);
}

Then a derived Command downcasts to see if it can do what it needs to the target:

public class FireBallCommand : Command
{
    public override void Perform(Entity source, Entity target)
    {
        // a fireball hurts the target and blows it back
        var damageTarget = target as IDamageable;
        if (damageTarget != null)
        {
            damageTarget.TakeDamage(234);
        }

        var moveTarget = target as IMoveable;
        if (moveTarget != null)
        {
            moveTarget.Move(1, 1);
        }
    }
}

Note that:

  1. A derived Entity only has to implement the capabilities that are appropriate for it.

  2. The base Entity class doesn't have code for any capability. It's nice and simple.

  3. Commands can gracefully do nothing if an entity is unaffected by it.

munificent
So far I really like this approach. It feels like a simplified component based entity design. However, do all classes HAVE to derive from `Entity`? As long as the right interfaces are added can't I just have them derive from any class?
mikedev
No, they don't. It may be helpful for them to for other reasons (there may be stuff all entities in the game have in common that you want to be able to assume). Otherwise, you could just pass in object and it would work fine.
munificent