This is part of an ongoing series where we write a complete 2D game engine in C++ and SFML. A new tutorial is released every Monday. You can find the complete list of tutorials here and download the source code from the projects GitHub page.
Last week we wrote the code that will enable communication between objects when they collide. This communication can happen at three stages of a collision: when the collision occurs, every frame the collision is maintained, and when the collision ends. This week we are going to write a new component for our projectile that will remove it from the game when it collides with another object (so when collision first happens). It will be a simple component but will hopefully show you how to respond to collision events.Create a new component called C_RemoveObjectOnCollisionEnter. It’s a wordy title but it explains exactly what this class will do.
The class inherits from Component but it also is derived from C_Collidable, which is the system we were working on last week. We override OnCollisionEnter as we’ll use that function to remove the projectile at the beginning of a collision.For now, we’ll queue the object for removal as soon as it collides with another object. In future we could check the objects tag name so, for example, we only remove the object when it collides with other objects with the tag “Tile”. We will also want to use some form of object pooling but that’s a topic for a future tutorial.
Attach the component to newly spawned projectiles in C_ProjectileAttack.
If you run the game and fire off a few projectiles you’ll quickly run into a problem. As soon as the projectiles collide with an object we get a “bad access” exception.
Bad Access Exception.
You’ll receive this exception if you attempt to use a pointer that points to memory that has been deallocated. It’s not difficult to imagine what’s happening in our code. We’ve removed the projectile object from the game, and its memory has been automatically deallocated for us as we are using smart pointers; however something (in this case the collision system), still thinks it holds a valid pointer and is trying to call GetCollidable on the removed projectile.
Call Stack on Main Thread.
To start debugging the issue we should start where the projectile is supposedly removed from the collision systems collection. We can check if the code to remove the projectile is being called by setting a breakpoint.
Setting a breakpoint for removing the projectile from the collision system.
If we then set a breakpoint just after we clear the collision tree in the Update function or at the end of the ProcessRemovals function and inspect the contents of collidables you’ll notice something strange: it still contains the pointer to the projectile that we have supposedly removed.
Inspecting contents of collidables.If we look closely at the breakpoint navigator and step over the debug point we created (when we remove the drawable component) you’ll notice that the component is removed from the ‘layer’ (the name we gave each collidable layer as we iterate over them) however it is not removed from the collidables collection.
Projectile removed from layer but not collision collection.
This is because as we iterate over the layers in collidables:
We are actually creating a copy of the layer and then removing the collidables from that copy. This is not the intended behaviour and luckily is easy to fix, all we need to do is retrieve a reference to the layer:
So the full function now looks like this:
And that fixes the bad access exception. Now objects are successfully removed from the collision system and we can fire projectiles as much as we want!
Now would be a good time to perform an audit on our other range-based for loops. As a guideline we want to use:
- auto: when we want to make a copy of the original.
- auto*: when we want to read and modify the original and be able to change what the pointer references.
- const auto&: when we want to read and modify a property of the original but do not want to be able to change what the pointer references.
- const shared_ptr&: when we want a read-only copy of the original.
It should also be noted that a pointer and the thing it points to are two separate objects. Either, none or both may be const and a const pointer simply means that you cannot change where the pointer points.
This doesn’t affect us much as we generally use references and smart pointers but its good to be aware of.The first change we’ll make is to TilemapParser. As we loop over each layer in the map we are creating unnecessary copies.
While we’re in this function I’ve also taken the liberty to make a couple of other changes: I’ve removed the references to map size as we didn’t use them and I’ve also changed the type of the layer count to an unsigned int as we don’t want/expect it to be a negative number.The last change we make to the for loops (at least for now) is to change how we loop over the components in the Object class. We want to retrieve them all by const reference.
There are a couple of small house tidying tasks I want to do before we finish for the week. First, in our collision system we add objects to the quadtree as they are added to a collision layer, however each frame in the collision systems Update function we remove all collidables from the tree and then re-add them (we do this to partition the objects based on their new position). So we don’t need to add objects individually to the tree.
I also refactored how we insert a new collidable pair. We no longer create a vector on a separate line but use an initialisation list.In C_Projectile::SpawnProjectile we retrieve the current facing direction twice. So we’ll remove one of them:
And last change for the week, in TileMapParser::BuildLayer we retrieve the height value from the levels data file and don’t use it, so we’ll remove that line.
As always, if you have any suggestions for what you would like covered or are having any trouble implementing a feature, then let me know in the comments and I’ll get back to you as soon as I can.
Thank you for reading 🙂