Next, as I have been promising for around the last seventeen pages, we will use all the new C++ techniques to draw and move some branches on our tree.
Add this code outside the main
function. Just to be absolutely clear, I mean before the code int main()
:
#include "stdafx.h"
#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:
updateBranches
. We can see that it does not return a value (void
) and it takes an int
argument called seed
. We will write the function definition soon and we will then see exactly what it does.int
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
will be useful to us.Sprite
objects called branches
that can hold six Sprites.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.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 type side
, which can be either, LEFT
, RIGHT
, or NONE
.Of course, what you really want to know is why the constant, the two arrays, and the enumeration were declared outside the main
function. By declaring them above main
they now have global scope. Or, describing it another way, the constant, the two arrays, and the enumeration have scope for the entire game. This will mean we can access and use them all anywhere in the main
function and in the updateBranches
function. Note that it is good practice to make all 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.
Now we will prepare our six Sprite
objects and load them into the branches
array. Add the 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 center
// We can then spin it round without changing its position
branches[i].setOrigin(220, 20);
}
while (window.isOpen())
{
The previous code does not use any new concepts. First we declare an SFML Texture
object and load the branch.png
graphic into it.
Next, we create a for
loop, which sets i
to zero and increments i
by one 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 which is located when it is drawn), with setOrigin
, to the center of the sprite. Soon, we will be rotating these sprites and having the origin in the center means they will spin nicely around, without moving the sprite out of position.
In this next code we 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 highlighted code and try to understand it, then we can 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, increments i
by 1 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 of 150, and the sixth of 750.
Next we have a structure of if
and else
blocks. Look at the structure with the code stripped out:
if() { } else if() { } else { }
The first if
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.
The 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 (1330
pixels). Then the sprite rotation is set to 0 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
assumes, correctly, that the current branchPosition
must be NONE
and hides the branch off screen at 3000
pixels.
At this point, our branches are in position, ready to be drawn.
Here we 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 actually 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.
We have already added the function prototype above the main
function. Now we 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 we can more 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. The code branchPositions[j] = branchPositions[j - 1];
makes the actual move.
The other thing to note, with the 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 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 and we will see how this value is arrived at in the next chapter.
Next, we generate a random number between zero and four and store the result in the int
variable 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 on the left-hand side at the top of the tree. If r
is equal to 1 then the branch goes on the right. If r
is anything else, (2, 3, or 4) then the default
ensures that no branch will be added at the top. This balance of left, right, and none makes the tree seem realistic and the game works 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 glimpse a single one of them in the game. This is because we have more work to do before we can actually call updateBranches
.
If you really want 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 their place. But if the branches are to actually move we will need to call updateBranches
on a regular basis.
Now we can turn our attention to the player and call the updateBranches
function for real.
18.220.178.243