Yes, thank you Chrome, only one of those is a word, and yet Jario now has both! Sounds were ridiculously easy to add through a generic SoundSystem with a handful of static sounds (courtesy of http://www.freesound.org/) and a static queue-push method, and then the single-line calls were peppered throughout the systems as required. The result is a game which is a lot more fun to play! Despite still being about 60 seconds long.
Go ahead, try it out!
The issue of passability is a more technical one. Previously, all detected collisions were handled directly by each of the colliding entities. This is great for many cases, but there are some times when you don’t want to handle a collision, or when you want to not handle it under certain conditions, and for which adding in a bunch of conditional guards is ugly and inefficient.
My solution was to sneak an extra layer into my collision handling hierarchy (which I’ll talk more about when it’s a little more polished), right up the top, to manage collision persistence. In this context, persistence means that when two entities collide, all future collisions that are detected until they stop colliding can be grouped into a single collision, and handled only once at the beginning. This is important for a number of gameplay mechanics.
For example, the two cases I added just now were semi-passable platforms and improved handling of player recovery. Semi-passable platforms are those ones that you can jump through from below, as well as stand on top of. One way to get this behaviour is to say “if the collision is not from the top edge, do nothing, otherwise block the entity”. This may work, depending on your collision detection mechanism, but for the time being mine is very basic, and detects the edge of the collision based on the X and Y distance the two entities are overlapping by. The result is that if you jump through from the bottom, a bunch of collisions will occur for the handful of frames you’re passing through, and as you are about to exit from the top, one of those collisions will be detected as “top edge”, and the default behaviour is to place the player on top of the platform. The behaviour we want is for the player to keep jumping, eventually fall back down, and then be placed on the platform.
My solution is a little more complex than perhaps it needs to be, but it’s a good starting point. When a collision occurs, the handling systems can register the collision as a “passing through” case, after which future collisions will not be handled by the entity that is passing through (the ‘passer’) until the entities separate. However, the entity being passed through (the ‘passable’) will continue to receive handling calls in case the circumstances of its passability change. It can therefore also check whether the colliding entity has been previously registered as a passer. All of this handling is contained in PersistentEntityHandlingSystem class, which all current collision handlers extend.
In the case of the platforms, this is straightforward. On the first collision, if the player collides with a non-top edge of the platform, the platform registers the player as a passer, and the player continues unhindered. Once the player exits at the top, they are unregistered from the platform, and the next collision will be handled appropriately on the top edge.
Player recovery is a little trickier. By “recovery” I mean the period of invulnerability you get after being damaged with >1 health. During this time, you shouldn’t be able to stomp enemies from inside (i.e. walk into them from the left, then jump and kill them on the way out). So for this case, when the player collides with an enemy, he first checks whether he’s in recovery mode, and if so, if the enemy is not already registered as passing through, then the registration occurs. The enemy passing through the player means that the enemy is oblivious to the collisions (and so can’t die until the player leaves and comes back), but the player’s handler is still called each frame. Therefore when the test for recovery fails (i.e. recovery time has expired), then the enemy is unregistered as a passer, and the collision can be handled normally.
But wait, there’s more. It’s possible for recovery to expire while the player is standing right in the middle of the enemy, and if the position is right, it will register as a top-edge collision, and “handling normally” means the enemy will get stomped. This is not the desired behaviour – if the the player is standing inside the enemy when recovery expires, the player should get stomped instead. To work around this, I added a parallel map that records which passable-passer relationships should be unregistered in the next frame. This gives the player collision handler a chance to handle the collision manually and correctly (take damage) before unregistering. If the player manually takes damage AND unregisters in that frame, by the time the enemy handles the collision it will appear unregistered, and the potential top-edge problem persists. By delaying it one frame, the player’s damage will have been fully accounted for, and the enemy collision will not occur at all.
Problem solved! You can see all that in PlayerHandlingSystem if you like. Like I said, it’s quite complex and I’d like to simplify it a bit, but it’s a start. By experimenting with this approach, I’ve gained even more of an appreciation of the Artemis framework. You really can tackle your problems one at a time, in isolation. Even in the case of a two-entity collision, the collision handling of the two entities can be neatly decoupled. The future is looking bright!