Question

I'm writing a RTS Game in Java and have run into some problem with polymorphism. To describe my issue I'll list a few objects/classes in the game: (I will state what each of them should be able to do within brackets)

  • Tank (Can move, can attack)
  • Tower (Can attack)
  • Factory (Can build tanks)
  • Tree (Can be farmed)
  • Tiger (Can move, can attack, can be farmed)

The ideal way to do it in my eyes would be a class for each unique ability i.e Movable, Attackable, Farmable, Constructible. This is different abilities that all of the listed objects have. And they are all a GameObject (highest class in hierarchy with common data for all objects).

Movable holds all code for moving an object (calculating speed, direction, new position and so on). Attackable holds code for following a target, firing, update fired shots etc. And same goes for the other ability classes.

So in my eyes this would be perfect:

public class Tank extends Movable, Attackable, GameObject {}
public class Tiger extends Movable, Attackable, Farmable, GameObject {}

Oviously Java doesn't allow for extending multiple classes. And I don't see how I could use interfaces to solve my polymorphism and class hierarchy issues.

Any ideas? The goal is of course not to repeat code that several objects in the game shares.

Was it helpful?

Solution

Movable, Attackable are kinds of possible behaviours of an object, so it's better for them to be interfaces.

As for implementation of those behaviours - to solve your problem of repeating code, you can have separate classes - let's name them services - for each behaviour, eg. MoveService, AttackService.

Then, you can either inject those services into the objects you're creating (eg. new Tank(myMoveService) ) or pass objects to those services, eg. MoveService.instance().move(myTank)

You can run some custom code for each class by calling a delegate method.

Example

interface Movable { public void onMove(); }

class Tank implements Movable {
    public void onMove() { 
        //tank moved! 
    }
}

class MoveService {
    public void move(Movable m) {
        // do what you need to do to move
        // invoke custom code by running a delegate method
        m.onMove();
    }
}

OTHER TIPS

I would suggest a combination of interfaces and composition. Here's an example of a Factory that uses generics to save some code.

interface UnitFactory<U extends Unit> {
    public U newUnit();
}

abstract class UnitFactoryBuilding<U, F extends AbstractUnitFactory<U>>
extends Building // assume 'Building extends GameObject'
implements UnitFactory<U> {

    final F factory;

    UnitFactoryBuilding(F factory) {
        this.factory = factory;
    }

    MapPosition getExitPoint() {
        /* return the point for the 'door' on the model */
    }

    @Override
    public U newUnit() {
        U unit = factory.newUnit();

        /* assume Building has a method that
         * returns the player that 'owns' it
         */
        getPlayer().deductResources(unit.getResourceCost());

        return unit;
    }
}

abstract class AbstractUnitFactory<U>
implements UnitFactory<U> {
    final Building owner;

    AbstractUnitFactory(Building owner) {
        this.owner = owner;
    }

    MapPosition getPositionForNewUnit() {
        return owner.getExitPoint();
    }
}

class TankFactory
extends AbstractUnitFactory<Tank> {

    TankFactory(TankFactoryBuilding owner) {
        super(owner);
    }

    @Override
    public Tank newUnit() {
        return new Tank(getPositionForNewUnit());
    }
}

class TankFactoryBuilding
extends UnitFactoryBuilding<Tank, TankFactory> {

    TankFactoryBuilding() {
        super(new TankFactory(this));
    }
}

It's a bit elaborate but something like that lets you keep your implementations separate while not having to duplicate a whole lot of code. Creating a new kind of Factory only requires you to write the last two classes.

You don't have to use generics for this kind of thing but it makes it convenient because TankFactoryBuilding can access its factory member as a TankFactory.

Movable and Attackable are just attributes.

Consider a unit with 'Stealth' ability it cannot be attacked when it is stealth but can be when it is not.

Object trees are about behaviour.

Classes such as Terrain, Unit, Building make more sense, as they would have broadly similar behaviour.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top