You're correct that the problem (well, nuisance) is the if
statements. Sadly, no matter what language your working in or what programming fanciness you employ you are going to have to check some component data / entity types / other flags on the colliding entity that will tell the present entity how to react to a collision.
Good Practices (my experience)
There are 2 practices that I have found useful to minimize these checks.
First of all, I find it helpful to think about which entities would actually dictate the collision outcome in real life. Take your Alien
for example. Imagine a real alien colliding with a real missile, and consider two scenarios:
The alien, upon making contact with the missile says to himself:
By jove, it appears I've struck a missile. My alien behaviour manual says that I must promptly commit suicide.
after which he pops out a potassium cyanide capsule from between his teeth and swallows.
Another possibility is that the missile, upon colliding with the alien says to itself:
Wot 'ave we got 'ere? An alien et seems. Looks loike I'm 'upposed to make a right massive explosion.
The missle then detonates itself.
Neither of these scenarios are realistic, but the second one is a lot closer to what would actually happen than the first. When the Missile
collides with an Alien
the reaction should be to blow up. This should, in turn, trigger another event / function call to deal damage to surrounding entities (And methods for doing that have already been discussed). Once the Alien
's health reaches 0, then he can kill himself1. The code for when the Alien
's reaction when colliding with a Missile
is ... nothing; there's nothing the Alien
can really do at this point.
I found that when I continually asked myself Who's really dictating this collision outcome?, the elseif
chains for checking types became remarkably short. For example, for a large number of Entity
types it doesn't matter what the missile is colliding with, the response will be to explode. This might be the default behaviour (i.e. the else
at the end of your if
-elseif
chain). In a similar way it's the Wall
who stops the Alien
, not the Alien
who stops when it collides with a Wall
. Then all of a sudden it matters little what the moving entity is when it collides with a Wall
; the reaction will almost always be to stop it (otherEnt.setVelocity(0,0)
).
If you use some variation of an Entity-Component System you can (in some cases, I have found) check for the existence of a component on the opposite entity to determine a subsequent reaction. I won't go into detail about that here since it will make the answer much longer than it already is, and you don't seem to be using an ECS (yet) anyway.
Long elseif
chains: A Game Design flaw?
Finally, I will say that if you find yourself coding long elseif
chains, it could be symptomatic of a design issue: the rules of your game could be too complicated. If you have a long list of different behaviours for each combination of colliding entities, your players will need to memorize (through frustrating trial-and-error or mind-numbing tutorials) all of these potential collision outcomes. If the challenge of memorizing possible collision outcomes is part of what makes the game fun (and you carefully introduce these rules), then long elseif
chains are to be expected, otherwise, they're sure to be the source of many curse words and damaged controllers / keyboards. If players are confident about the effects certain actions will have, they spend more time thinking about strategy and less time wondering how the game world works.
An example: Portals
Consider Portal for example: there are walls that support portals (white panels, tan walls, etc...) and the rest don't. There are no walls that make small portals, nor are there walls that permit only orange portals, nor are there walls that periodically support portals... It's easy for game designers and players to understand. So the game could be designed so that when the blue/orange projectile that is shot from the portal gun (not sure what that thing's called) hits a wall it does this:
listOfPortalWalls = {'White Panel', 'Tan Wall', 'Concrete Floor', /*etc...*/}
...
class PortalBall{
...
void handleCollision{
if(listOfPortalWalls.contains(otherEnt.type)) //We can make a portal :)
/* Test if there's already a portal there (edge case, don't sweat the if statement here) */
/* Make a portal */
else //We can't make a portal :(
/* Explode the portal ball with a discouraging sound effect */
}
}
That elseif
chain was so short it didn't even have an elseif
! Notice that it's the PortalBall
's job to make the portal (as I would expect if portals were real). The wall wouldn't do anything. All the player has to figure out is which walls support portals. The portal gun never runs out of energy and it has a pretty short cooldown (not to mention that there are seldom time pressure situations), so experimenting to figure out which walls support portals is a painless experience.
Edit:
To clarify, the elseif
chains are a fine way of going about things IMO; but you can make things easier by carefully probing which entities are actually responsible for the outcomes of a collision, and placing that code in the relevant Entity
subclasses.
1 This case could potentially be made more realistic by making a system that slowly removes the Alien's available actions restricts behaviours as the health is lowered (e.g. when health is below 50% can no longer attack, and max movement speed is proportional to health). So that when the health reaches zero the alien is a complete vegetable (effectively dead). I would not recommend doing this excessively because if you keep trying to approximate realism your game may become needlessly complicated, making both debugging harder (more code) and game design harder (more complicated game states).
else if
s, and more importantly type checking, is bad practice, and that Polimorphism should be used instead. However I`m not an experienced game dev, so I'm not sure how and if this applies to game development. In your opinion, is the situation I described okay? Having lots of type checking but encapsulated in the relevant classes? \$\endgroup\$ – Aviv Cohn Jun 16 '14 at 11:06