Chapter 4: Loops, Arrays, Switches, Enumerations, and Functions – Implementing Game Mechanics

This chapter probably has more C++ information in it than any other chapter in this book. It is packed with fundamental concepts that will move our understanding on enormously. It will also begin to shed light on some of the murky areas we have been skipping over a little bit, such as functions and the game loop.

Once we have explored a whole list of C++ language necessities, we will then use everything we know to make the main game mechanic—the tree branches—move. By the end of this chapter, we will be ready for the final phase and the completion of Timber!!!.

In this chapter, we will cover the following topics:

  • Loops
  • Arrays
  • Making decisions with switch
  • Enumerations
  • Getting started with functions
  • Creating and moving the tree branches

Loops

In programming, we often need to do the same thing more than once. The obvious example that we have seen so far is the game loop. With all the code stripped out, our game loop looks like this:

while (window.isOpen())

{

}

There are a few different types of loops, and we will look at the most commonly used ones here. The correct term for this type of loop is a while loop.

while loops

The while loop is quite straightforward. Think back to the if statements and their expressions that evaluated to either true or false. We can use the exact same combination of operators and variables in the conditional expressions of our while loops.

Like if statements, if the expression is true, the code executes. The difference with a while loop, however, is that the C++ code within it will repeatedly execute until the condition is false. Take a look at the following code.

int numberOfZombies = 100;

while(numberOfZombies > 0)

{

    // Player kills a zombie

    numberOfZombies--;

    // numberOfZombies decreases each pass through the loop

}

// numberOfZombies is no longer greater than 0

Let's go over what's happening in the previous code. Outside of the while loop, int numberOfZombies is declared and initialized to 100. Then, the while loop begins. Its conditional expression is numberOfZombies > 0. Consequently, the while loop will continue looping through the code in its body until the condition evaluates to false. This means that the preceding code will execute 100 times.

On the first pass through the loop, numberOfZombies equals 100, then 99, then 98, and so on. But once numberOfZombies is equal to zero, it is, of course, no longer greater than zero. Then, the code will break out of the while loop and continue to run, after the closing curly brace.

Just like an if statement, it is possible that the while loop will not execute even once. Take a look at the following code:

int availableCoins = 10;

while(availableCoins > 10)

{

    // more code here.

    // Won't run unless availableCoins is greater than 10

}

The preceding code inside the while loop will not execute because the condition is false.

Note that there is no limit to the complexity of the expression or the amount of code that can go in the loop body. Consider the following hypothetical variation of our game loop:

int playerLives = 3;

int alienShips = 10;

while(playerLives !=0 && alienShips !=0 )

{

    // Handle input

    // Update the scene

    // Draw the scene

}

// continue here when either playerLives or alienShips equals 0

The previous while loop would continue to execute until either playerLives or alienShips was equal to zero. As soon as one of those conditions occurred, the expression would evaluate to false and the program would continue to execute from the first line of code after the while loop.

It is worth noting that once the body of the loop has been entered, it will always complete at least once, even if the expression evaluates to false partway through, as it is not tested again until the code tries to start another pass. Let's take a look at an example of this:

int x = 1;

while(x > 0)

{

    x--;

    // x is now 0 so the condition is false

    // But this line still runs

    // and this one

    // and me!

}

// Now I'm done!

The previous loop body will execute once. We can also set up a while loop that will run forever, and unsurprisingly is called an infinite loop. Here is an example:

int y = 0;

while(true)

{

    y++; // Bigger... Bigger...

}

If you find the preceding loop confusing, just think of it literally. A loop executes when its condition is true. Well, true is always true, and will therefore keep executing.

Breaking out of a while loop

We might use an infinite loop so that we can decide when to exit the loop from within its body rather than in the expression. We would do this by using the break keyword when we are ready to leave the loop body, perhaps like this:

int z = 0;

while(true)

{

    z++; // Bigger... Bigger...

    break; // No you're not

    

    // Code doesn't reach here

}

In the preceding code, the code inside the loop will execute once, upto and including the break statement, and then execution will continue after the closing curly brace of the while loop.

As you may have been able to guess, we can combine any of the C++ decision-making tools such as if, else, and another that we will learn about shortly, known as switch, within our while loops and other loop types as well. Consider the following example:

int x = 0;

int max = 10;

while(true)

{

    x++; // Bigger... Bigger...

    if(x == max){

        break;

    } // No you're not

    // code reaches here only until max = 10

}

In the preceding code, the if condition decides if and when the break statement is executed. In this case, the code will keep looping until max reaches 10.

We could go on for a long time looking at the various permutations of C++ while loops, but, at some point, we want to get back to making games. So, let's move on to another type of loop: the for loop.

for loops

The for loop has a slightly more complicated syntax than the while loop because it takes three parts to set one up. Take a look at the following code first. We will break it apart after:

for(int x = 0; x < 100; x ++)

{

    // Something that needs to happen 100 times goes here

}

Here is what all the parts of the for loop condition do:

for(declaration and initialization; condition; change before each iteration)

To clarify this further, here is a table to explain each of the three key parts, as they appear in the previous for loop example:

We can vary for loops so that they do many more things. Here is another simple example that counts down from 10:

for(int i = 10; i > 0; i--)

{

    // countdown

}

// blast off

The for loop takes control of initialization, condition evaluation, and the control variable. We will use for loops in our game, later in this chapter.

Now, we can move on to the topic of C++ arrays, which help us store large amounts of related data.

Arrays

If a variable is a box in which we can store a value of a specific type, such as int, float, or char, then we can think of an array as a row of boxes. The rows of boxes can be of almost any size and type, including objects made from classes. However, all the boxes must be of the same type.

Tip

The limitation of having to use the same type in each box can be circumvented to an extent once we learn some more advanced C++ in the penultimate project.

This array sounds like it could have been useful for our clouds in Chapter 2, Variables, Operators, and Decisions – Animating Sprites. So, how do we go about creating and using an array?

Declaring an array

We can declare an array of int type variables like this:

int someInts[10];

Now, we have an array called someInts that can store ten int values. Currently, however, it is empty.

Initializing the elements of an array

To add values to the elements of an array, we can use the type of syntax we are already familiar with while introducing some new syntax, known as array notation. In the following code, we store the value of 99 in the first element of the array:

someInts[0] = 99;

In order to store a value of 999 in the second element, we need to use the following code:

someInts[1] = 999;

We can store a value of 3 in the last element like this:

someInts[9] = 3;

Note that the elements of an array always start at zero and go upto the size of the array minus one. Similar to ordinary variables, we can manipulate the values stored in an array. The only difference is that we would use the array notation to do so because although our array has a name— someInts— the individual elements do not.

In the following code, we add the first and second elements together and store the answer in the third:

someInts[2] = someInts[0] + someInts[1];

Arrays can also interact seamlessly with regular variables, for example:

int a = 9999;

someInts[4] = a;

There are more ways we can initialize arrays, so let's look at one way now.

Quickly initializing the elements of an array

We can quickly add values to the elements as follows. This example uses a float array:

float myFloatingPointArray[3] {3.14f, 1.63f, 99.0f};

Now, the 3.14, 1.63, and 99.0 values are stored in the first, second, and third positions, respectively. Remember that, when using an array notation to access these values, we would use [0], [1], and [2].

There are other ways to initialize the elements of an array. This slightly abstract example shows using a for loop to put the values 0 through 9 into the uselessArray array:

for(int i = 0; i < 10; i++)

{

    uselessArray[i] = i;

}

The preceding code assumes that uslessArray had previously been initialized to hold at least 10 int variables.

But why do we need arrays?

What do these arrays really do for our games?

We can use arrays anywhere a regular variable can be used – perhaps in an expression like this:

// someArray[4] is declared and initialized to 9999

for(int i = 0; i < someArray[4]; i++)

{

    // Loop executes 9999 times

}

One of the biggest benefits of arrays in game code was hinted at at the start of this section. Arrays can hold objects (instances of classes). Let's imagine that we have a Zombie class and we want to store a whole bunch of them. We can do so like this:

Zombie horde [5] {zombie1, zombie2, zombie3}; // etc...

The horde array now holds a load of instances of the Zombie class. Each one is a separate, living (kind of), breathing, self-determining Zombie object. We could then loop through the horde array, each of which passes through the game loop, move the zombies, and check if their heads have met with an axe or if they have managed to catch the player.

Arrays, had we known about them at the time, would have been perfect for handling our clouds in Chapter 2, Variables, Operators, and Decisions – Animating Sprites. We could have had as many clouds as we wanted and written less code than we did for our three measly clouds.

Tip

To check out this improved cloud code in full and in action, look at the enhanced version of Timber!!! (code and playable game) in the download bundle. Alternatively, you can try to implement the clouds using arrays yourself before looking at the code.

The best way to get a feel for all of this array stuff is to see it in action. We will do this when we implement our tree branches.

For now, we will leave our cloud code as it is so that we can get back to adding features to the game as soon as possible. But first, let's do a bit more C++ decision-making with switch.

Making decisions with switch

We have already looked at if, which allows us to decide whether to execute a block of code based upon the result of its expression. But sometimes, a decision in C++ can be made in other ways that are better.

When we must make a decision based on a clear list of possible outcomes that don't involve complex combinations or wide ranges of values, then switch is usually the way to go. We can start a switch decision as follows:

switch(expression)

{

    // More code here

}

In the previous example, expression could be an actual expression or just a variable. Then, within the curly braces, we can make decisions based on the result of the expression or value of the variable. We do this with the case and break keywords:

case x:

    //code for x

    break;

 

case y:

    //code for y

    break;

As you can see, each case states a possible result and each break denotes the end of that case and the point that the execution leaves the switch block.

Optionally, we can also use the default keyword without a value to run some code in case none of the case statements evaluate to true, as follows:

default: // Look no value

    // Do something here if no other case statements are true

    break;

As a final and less abstract example for switch, consider a retro text adventure where the player enters a letter such as "n", "e", "s", or "w" to move North, East, South, or West. A switch block could be used to handle each possible input from the player:

// get input from user in a char called command

switch(command){

    case 'n':

        // Handle move here

        break;

    case 'e':

        // Handle move here

        break;

    case 's':

        // Handle move here

        break;

    case 'w':

        // Handle move here

        break;

    

    // more possible cases

    default:

        // Ask the player to try again

        break;

}

The best way of understanding all we have seen regarding switch is by putting it into action, along with all the other new concepts we are learning about.

Next, we will learn about another C++ concept we need to understand before we write some more code. Let's look at class enumerations.

Class enumerations

An enumeration is a list of all the possible values in a logical collection. C++ enumerations are a great way of, well, enumerating things. For example, if our game uses variables that can only be in a specific range of values and if those values could logically form a collection or a set, then enumerations are probably appropriate to use. They will make your code clearer and less error-prone.

To declare a class enumeration in C++, we can use these two keywords, enum class, together, followed by the name of the enumeration, followed by the values the enumeration can contain, enclosed in a pair of curly braces {...}.

As an example, examine the following enumeration declaration. Note that it is convention to declare the possible values from the enumeration in uppercase:

enum class zombieTypes {

   REGULAR, RUNNER,

   CRAWLER, SPITTER, BLOATER

};

Note that, at this point, we have not declared any instances of zombieType, just the type itself. If that sounds odd, think about it like this. SFML created the Sprite, RectangleShape, and RenderWindow classes, but to use any of those classes, we had to declare an object/instance of the class.

At this point, we have created a new type called zombieTypes, but we have no instances of it. So, let's do that now:

zombieType jeremy = zombieTypes::CRAWLER;

zombieType anna = zombieTypes::SPITTER;

zombieType diane = zombieTypes::BLOATER;

/*

    Zombies are fictional creatures and any resemblance

    to real people is entirely coincidental

*/

Next is a sneak preview of the type of code we will soon be adding to Timber!!!. We will want to track which side of the tree a branch or the player is on, so we will declare an enumeration called side, like this:

enum class side { LEFT, RIGHT, NONE };

We could position the player on the left like this:

// The player starts on the left

side playerSide = side::LEFT;

We could make the fourth level (arrays start from zero) of an array of branch positions have no branch at all, like this:

branchPositions[3] = side::NONE;

We can use enumerations in expressions as well:

if(branchPositions[5] == playerSide)

{

    // The lowest branch is the same side as the player

    // SQUISHED!!

}

The preceding code tests whether the branch in position [5] element of the array is on the same side as the player.

We will look at one more vital C++ topic, that is, functions, and then we will get back to coding the game. When we want to compartmentalize some code that does one specific thing, we can use a function.

Getting started with functions

What exactly are C++ functions? A function is a collection of variables, expressions, and control flow statements (loops and branches). In fact, any of the code we have learned about in this book so far can be used in a function. The first part of a function that we write is called the signature. Here is an example function signature:

void shootLazers(int power, int direction);

If we add an opening and closing pair of curly braces {...} along with some code that the function performs, we will have a complete function, that is, a definition:

void shootLazers(int power, int direction)

{

    // ZAPP!

}

We could then use our new function from another part of our code, perhaps like this:

// Attack the player

shootLazers(50, 180) // Run the code in the function

// I'm back again - code continues here after the function ends

When we use a function, we say that we call it. At the point where we call shootLazers, our program's execution branches to the code contained within that function. The function will run until it reaches the end or is told to return. Then, the code will continue running from the first line after the function call. We have already been using the functions that SFML provides. What is different here is that we will learn to write and call our own functions.

Here is another example of a function, complete with the code to make the function return to the code that called it:

int addAToB(int a, int b)

{

    int answer = a + b;

    return answer;

}

The call so that we can use the preceding function may look like this:

int myAnswer = addAToB(2, 4);

Obviously, we don't need to write functions to add two variables together, but this example helps us look into the workings of functions. First, we pass in the values 2 and 4. In the function signature, the value 2 is assigned to int a, and the value 4 is assigned to int b.

Within the function body, the a and b variables are added together and used to initialize the new variable, int answer. The return answer; line does just that. It returns the value stored in answer to the calling code, causing myAnswer to be initialized with the value 6.

Notice that each of the function signatures in the preceding examples vary a little. The reason for this is that the C++ function signature is quite flexible, allowing us to build exactly the functions we require.

Exactly how the function signature defines how the function must be called and if/how the function must return a value deserves further discussion. Let's give each part of that signature a name so that we can break it into parts and learn about them.

Here is a function signature with its parts described by their formal/technical term:

return type | name of function | (parameters)

Here are a few examples that we can use for each of those parts:

  • Return-type: void,  bool, float, int, and so on, or any C++ type or expression
  • Name of function: shootLazers, addAToB, and so on
  • Parameters: (int number, bool hitDetected), (int x, int y), (float a, float b)

Now, let's look at each part in turn, starting with the return type.

Function return types

The return type, as its name suggests, is the type of the value that will be returned from the function to the calling code:

int addAToB(int a, int b){

 

    int answer = a + b;

    return answer;

 

}

In our slightly dull but useful addAtoB example that we looked at previously, the return type in the signature is int. The addAToB function sends back and returns  a value that will fit in an int variable to the code that called it. The return type can be any C++ type we have seen so far or one of the ones we haven't seen yet.

A function does not have to return a value at all, however. In this case, the signature must use the void keyword as the return type. When the void keyword is used, the function body must not attempt to return a value as this will cause an error. It can, however, use the return keyword without a value. Here are some combinations of the return type and use of the return keyword that are valid:

void doWhatever(){

 

    // our code

    // I'm done going back to calling code here

    // no return is necessary

 

}

Another possibility is as follows:

void doSomethingCool(){

 

    // our code

 

    // I can do this if I don't try and use a value

    return;

}

The following code is yet more examples of possible functions. Be sure to read the comments as well as the code:

void doYetAnotherThing(){

    // some code

 

    if(someCondition){

 

        // if someCondition is true returning to calling code

        // before the end of the function body

        return;

    }

 

    // More code that might or might not get executed

 

    return;

 

    // As I'm at the bottom of the function body

    // and the return type is void, I'm

    // really not necessary but I suppose I make it

    // clear that the function is over.

}

 

bool detectCollision(Ship a, Ship b){

 

    // Detect if collision has occurred

    if(collision)

    {

        // Bam!!!

        return true;

    }

    else

    {

        // Missed

        return false;

    }

 

}

The last function example in the preceding code, which is for detectCollision, is a glimpse into the near future of our C++ code and demonstrates that we can also pass in user-defined types known as objects into functions so that we can perform calculations on them.

We could call each of the functions, in turn, like this:

// OK time to call some functions

doWhatever();

doSomethingCool();

doYetAnotherThing();

 

if (detectCollision(milleniumFalcon, lukesXWing))

{

    // The jedi are doomed!

    // But there is always Leia.

    // Unless she was on the Falcon?

}

else

{

    // Live to fight another day

}

 

// Continue with code from here

Don't worry about the odd-looking syntax regarding the detectCollision function; we will see real code like this soon. Simply, we are using the return value (true or false) as the expression directly in an if statement.

Function names

The function name that we use when we design our own function can be almost anything at all. But it is best to use words, usually verbs, that clearly explain what the function will do. For example, take a look at the following function:

void functionaroonieboonie(int blibbityblob, float floppyfloatything)

{

    //code here

}

The preceding function is perfectly legal and will work, but the following function names are much clearer:

void doSomeVerySpecificTask()

{

    //code here

}

 

int getMySpaceShipHealth()

{

    //code here

}

 

void startNewGame()

{

    //code here

}

Using clear and descriptive function names such as in the preceding three examples is good practice, but, as we saw from the functionaroonieboonie function, this is not a rule that the compiler enforces. Next, we will take a closer look at how we share some values with a function.

Function parameters

We know that a function can return a result to the calling code. But what if we need to share some data values from the calling code with the function? Parameters allow us to share values with the function. We have already seen examples of parameters while looking at return types. We will look at the same example but a little more closely:

int addAToB(int a, int b)

{

    int answer = a + b;

    return answer;

}

Here, the parameters are int a and int b. Notice that, in the first line of the function body, we use a + b as if they are already declared and initialized variables. Well, that's because they are. The parameters in the function signature is their declaration, and the code that calls the function initializes them.

Important jargon note

Note that we are referring to the variables in the function signature brackets (int a, int b) as parameters. When we pass values into the function from the calling code, these values are called arguments. When the arguments arrive, they are used by the parameters to initialize real, usable variables, like:

int returnedAnswer = addAToB(10,5);

Also, as we have partly seen in previous examples, we don't have to just use int in our parameters. We can use any C++ type. We can also use as many parameters as is necessary to solve our problem, but it is good practice to keep the parameter list as short and therefore as manageable as possible.

As we will see in future chapters, we have left a few of the cooler uses of functions out of this introductory tutorial so that we can learn about related C++ concepts before we take the topic of functions further.

The function body

The body is the part we have been kind of avoiding and has comments such as the following:

// code here

// some code

Actually, we already know exactly what to do here! Any C++ code we have learned about so far will work in the body of a function.

Function prototypes

So far, we have seen how to code a function and we have seen how to call one as well. There is one more thing we need to do, however, to make them work. All functions must have a prototype. A prototype is what makes the compiler aware of our function, and without a prototype the entire game will fail to compile. Fortunately, prototypes are straightforward.

We can simply repeat the function's signature, followed by a semicolon. The caveat is that the prototype must appear before any attempt to call or define the function. So, the absolute most simple example of a fully usable function in action is as follows. Look carefully at the comments and the location in the code that the different parts of the function appear in:

// The prototype

// Notice the semicolon on the end

int addAToB(int a, int b);

 

int main()

{

    // Call the function

    // Store the result in answer

    int answer = addAToB(2,2);

 

    // Called before the definition

    // but that's OK because of the prototype

 

    // Exit main

    return 0;

 

}// End of main

 

// The function definition

int addAToB(int a, int b)

{

    return a + b;

}

What the previous code demonstrates is the following:

  • The prototype is before the main function.
  • The call to use the function is as we might expect, inside the main function.
  • The definition is after/outside the main function.

    Important note

    Note that we can omit the function prototype and go straight to the definition when the definition occurs before the function is used. As our code becomes longer and spread across multiple files, however, this will almost never happen. We will use separate prototypes and definitions all the time.

Let's see how we can keep our functions organized.

Organizing functions

It's well worth pointing out that if we have multiple functions, especially if they are fairly long, our .cpp file will quickly become unwieldy. This defeats part of the objective that functions are intended for. The solution that we will see in the next project is that we can add all our function prototypes to our very own header file (.hpp or .h). Then, we can code all our functions in another .cpp file and simply add another #include... directive in our main .cpp file. This way, we can use any number of functions without adding any of their code (prototype or definition) to our main code file.

Function gotcha!

Another point that we should discuss about functions is scope. If we declare a variable in a function, either directly or in one of the parameters, that variable is not usable/visible outside of that function. Furthermore, any variables declared inside other functions cannot be seen/used inside the function.

The way that we should share values between function code and calling code is through the parameters/arguments and the return value.

When a variable is not available because it is from another function, it is said to be out of scope. When it is available and usable, it is said to be in scope.

Important note

Variables declared within any block in C++ are only in scope within that block! This includes loops and if blocks as well. A variable that's declared at the top of main is in scope anywhere in main, a variable that's declared in the game loop is only in scope within the game loop, and so on. A variable that's declared within a function or other block is called a local variable. The more code we write, the more this will make sense. Every time we come across an issue in our code regarding scope, I will discuss it to make things clear. There will be one such issue coming up in the next section. There are also some more C++ staples that blow this issue wide open. They are called references and pointers, and we will learn about them in Chapter 9, C++ References, Sprite Sheets, and Vertex Arrays and Chapter 10, Pointers, the Standard Template Library, and Texture Management respectively.

More on functions

There is even more we could learn about functions, but we know enough about them already to implement the next part of our game. And don't worry if all the technical terms such as parameters, signatures, and definitions have not completely sunk in yet. These concepts will become clearer when we start to use them.

An absolute final word on functions – for now

It has probably not escaped your attention that we have been calling functions, especially the SFML functions, by appending the name of an object and a period before the function name, like this:

spriteBee.setPosition...

window.draw...

// etc

And yet, our entire discussion of functions saw us calling functions without any objects. We can write functions as part of a class or simply as a standalone function. When we write a function as part of a class, we need an object of that class to call the function, but when we have a standalone function, we don't.

We will write a standalone function in a minute and we will write classes with functions starting from Chapter 6, Object-Oriented Programming – Starting the Pong Game. Everything we know so far about functions is relevant in both cases.

Now, we can get back to coding the branches in the Timber!!! game.

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:

  1. First, we declare an SFML Texture object and load the branch.png graphic into it.
  2. 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.
  3. 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.
  4. 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.

Summary

Although not quite the longest, this was probably the chapter where we've covered the most C++ so far. We looked at the different types of loops we can use, such as for and while loops. We then studied arrays that we can use them to handle large amounts of variables and objects without breaking a sweat. We also learned about enumerations and switch. Probably the biggest concept in this chapter was functions, which allow us to organize and abstract our game's code. We will be looking more deeply at functions in a few more places in this book.

Now that we have a fully "working" tree, we can finish the game off, which we will do in the next and final chapter for this project.

FAQ

Q) You mentioned there were a few more types of C++ loops. Where can I find out about them?

A) Yes, take a look at this tutorial and explanation for the do while loops: http://www.tutorialspoint.com/cplusplus/cpp_do_while_loop.htm.

Q) Can I assume I am now an expert on arrays?

A) Like many of the topics in this book, there is always more to learn. You know enough about arrays to proceed, but if you're hungry for more, take a look at this fuller arrays tutorial: http://www.cplusplus.com/doc/tutorial/arrays/.

Q) Can I assume that I am an expert on functions?

A) Like many of the topics in this book, there is always more to learn. You know enough about functions to proceed, but if want to know even more, take a look at this tutorial: http://www.cplusplus.com/doc/tutorial/functions/.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.226.109