If you enjoy this tutorial, please consider supporting it by purchasing a book through one of the links on my site, such as this one ->
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.This week we take a break from working on our projectile code to create a shared repository of useful classes, which we’ll call the shared context. The shared context is a class that contains references to other classes that we want our components to have access to. It all sounds very simple and luckily it is.Creating a shared context involves a number of steps:
- Writing the shared context and adding references to the classes we want shared.
- Ensuring every object has access to the shared context.
- Removing any local references that components currently store and instead using the references in the shared context.
- Removing any setters/mutators from components for classes that are now included in the shared context.
At the moment the classes we want to include in the context are:
- Input: we want any component to be able to query the keyboard state.
- ObjectCollection: we want any component to be able to add other objects to the game, such as our projectile attack component creating new arrow objects.
- WorkingDirectory: to retrieve anything from the disk we first need to retrieve the location of the asset.
- TextureAllocator: some of our components (like our projectile attack) can add textures to objects.
- Window: any component that acts as a camera or interacts with the player’s view will require access to the Window class.
This list will almost certainly expand in future as we create new components. Now that we’ve decided which classes we want to include we can write the SharedContext class.The class is simple especially as I’ve opted not to write mutators and accessors, although feel free to include them if you wish.We want every object and it’s components to have access to the shared context. To achieve this change the Objects constructor so that it accepts a reference to the shared context. Again you can create a getter/setter for the context if you wish. Now when you create an object you need to pass it the shared context. Right now we’re only creating objects in our game scene so that’s where we’ll instantiate SharedContext. We may wish to change this in future so that we can share these classes between scenes. Now SceneGame owns the context it will also be its job to initialise the pointers. We’ll do this first thing in the OnCreate function. This ensures that any object created does not have to worry about null pointers in the context. As we’ve changed the objects default constructor we also need to change how we create new objects: we now have to pass the shared context. This includes the creation of our player object in the OnCreate function (just after initialising the pointers).
I kept the previous way of instantiating objects as a reminder of how we used to do it. The only change is that when creating the shared object we pass a reference to the context. Our tilemap parser will also need a context to create each tile object. We’ll pass the context in the constructor and store a reference to be used when creating tiles.Now when we create new tile objects in the Parse function we pass a pointer to the context. We need to change the call to the TileMapParser constructor in SceneGame. The last object instantiation that needs to be changed is the creation of the projectile object in the C_ProjectileAttack component. As each object has a reference to the context and every component can access its own object we can use that reference. This shows how we can access the context from any component. Now that it’s possible to access the shared context we no longer need many of the mutator functions we created previously. We need to remove pointers and their corresponding set functions in the C_Sprite, C_KeyboardMovement, C_Camera, and our recently written C_ProjectileAttack classes. Let’s start with C_Sprite. Remove the function from the implementation file as well and change any references to the local texture allocator to use the one in our shared context instead. Remove the references to the recently removed SetTextureAllocator function in TileMapParser::Parse, SceneGame::OnCreate and C_ProjectileAttack::SpawnProjectile. That’s it for C_Sprite, now we’ll do the same for C_KeyboardMovement by removing it’s SetInput function and its local reference to the Input class (which has now been moved to the shared context). Again we need to remove the function from the implementation file and replace any references to the Input class with that in the context. Remove the call to SetInput from SceneGame::OnCreate. We’re almost there, only the camera and projectile class to go. Let’s update the camera class next by removing its SetWindow function and the local pointer to the Window class.
Again remember to remove the function from the implementation file and change the calls to the window class to retrieve it from the shared context instead. We once again need to edit SceneGame::OnCreate, this time to remove the call to SetWindow. Last up is our new projectile attack component, we no longer need to set the input, object collection, working directory, or texture allocator manually. Once again, and for the last time, we need to remove the functions from the implementation file and change how we reference the classes we’ve just removed. We’re nearly there I promise! The last step to implementing the shared context is to remove the calls to the mutator functions (the ones we’ve just deleted) from SceneGame::OnCreate. Because we’ve made numerous small changes to the SceneGame::OnCreate function over the course of this tutorial you may want to refer to the implementation in the Github repo to make sure that your code matches mine.And that’s it, when you run the game everything should still work in the same way (if not then let me know in the comments), but in future we no longer need to write mutator functions in our components for common classes, we can simply retrieve them from the shared repository that we created this week.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 🙂