How can I improve my code design to remove the need for 'instanceof' in Java?

Viewed 61

I've decided to try to create a simple game engine and I appear to be stuck on how to check for collisions between different Collider types for my game objects without the need for using 'instanceof' repeatedly. For example, some game objects may use a SphereCollider and others may use an AABBCollider, both of which extend the Collider class.

Here is the code within my GameObject class.

public final boolean collidesWith(Collider c) {
    if (this.collider instanceof Collider3D) {
        if (c instanceof AABBCollider3D) {
            if (((Collider3D) this.collider).collidesWith((AABBCollider3D) c))
                return true;
        } else if (c instanceof SphereCollider3D) {
            if (((Collider3D) this.collider).collidesWith((SphereCollider3D) c))
                return true;
        } else if (c instanceof AABBCollider2D) {
            if (((Collider3D) this.collider).intersects((AABBCollider2D) c))
                return true;
        }
    } else if (this.collider instanceof Collider2D) {
        // Do 2D collision stuff...
    }
}

Collider3D class

public abstract class Collider3D extends Collider {

public Collider3D(RigidBody rigidBody) {
    super(rigidBody);
}


public abstract boolean collidesWith(AABBCollider3D collider);

public abstract boolean collidesWith(SphereCollider3D collider);

public abstract boolean intersects(AABBCollider2D collider);

public abstract boolean intersects(CircleCollider2D collider);

}

AABBCollider3D class

public class AABBCollider3D extends Collider3D {

private final Cuboid collisionBounds;


public AABBCollider3D(RigidBody rigidBody) {
    super(rigidBody);

    this.collisionBounds = new Cuboid();
}


@Override
public void update() {
    final Vector3f objectLocation = getRigidBody().getObject().getLocation().getPosition();
    final Matrix4f transformMatrix = getRigidBody().getObject().getTransformationMatrix();

    final float width = transformMatrix.m03();
    final float height = transformMatrix.m13();
    final float depth = transformMatrix.m23();

    // Set the collision bounds' location
    collisionBounds.getLocation().setPosition(
            objectLocation.x,
            objectLocation.y,
            objectLocation.z);  

    // Set the dimension of the collision bounds
    collisionBounds.setWidth(width);
    collisionBounds.setHeight(height);
    collisionBounds.setDepth(depth);
}

@Override
public boolean collidesWith(AABBCollider3D collider) {
    // Check collisions...
}

@Override
public boolean collidesWith(SphereCollider3D collider) {
    // Check collisions...
}

@Override
public boolean intersects(AABBCollider2D collider) {
    // Check collisions...
}

@Override
public boolean intersects(CircleCollider2D collider) {
    // Check collisions...
}

}

I would like to know if there is a better way of doing this, mainly for two reasons:

  1. I've heard using the 'instanceof' keyword to check object types in this way is a workaround for bad code design
  2. Adding custom Collider types in the future would require me to extend the logic here, as well as in my Collider class
1 Answers

Add a methods in to your Collider type for each special subtype. This is a sort of degenerate visitor pattern.

So it will look something like:

interface Collider {
    boolean collidesWith(Collider other);

    // I've used the same name here,
    //   not necessary but makes things easier.
    boolean collidedBy(AABBCollider3D collider);
    boolean collidedBy(SphereCollider3D collider);
    // ...
}

Each subtype will look like:

public class AABBCollider3D implements Collider {
    public boolean collidesWith(Collider other) {
        return other.collidedBy(this);
    }

    public boolean collidedBy(AABBCollider3D collider) {
        // ... code for AABBCollider3D collided by AABBCollider3D.
    }
    public boolean collidedBy(SphereCollider3D collider) {
        // ... code for AABBCollider3D collided by SphereCollider3D.
    }
    // ...
}

This will create mutual dependencies between Collider subtypes. To reduce the impact of this split the type between where this is necessary and where it is not.

Related