• 2D shoot 'em up
SDL2 turn-based strategy tutorial
Water Closet ported to PlayStation Vita
The Legend of Edgar 1.35
SDL2 Rogue tutorial
— A simple turn-based strategy game —
The current camera we have in place works pretty well, but one problem is that when it comes to the AI turn (and with the player unit selection), the camera jumps straight to the unit. This can be disorientating, and doesn't look great. In this part, we're going to change the camera, so it moves smoothly to its target.
Extract the archive, run cmake CMakeLists.txt, followed by make, and then use ./sdl2TBS22 to run the code. You will see a window open like the one above, showing three wizards in a maze-like map, as well as a number of ghosts. Play the game as normal. Notice how the camera smoothly moves from one unit to another, as needed. This helps us to see the battlefield in context, and doesn't lead to any sudden lurches. Once you're finished, close the window to exit.
Inspecting the code
As expected, the major focus of the updates in this part is to the camera code, with other parts of the code requiring just simple one-line changes.
Let's being with structs.h:
Our Stage struct is all we need to update here. We've added to the existing anonymous `camera` struct inside of it. We're now introduced four new fields: `tx` and `ty` are the target x and target y location that the camera wants to move to. This will be, for example, the x and y screen coordinates of a unit or map tile. `scrolling` is a flag to say whether the camera is currently in motion. When it is, it will block all interaction with the camera, sort of like Stage's `animating` flag. Finally, scrollDelay is a variable to act as a timer before the camera begins to move. In the AI's turn, we'll want to pause for a moment before going to the next enemy unit, to, again, do away with the whiplash effect of the camera suddenly shifting.
No, let's head over to camera.c, where we've made the bulk of the changes. Starting with doCamera, a new function:
The doCamera function is responsible for actually driving the motion of the camera. Our camera will move so long as the scrolling delay value is 0 and also if it is not yet near its target location.
The first thing we're doing is decreasing the value of camera's scrollDelay, locking it at 0. Next, we're testing whether camera's `scrolling` flag is set and also if scrollDelay is 0. If both of these are true, it means that our camera is free to move. We start by working out the difference between the camera's `x` and `y` and its `tx` (target x) and `ty` (target y). These, we're assigning to variables called diffX and diffY. With these known, we assign a variable called `dist` the greater of the two values. `dist` will now hold the maximum distance our target location is from our present location.
Next, we're dividing both diffX and diffY by 10, and then limiting these values to between 0 and MAX_SCROLL_SPEED (defined in camera.h as 12). What this will do is give us values that we use for the speed that the camera wants to move at. The further away the camera is, the greater the values of diffX and diffY. However, we don't want these values to be too large, as it will mean our camera will scroll too fast (or, as we had previously, jump instantly to the target location).
Now that we have our horizontal and vertical speeds (diffX and diffY), we test where the camera's `x` and `y` values are compared to its `tx` and `ty`. We'll then adjust the camera's `x` and `y` by diffX and diffY (either adding or subtracting) to make it move. So, in effect, we're making the camera's `x` and `y` increase or decrease in order to reach its target location.
Finally, we update the camera's `scrolling` flag. We want our camera to stop moving once it is within range of its `tx` and `ty` (we don't need it to be exact). If the value of dist is greater than MAX_SCROLL_SPEED, we're still not yet within reach of our target location, and so we'll set it to 1. Otherwise, we'll set it to 0. We don't want to try and match the location exactly here, as that will often not happen, and our camera will get stuck.
That's our main camera logic done! That wasn't too bad, eh? What follows for the rest of this part will also very straight forward. Continuing with the updates in camera.c, we've tweaked the centreCameraOn function:
It now takes an argument called scrollTo. This is a flag to say whether we want our camera to smoothly move to the target entity or immediately jump to them. As before, we're working out the screen position of the entity (as `x` and `y`), but are then testing whether the scrollTo flag is set. If so, we're setting the camera's `tx` and `ty` to the values of `x` and `y`. Otherwise, it's the camera's `x` and `y` that are updated as before. We're then setting camera's `scrolling` flag as the value of scrollTo, to make it move if required.
As we saw in doCamera, if we're scrolling, we'll move to the `tx` and `ty` values. This function now sets that up.
To demonstrate this, let's look at the change to ensureOnScreen:
To conform to the new function signature, we're passing over the extra parameter. In this case, we want the camera to smoothly scroll.
Now, let's head over to stage.c, where we've updated `logic` with a very important change:
Before all else, we're calling doCamera. We're then testing to see whether our camera's `scrolling` flag is set. If not, our game's logic is free to run as usual. Otherwise, our game will essentially pause while the camera is in motion.
That's all the major work to get our smooth camera flowing done. Now we just need to tweaks several other files to make use of the new function. These will be, as expected, all one or two line changes and additions.
First, let's head to ai.c, where we've updated nextUnit:
At the end of nextUnit, we're setting the camera's scrollDelay value to a quarter of a second. This is so that when the AI unit has finish taking its turn, the camera doesn't instantly move to the next unit or back to the player. This is the sole reason the scrollDelay value exists, so that the player isn't disorientated by the sudden shift.
Next, over to hud.c:
We've updated the call to centreCameraOn, to conform to the new function signature. Here, we're passing over 1, to make the camera smoothly scroll to the unit.
Heading over to player.c, we've done likewise with addPlayerUnits:
Here, however, we're telling the camera to jump straight to the player unit's location. This is the only place in the code we're doing this. We don't want the camera to fly across the map when the game starts, as it looks a bit odd.
And there you go! A camera that smoothly scrolls across the battlefield as we need it to. I'm sure you'll agree that this is far more preferrable than the jumping camera we had before, and it wasn't all that difficult to setup or incorporate.
One last major thing we want to do now is look into the map generation. Perhaps you've noticed that from time to time our game appears to freeze when starting up, as it creates the map. This freezing can become more pronounced if we simply increase the size of the map. Doubling the values, for example, will lead to the game getting stuck for several seconds. That's not very good. So, in our penultimate part, we'll look into solving this by using some threads, to make the experience a little more pleasant for the player.
The source code for all parts of this tutorial (including assets) is available for purchase:
It is also available as part of the SDL2 tutorial bundle:
If you do not wish to create an itch.io account, you can also purchase the tutorial bundle using PayPal, and then download the tutorials directly from the main tutorials page.