Growing the branches
Next, as I have been promising for the last 20 pages, we will use all the new C++ techniques we've learned about to draw and move some branches on our tree.
Add the following code outside of the main function. Just to be absolutely clear, I mean before the code for int main():
#include <sstream>
#include <SFML/Graphics.hpp>
using namespace sf;
// Function declaration
void updateBranches(int seed);
const int NUM_BRANCHES = 6;
Sprite branches[NUM_BRANCHES];
// Where is the player/branch?
// Left or Right
enum class side { LEFT, RIGHT, NONE };
side branchPositions[NUM_BRANCHES];
int main()
We just achieved quite a few things with that new code:
- First, we wrote a function prototype for a function called updateBranches. We can see that it does not return a value (void) and that it takes an int argument called seed. We will write the function definition soon, and we will then see exactly what it does.
- Next, we declare an int constant called NUM_BRANCHES and initialize it to 6. There will be six moving branches on the tree, and we will soon see how NUM_BRANCHES is useful to us.
- Following this, we declare an array of Sprite objects called branches that can hold six Sprite instances.
- After that, we declare a new enumeration called side with three possible values: LEFT, RIGHT, and NONE. This will be used to describe the position of individual branches, as well as the player, in a few places throughout our code.
- Finally, in the preceding code, we initialize an array of side types with a size of NUM_BRANCHES (6). To be clear about what this achieves, we will have an array called branchPositions with six values in it. Each of these values is of the side type and can be either LEFT, RIGHT, or NONE.
Important note
Of course, what you really want to know is why the constant, two arrays, and the enumeration were declared outside of the main function. By declaring them above main, they now have global scope. To describe this in another way, the constant, two arrays, and the enumeration have scope for the entire game. This means we can access and use them all anywhere in the main function and the updateBranches function. Note that it is good practice to make all the variables as local to where they are actually used as possible. It might seem useful to make everything global, but this leads to hard-to-read and error-prone code.
Preparing the branches
Now, we will prepare our six Sprite objects and load them into the branches array. Add the following highlighted code just before our game loop:
// Position the text
FloatRect textRect = messageText.getLocalBounds();
messageText.setOrigin(textRect.left +
textRect.width / 2.0f,
textRect.top +
textRect.height / 2.0f);
messageText.setPosition(1920 / 2.0f, 1080 / 2.0f);
scoreText.setPosition(20, 20);
// Prepare 6 branches
Texture textureBranch;
textureBranch.loadFromFile("graphics/branch.png");
// Set the texture for each branch sprite
for (int i = 0; i < NUM_BRANCHES; i++) {
branches[i].setTexture(textureBranch);
branches[i].setPosition(-2000, -2000);
// Set the sprite's origin to dead centre
// We can then spin it round without changing its position
branches[i].setOrigin(220, 20);
}
while (window.isOpen())
In the preceding code, we are doing the following:
- First, we declare an SFML Texture object and load the branch.png graphic into it.
- Next, we create a for loop that sets i to zero and increments i by one on each pass through the loop until i is no longer less than NUM_BRANCHES. This is exactly right because NUM_BRANCHES is 6 and the branches array has positions 0 through 5.
- Inside the for loop, we set the Texture for each Sprite in the branches array with setTexture and then hide it off-screen with setPosition.
- Finally, we set the origin (the point that is used to locate the sprite when it is drawn) with setOrigin, to the center of the sprite. Soon, we will be rotating these sprites. Having the origin in the center means they will spin nicely around, without moving the sprite out of position.
Now that we have prepared all the branches, we can write some code to update them all each frame.
Updating the branch sprites each frame
In the following code, we will set the position of all the sprites in the branches array, based upon their position in the array and the value of side in the corresponding branchPositions array. Add the following highlighted code and try to understand it first before we go through it in detail:
// Update the score text
std::stringstream ss;
ss << "Score: " << score;
scoreText.setString(ss.str());
// update the branch sprites
for (int i = 0; i < NUM_BRANCHES; i++)
{
float height = i * 150;
if (branchPositions[i] == side::LEFT)
{
// Move the sprite to the left side
branches[i].setPosition(610, height);
// Flip the sprite round the other way
branches[i].setRotation(180);
}
else if (branchPositions[i] == side::RIGHT)
{
// Move the sprite to the right side
branches[i].setPosition(1330, height);
// Set the sprite rotation to normal
branches[i].setRotation(0);
}
else
{
// Hide the branch
branches[i].setPosition(3000, height);
}
}
} // End if(!paused)
/*
****************************************
Draw the scene
****************************************
The code we just added is one big for loop that sets i to zero and increments i by one each time through the loop and keeps going until i is no longer less than 6.
Inside the for loop, a new float variable called height is set to i * 150. This means that the first branch will have a height of 0, the second a height of 150, and the sixth a height of 750.
Next, we have a structure of if and else blocks. Take a look at the structure with the code stripped out:
if()
{
}
else if()
{
}
else
{
}
The first if statement uses the branchPositions array to see whether the current branch should be on the left. If it is, it sets the corresponding Sprite from the branches array to a position on the screen, appropriate for the left (610 pixels) and whatever the current height is. It then flips the Sprite by 180 degrees because the branch.png graphic "hangs" to the right by default.
Note that else if only executes if the branch is not on the left. It uses the same method to see if it is on the right. If it is, then the branch is drawn on the right (1,330 pixels). Then, the sprite rotation is set to zero degrees, just in case it had previously been at 180 degrees. If the x coordinate seems a little bit strange, just remember that we set the origin for the branch sprites to their center.
The final else statement correctly assumes that the current branchPosition must be NONE and hides the branch off-screen at 3,000 pixels.
At this point, our branches are in position and ready to be drawn.
Drawing the branches
Here, we will use another for loop to step through the entire branches array from 0 to 5 and draw each branch sprite. Add the following highlighted code:
// Draw the clouds
window.draw(spriteCloud1);
window.draw(spriteCloud2);
window.draw(spriteCloud3);
// Draw the branches
for (int i = 0; i < NUM_BRANCHES; i++) {
window.draw(branches[i]);
}
// Draw the tree
window.draw(spriteTree);
Of course, we still haven't written the function that moves all the branches. Once we have written that function, we will also need to work out when and how to call it. Let's solve the first problem and write the function.
Moving the branches
We have already added the function prototype, above the main function. Now, we can code the actual definition of the function that will move all the branches down by one position each time it is called. We will code this function in two parts so that we can easily examine what is happening.
Add the first part of the updateBranches function after the closing curly brace of the main function:
// Function definition
void updateBranches(int seed)
{
// Move all the branches down one place
for (int j = NUM_BRANCHES-1; j > 0; j--) {
branchPositions[j] = branchPositions[j - 1];
}
}
In this first part of the function, we simply move all the branches down one position, one at a time, starting with the sixth branch. This is achieved by making the for loop count from 5 through to 0. Note that branchPositions[j] = branchPositions[j - 1]; makes the actual move.
The other thing to note with this previous code is that after we have moved the branch in position 4 to position 5, then the branch in position 3 to position 4, and so on, we will need to add a new branch at position 0, which is the top of the tree.
Now, we can spawn a new branch at the top of the tree. Add the following highlighted code, and then we will talk about it:
// Function definition
void updateBranches(int seed)
{
// Move all the branches down one place
for (int j = NUM_BRANCHES-1; j > 0; j--) {
branchPositions[j] = branchPositions[j - 1];
}
// Spawn a new branch at position 0
// LEFT, RIGHT or NONE
srand((int)time(0)+seed);
int r = (rand() % 5);
switch (r) {
case 0:
branchPositions[0] = side::LEFT;
break;
case 1:
branchPositions[0] = side::RIGHT;
break;
default:
branchPositions[0] = side::NONE;
break;
}
}
In the final part of the updateBranches function, we use the integer seed variable that gets passed in with the function call. We do this to guarantee that the random number seed is always different. We will see how we arrived at this value in the next chapter.
Next, we generate a random number between zero and four and store the result in the int variable called r. Now, we switch, using r as the expression.
The case statements mean that if r is equal to zero, then we add a new branch to the left-hand side, at the top of the tree. If r is equal to 1, then the branch goes to the right. If r is anything else, (2, 3, or 4), then default ensures no branch will be added at the top. This balance of left, right, and none makes the tree seem realistic and the game work quite well. You could easily change the code to make the branches more frequent or less so.
Even after all this code for our branches, we still can't see a single one of them in the game. This is because we have more work to do before we can call the updateBranches function.
If you want to see a branch now, you can add some temporary code and call the function five times with a unique seed each time, just before the game loop:
updateBranches(1);
updateBranches(2);
updateBranches(3);
updateBranches(4);
updateBranches(5);
while (window.isOpen())
{
You can now see the branches in place. But if the branches are to actually move, we will need to call updateBranches on a regular basis:
Tip
Don't forget to remove the temporary code before moving on.
Now, we can turn our attention to the player as well, as calling the updateBranches function for real. We will do so in the next chapter.