Well, as some kind of solution I may suggest that in your case you should use something called Double Dispatch: the pattern Visitor actually.
Let's look at your example briefly:
- If the player and a monster collide, the player should loose a life.
- If a monster and a bullet collide, the monster should die.
- If the player and an NPC collide, a dialog should start.
- If the player and an item collide, the player should pick it up.
The code can be like this:
public interface ICollideable
{
void resolveCollision( ICollideable collideable );
void resolveCollision( Monster monster );
void resolveCollision( Npc npc );
void resolveCollision( Player npc );
void resolveCollision( Item npc );
void resolveCollision( Bullet npc );
}
public class Entity implements ICollideable
{
public abstract void resolveCollision( ICollideable collideable );
// virtual methods.
public void resolveCollision( Monster monster ) { }
public void resolveCollision( Npc npc ) { }
public void resolveCollision( Player npc ) { }
public void resolveCollision( Item npc ) { }
public void resolveCollision( Bullet npc ) { }
}
public class Player extends Entity
{
@Override
public void resolveCollision( ICollideable collideable )
{
collideable.ResolveCollision( this );
}
@Override
public void resolveCollision( Monster monster )
{
this.health -= monster.getDamage();
}
@Override
public void resolveCollision( Item item )
{
this.inventory.addItem( item );
}
@Override
public void resolveCollision( Npc npc )
{
NavigationManager.openQuestListUI( this, npc );
}
}
public class Monster extends Entity
{
@Override
public void resolveCollision( ICollideable collideable )
{
collideable.ResolveCollision( this );
}
@Override
public void resolveCollision( Bullet bullet )
{
this.die();
}
}
Sorry, there may be some mistakes, because I'm not really good with java syntax. Such approach is useful if your language supports methods overloading (like java or c#).
P.S. You can extend the example and implement both reactions of player and monster (remove health from player and monster on interaction).
Links:
- https://en.wikipedia.org/wiki/Visitor_pattern
- https://sourcemaking.com/design_patterns/visitor/java/2
Update 1.
There are of course some alternatives floating around, however they are more about whole-game-architect patterns. I will try shortly discuss them:
- Entity Component Systems (ECS)
In ECS-like code you want to move far away from holistic classes and objects, so you split the whole state and behaviour into three parts:
a) Component
- the pure data. For example Position
is just a Component
with X
, Y
and Z
(depends on business-logic ofc).
b) Entity
- just an ID with which you can associate a set of components with no intersections in types (no way to get two Position
in one Entity
at all).
c) System
- static "whole-universe" things, that can handle Entities
with specified set of Components
. Good example - GravitySystem
. It takes all the Entities
with Position
, Mass
and changes their Velocity
. Then MovementSystem
or VelocitySystem
then process the velocity and turn it into change of Position
.
This is just a starting explanation. There are a lot of questions about ECS
with nice answers. You can find them by tag entity-system
.
- Dynamic message-passing (event-passing)
For example, instead of calling resolveCollision
method, you pass the message (like CollisionWithAnotherEntityOccured
) to whole Entity
. It then will use something called MessageBus
to re-transfer the message to all interested Components
. Maybe there will be AttackerModule
with code:
public void onCollision( CollisionWithAnotherEntityOccured message )
{
var weapon = GetComponent<Weapon>(); // Unity-style.
message.Entity.Pass( new AttackMessage( weapon.getDamage() ) );
}
The responsibility to handle AttackMessage
is now on attacked entity, and if and only if it has HealthComponent
with AttackReceiver
you can perform a success attack.
This is of course simplified example to demonstrate some powers of such decoupling. It is greatly used in SQRS
-pattern.
I hope this additional explanation will help you to choose some way to deal with your current and future problems.