— 2D Santa game — Note: this tutorial assumes knowledge of C, as well as prior tutorials.
Introduction We have our ground scrolling nicely, so it's time to move onto the next important part of our game - adding houses. In this part, we're going to introduce our houses, and have them move from right to left, along with the ground. Extract the archive, run cmake CMakeLists.txt, followed by make, and then use ./santa02 to run the code. You will see a window open like the one above, with the ground moving from right to left. Houses will appear at random intervals moving from the right side of the screen to the left. When you're finished, close the window to exit. Inspecting the code The aim of our game will be to throw gifts (and coal!) down the chimneys of houses that Santa's sleigh flies over. Our houses will come in two flavours - Nice houses (everyone is in bed, all the lights are off), and Naughty houses, where the upstairs lights are still on because the children are still awake. There will be an equal chance of both types of houses spawning. We'll start by looking at the changes to defs.h:
We've added in an enum, to define our entity types (ET). You'll likely recognise this enum pattern, as used in many of the previous tutorials. Here, we've defined an entity type of NONE and another of HOUSE. That's it for defs.h, so it's over next to structs.h, where we've introduced two new structs and also made some updates:
We've added in an Entity struct. As you can no doubt see, it's rather par for the course. We've got a `type`; `x` and `y` variables, to hold positional data; `dx` and `dy` variables, to hold velocities; a `texture`; and a flag for marking the entity as `dead`. We're also defining some function pointers, `tick`, `draw`, `touch`, and `die`. Moving on, we've added the House struct:
At the moment, it has just one field: a variable called `naughty`. This will determine whether our house is on the Naughty or Nice list. The flag will control how it is drawn and, later on, how it responds to gift / coal deliveries. Lastly, we've updated the Stage struct:
We've added in our entity linked list, with the head and tail variables. Nice and straightforward so far. So, on to our first new file: house.c. This is where we'll be handling all the logic and rendering for our houses. Everything in house.c is rather easy to understand, so we'll start with initHouse:
This function is pretty simple to understand. We're first checking if we need to load our house textures (we've got a few different house colours, to keep things interesting), and then we're setting up two variables, `x` and `y`. `x` will be positioned off the right of the screen, while `y` aligns to the ground (based on the height of the first house texture - all houses are the same size). With that done, we test if we're free to position a house here by calling a function named canAddEntity (defined in entities.c - we'll see more on this later), passing in `x`, `y`, and the width and height of the house texture. Should we be allowed to, we're mallocing a House, spawning an entity, and assigning all the relevant details. Our house has a 50/50 chance of being Naughty or Nice. The house's texture is also chosen at random from our houseTextures array. Nothing taxing. So, let's look at all the individual function pointers. As we saw, our Entity supports `tick`, `draw`, `touch`, and `die`. Right now, we're only setting up `tick` and `draw` here. We'll look at `tick` first:
Here, we can see why we wanted to know the speed of the Stage. Each time we call `tick` (once per frame), we're going to decrease the value of our house's `x` value by Stage's `speed`. This will make the house move left at the same speed of the ground. We then test to see if the house has moved completely off the left-hand side of the screen (taking into account its texture's width), and setting its `dead` flag to 1 if so. This will basically mean that our house will be removed from the game when its moved off screen. Our `draw` function follows. Once again, it's easy enough to understand:
We're always drawing the house's texture. Next, we're testing if this is a Naughty house, and if so we're overlaying a texture called houseLights on top of it. The houseLights texture is a transparent PNG of the same dimensions as our house, but with the top bedroom light on. There were a few choices when it came to indicating a Naughty house - perhaps a marker above the house itself, a radar system, or perhaps something outside the house itself. There was no particular reason for selecting the bedroom light, though the "radar" was quickly ruled out as it could be difficult to read and understand. The final function is loadTextures:
Nothing special here. As with the loadTextures function in stage.c, we're loading a series of textures, using formatted strings. We're also loading our houseLights texture here. That's all there is to house.c for now. We'll take a brief look at entities.c, though it will once again be very familiar. Starting with initEntities:
We're simply preparing the Entity linked list in Stage here. On to doEntities:
A standard loop to process our entities. For each entity in the loop, we're calling the `tick` function, testing whether the entity is `dead`, and calling its `die` function if on is set. Unlike some of the other games we've made, we're not pushing the entities into a "dead list", as there is no chance of dangling pointer references here, so we don't need to guard against dereferencing a NULL object. The canAddEntity function comes next:
This function takes a rectangle as its input (`x`, `y`, `w` (width), and `h` (height)), and loops through all the entities in the stage, to see if it overlaps any of them. If so, it will return 0. Otherwise it will return 1. In short, it's testing whether the desired location is occupied. We don't want our houses (or other entities) to overlap one another. Next up is the drawEntities function:
Again, nothing special - we're just looping through all our entities and calling their assigned `draw` function. spawnEntity comes next:
Here, we're creating an entity, adding it to Stage's linked list, and returning it. We've seen this many times before, too. That's it for entities.c, so let's return to stage.c, to look at the updates and additions we've made. Starting with initStage:
We've added a call to initEntities, and also added in a variable called houseSpawnTimer (static in entities.c), that will control how often we attempt to add in a new house. We set it to an eighth of the width of the screen to begin with, so that a house doesn't appear right away. Next, we've updated doStage, our logic step:
We're calling a new function here, addHouse, as well as doEntities. We'll take a look at what addHouse does now:
We're decreasing the value of houseSpawnTimer, according to the speed of the stage itself, so that houses will appear at a consistent rate. Whenever houseSpawnTimer falls to 0 or less, we're calling initHouse, to create a new house at the right-hand side of the screen. We're then setting houseSpawnTimer to a random value of half the screen's width. This will help to space out the houses (and our canAddEntity function will prevent overlaps). Finally, we've updated drawStage:
We're now calling drawEntities, to render how entities. And that's it for adding in our houses. There's still much to do, but as you can see, we've already got a nicely scrolling ground with houses erected on top of it. These houses will continue arriving from the right and move to the left for as long as the program runs. But wait, doesn't Santa deliver presents via chimneys? Our houses don't have any chimneys just yet, so Santa would be unable to do so. We'll change that in our next part, by adding chimneys to the houses when they are created. 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 | |
Desktop site |