3
\$\begingroup\$

In a simple game object class, you might have something like this:

public abstract class GameObject {

    protected String name;
    // other properties
    protected double x, y;

    public GameObject(String name, double x, double y) {

        // etc
    }

    // setters, getters
}

I was thinking, since a lot of game objects (ex. generic monsters) will share the same name, movement speed, attack power, etc, it would be better to have all that information shared between all monsters of the same type.

So I decided to have an abstract class "ObjectData" to hold all this shared information. So whenever I create a generic monster, I would use the same pre-created "ObjectData" for it. Now the above class becomes more like this:

public abstract class GameObject {

    protected ObjectData data;
    protected double x, y;

    public GameObject(ObjectData data, double x, double y) {

        // etc
    }

    // setters, getters

    public String getName() {

        return data.getName();
    }
}

So to tailor this specifically for a Monster (could be done in a very similar way for Npcs, etc), I would add 2 classes. Monster which extends GameObject, and MonsterData which extends ObjectData. Now I'll have something like this:

public class Monster extends GameObject {

    public Monster(MonsterData data, double x, double y) {

        super(data, x, y);
    }
}

This is where my design question comes in. Since MonsterData would hold data specific to a generic monster (and would vary with what say NpcData holds), what would be the best way to access this extra information in a system like this? At the moment, since the data variable is of type ObjectData, I'll have to cast data to MonsterData whenever I use it inside the Monster class.

One solution I thought of is this, but this might be bad practice:

public class Monster extends GameObject {

    private MonsterData data; // <- this part here

    public Monster(MonsterData data, double x, double y) {

        super(data, x, y);

        this.data = data; // <- this part here
    }
}

I've read that for one I should generically avoid overwriting the underlying classes variables.

What do you guys think of this solution? Is it bad practice? Do you have any better solutions? Is the design in general bad? How should I redesign this if it is?

Thanks in advanced for any replies, and sorry about the long question. Hopefully it all makes sense!

\$\endgroup\$

1 Answer 1

7
\$\begingroup\$

Separating out data that is shared across instances from data that is unique per-instance is a fairly common approach but which is done in a variety of ways. Many think of that as the Flyweight pattern, where the container becomes less 'heavy' due to effectively outsourcing the shared data.

It starts getting less useful when you begin deriving different types of game object and different types of data though, because you end up trying to store some very different data objects in the base class, which can't make full use of the data (because much of it is specific to the derived class).

As usual with many programming problems, if inheritance is proving a bit awkward, you should just use composition instead. Lumps of data aren't a good match for inheritance because only the methods work equally well on base and derived classes, but you're not really using any methods that apply to both. So, I wouldn't derive MonsterData from ObjectData; I would make MonsterData contain an ObjectData. Then I pass the MonsterData to the Monster constructor, and that constructor passes the ObjectData member to the GameObject constructor via super().

Personally, I have made systems like this before and found them to work well, but often the structure gets in the way of future features - such as when you want to create a monster that is 10% stronger than a normal one, or one with a slightly different title. You sometimes end up migrating things out of the data blocks and into the main class, one by one. Or, you can just abandon this structure, and make complete copies of data from templates instead, knowing that you can change absolutely any part of the object after or during creation if you need. Then you're using something more like the Prototype pattern where new objects are made via copying. This is usually less efficient in memory terms but often more flexible.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ +1 for prototype pattern. The way to go IMO. There are ways to mix both approaches, where data is shared until it changes for a specific instance which then owns its own value. Anyways, I'd stick to the pure prototype approach since, well, most PCs nowadays have A LOT of horsepower. \$\endgroup\$
    – kaoD
    Apr 9, 2012 at 11:35
  • \$\begingroup\$ Thanks! Great explanation. It also never occurred to me that I could have ObjectData contained within MonsterData too. \$\endgroup\$
    – Mick
    Apr 9, 2012 at 12:25

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .