PC Games
• Orb Tutorials
• 2D shoot 'em up Latest Updates
SDL2 Versus game tutorial
Download keys for SDL2 tutorials on itch.io
The Legend of Edgar 1.37
SDL2 Santa game tutorial 🎅
SDL2 Shooter 3 tutorial
Tags • android (3) • battle-for-the-solar-system (10) • blob-wars (10) • brexit (1) • code (6) • edgar (9) • games (43) • lasagne-monsters (1) • making-of (5) • match3 (1) • numberblocksonline (1) • orb (2) • site (1) • tanx (4) • three-guys (3) • three-guys-apocalypse (3) • tutorials (17) • water-closet (4) Books Alysha When her village is attacked and her friends and family are taken away to be sold as slaves, Alysha Tanner sets out on a quest across the world to track them down and return them home. Along the way, she is aided by the most unlikely of allies - the world's last remaining dragon. |
— Making a 2D split screen game — Note: this tutorial assumes knowledge of C, as well as prior tutorials.
Introduction There's lots of things in our game already, including a HUD and some nice effects. But what this game is missing (well, one thing..!) is the ability to collect power-ups and other such items. In SDL2 Versus, we'll had "pods" that the players can collect, that will either score them points, restore their health, etc. These pods will randomly appear in our zone, and be a valuable thing that the players can battle over. Extract the archive, run cmake CMakeLists.txt, followed by make, and then use ./versus10 to run the code. You will see a window open like the one above, with each player on either side of our zone. Use the default controls (or, at your option, copy the config.json file from a previous tutorial, to use that - remember to exit the game before copying the replacement file). Play the game as normal. Notice how small items appear on screen, that the player can guide their ships towards to collect. These can earn the player points, restore their health, restore their rockets, or grant them an energy shield for a short time. The pods will last for a short time before vanishing. When points are earned, the HUD will update accordingly for the player. Once you're finished, close the window to exit. Inspecting the code Our Pods are very much like power-ups in many other games (and some of our past tutorials). They appear on screen, and will grant the player the relevant ability or resource if the player makes contact with them. Our Pods are simple entities, and since we already have code in place for entity-to-entity collision checks, putting in these Pods is very straightforward. We'll start first with defs.h:
To begin with, we've added a new enum to our entity types. ET_POD will be used to specify that this entity is an item. Next, we've created a new enum:
This will represent our pod types (PT). We'll come to these in a little bit. Note that our enum values are named after the type of Pod that they will represent. Notice that we have a "max" entry. This is so that we can create an array to hold our pods models. Next, over to structs.h, where we've made some updates and additions. Starting with Player:
We've added a new variable here, called `score`. Predictably, this will hold the score for the player. Next up, we have the Pod struct:
This struct is used to hold all the data for a Pod. `type` is the type of pod this is (PT). thinkTime is used to control how long it will be before this Pod takes its next action (we don't want all our Pod to be doing heavily processing at the same time, or all the time, for that matter). `bob` is a variable used to control the gentle bobbing of the Pod on screen, while `health` is how long the Pod will live for. That's our definitions of our Pod. We can now move over to pod.c, where the logic for an individual Pod entity lives. There's quite a few function here, but nothing tricky to understand. Let's start with initPod:
This function creates of a Pod at the specified location (`x` and `y`), and of the specified type (`type`). We start by loading the various models that our Pods will use, if need be (models is static in pod.c, and is an array of PT_MAX in size), then create the Pod data itself (`p`). We set the various attributes, including giving the Pod a random amount of `health` (between 30 and 60 seconds). We create the actual entity (`e`) with the ET_POD type, and set the `model` from the appropriate index in the `models` array (our models array aligns with the type of pods available). All as expected. Now, over to `tick`:
Our `tick` function will slowly deplete our Pod's `health`, setting its `dead` flag to 1 if its `health` drops to 0 or less. We're also decreasing the Pod's thinkTime. If the thinkTime is 0 or less, we'll be calling a function named lookForPlayers. This is what causes the Pod to quickly move towards the closest player, so that the player doesn't need to perfectly align themselves with the Pod in order to pick it up. The Pod will do this every 1/4 of a second. We're also updating the Pod's `position`, based on its `dir`, to make it move, and also updating the `bob` value. We'll see more on the `bob` value when we come to drawing. First, let's look at the lookForPlayers function:
This function is pretty easy to understand. The Pod will loop through both our players, looking for one closer than 100 pixels (with help from the hypot function). If one is found, we'll set the pod's `dir` to make it move quickly towards the player (using normalizeVector, and a `d`x and `dy` variable pair). We'll update the thinkTime here to 1/8th of a second, to make it react much faster, now that it is homing in on a player; we want the item to be collected as fast as possible. One thing about this function is that it will favour the second player, in situations where both players are nearby. But, to be honest, if both players were that close, there's a good chance there is more to worry about..! If we wanted to make this fairer, we could calculate the distances to both players, and then choose the nearest one to move towards. Again, given the proximity, this isn't something we need concern ourselves with. Now, let's move on to `draw`:
Our `draw` function is pretty simple. We're first testing if the Pod still has at least two seconds left to live, or if the `health` value's modulo of 10 is greater than 5, and drawing our model as normal, taking the camera position into account. The reason for the health check before hand is that this allows us to make the Pod start to rapidly blink if its `health` is running low, signalling to the players that it is about to expire. Notice also that when drawing our Pod, we're updating the drawPosition's `y` value by the sin of `bob` (multiplied by 8). This means our Pod will move up and down in a small area, to draw the players' attentions. This is just an aesthetic thing, and our Pod doesn't actually move anywhere. Finally, we have the `touch` function, which is the most important function of all:
The first thing we do is check if the thing that has touched the Pod is a Player (ET_PLAYER); only Players can collect Pods, after all! next, we check what `type` of Pod this is, and respond appropriately. A score pod (PT_SCORE) will grant the player 25 points. A health pod (PT_HEALTH) will restore the Player's health to full. A shield pod (PT_SHIELD) will grant the Player a shield. An ammo Pod (PT_AMMO) will restore the Player's supply of rockets. With that done, we set the Pod's `dead` flag to 1, to remove it from the game. So, all as expected. The next new file we'll look at is pods.c (with a plural, since this file is for handling adding pods to our zone). First up, we have initPods:
We're setting a variable called randomPodTimer to 0. This variable controls how often we add new pods. We can see this in action in doPods:
We start be decreasing the value of randomPodTimer. When it falls to 0 or less, we'll call addRandomPod, and set randomPodTimer to a value between 1 and 10 seconds, for when a new pod will be created. The addRandomPod function itself is easy to enough to understand:
The function sets up a do-while loop, and attempts to choose a position within our zone where it can add a pod. We do this by making a call to canAddPod, passing over our randomly selected `x` and `y` coordinates. Once we've found somewhere to add our Pod, we'll randomly select its `type` (score Pods are most common, health pods the rarest), and call initPod, passing over the `x` and `y` values, as well as the Pod type (`t`). All nice and simple. The canAddPod is the last function in pods.c:
This function accepts `x` and `y` variables as coordinates, and then loops through all the triangles in our zone, testing to see if any number of points that our Pod might occupy are inside of a triangle. We're using two inner loops for this: `px` and `py` each go from -1 to 1, and are multiplied by 16, adding to the `x` and `y` passed into the function. This very roughly lets us know if the Pod might appear either inside a triangle, or is close to it. Should the area be unoccupied, we can add our Pod there. That's about it for our Pod logic. It's likely as you would expect, based on the tutorials that have preceeded this one. Before we finish up, let's quickly look at where we're using the functions from pods.c. Over to zone.c, we've first updated initZone:
We're calling initPods here. Next, we've updated `logic`:
Here, we're calling doPods. We've no need to update `draw`, since our Pods are entities. And finally, we've updated hud.c:
We're now drawing the Players' score! So, whenever we collect score Pods, we'll see this value increase. See, I told you the HUD would be useful! Another great step forward. Our Pods allow the players to score points, replenish their health, ammo, and gain a shield. Our game will let our players win the matches in a number of different ways, such as by scoring the most points, or be defeating their opponent in combat, and stripping them of all their lives. The Pods help us to greatly enhance this. But speaking of goals, there's currently nothing for the players to aim towards (by which I mean, there is no point to the game!). We can endlessly die and be recreated, and pick up lots of pods, but to what end..? Well, in the next part, we're going to introduce the concept of goals, so that the players have a reason to compete with one another. Purchase The source code for all parts of this tutorial (including assets) is available for purchase, as part of the SDL2 tutorials bundle: From itch.io |