Beginning C++ Game Programming
上QQ阅读APP看书,第一时间看更新

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.