Introduction to C++ with Game Development: Part 10, Arrays

In this tutorial, we're going to look at one of the most common and easy ways to keep track of "things" in C++. Arrays are a simple way of keeping data in order so that it can be accessed and changed using a simple variable called an index.

Getting the stuff you need

Yes, once again we'll use the faithful old template. Extract the package to a fresh directory (say, c:\my_projects\fArrays) and load up the .sln file. Remove the 'hello world' stuff in the Tick function. 

Debug Mode

Since this tutorial needs a little more code than usual, it's safer to be in debug mode in case we want to track down any strange bugs, so to recap from lesson 7 put your IDE in debug mode. Let's hope we don't need to use it.

Debug mode

Sprites, Sprites, Sprites and Sprites

So far we've been quite limited in our ability to use sprites. If we want just one sprite it's not a problem. Something like this would work:

     Sprite theSprite( new Surface("assets/ctankbase.tga"), 16 );

If we have only one sprite this is fine, but what if we want two?

We could just give them different names like this

     Sprite Sprite1( new Surface("assets/ctankbase.tga"), 16 );
    Sprite Sprite2( new Surface("assets/ctankbase.tga"), 16 );

Now if we want to draw them we need to have a line of code to draw Sprite1 and a line to draw Sprite2. This is quite possible, but it does mean we're doubling the code like this

     Sprite1.Draw( 0, 0, m_Screen );
    Sprite2.Draw( 0, 0, m_Screen );

Well for one or two sprites this is not a big problem, but what if we have 20, 30, 300?

You can imagine that having to write 300 lines of code that are basically the same is a bit of a waste, not to mention the problem of making sure they have different screen coordinates. We need a simple system to let us draw 300 (or whatever) sprites in a simple way. And that's where arrays come in.

An array is a special type of list, where we can call our list something meaningful and give each entry in our list a number. Have a look at this line of code, which needs to go into your global space somewhere above your Init routine in game.cpp.

     Sprite* MySprites[300];

This basically replaces this line you used in past tutorials:

     Sprite Sprite1( new Surface("assets/ctankbase.tga"), 16 );

The [ ] (square) brackets identify this as an array. We've made an array called MySprites which has 300 entries. The Sprites* tells us that each of these entries is a pointer to a Sprite object. Now we have a way to tidy our Tick routine, remember our for loops from tutorial 4? Enter this code:

// Please note the slight change in the way the draw routine works now ..
// Because our array is full of pointers we are going to use the -> operator,
// not the . (dot) operator

void Game::Tick( float a_DT )
{
	// render a single frame here
	m_Screen->Clear( 0 );
	for (int index = 0; index < 300; index++)
		MySprites[index]->Draw( 0, 0, m_Screen );
}

This code won't quite work yet...but can you see what we've done? We now have a simple loop doing 300 values. Much better than typing the same line 300 times! Much more efficient and less prone to typing errors. Now before you continue reading, can you tell why it won't work yet?

...Well for a start we have an array of pointers, but when we typed:

     Sprite* MySprites[300];

all we did was tell the compiler we were going to have 300 addresses to Sprite type objects. At the moment these addresses are uninitialised and probably contain 0 or perhaps just rubbish numbers. We need to fill them with proper addresses.

In the same way we initialised our single Sprite we now have to initialse our 300 MySprites. But as we've seen, we can do this with a simple for loop:

for (int index = 0; index <300; index++)
    MySprites[index] = new Sprite( new Surface("assets/ctankbase.tga"), 16 );

The format is a little different from the Sprites you made before but if you look carefully its not so different. Compare:

     Sprite Sprite1( new Surface("assets/ctankbase.tga"), 16 );

What is different is the extra "new" command: this is a way of asking your program to give you a pointer to a "new" Sprite you are making which is also using a "new" Surface. When the program executed it made this "new" automatically so you did not have to worry about it, but now you have to do this yourself. This will make more sense when we do "classes" for real but for now its enough to know that we are now able to fill our array with 300 pointers to Sprite objects.

There is one more important difference that stops our code working. When we made our single Sprite we did not include it in the init function, it was a global variable and was initialised when the program started. This time when our program started all that happened is that we created 300 spaces to fill with pointers. Where do you think we should do that? Take a moment to ponder this before continuing...

If you said the Init routine, you were right. The correct place to initialse things is usually in our Init routine so go ahead and add those 2 lines to your init code.

void Game::Init()
{
    // put your initialization code here; will be executed once
    // create 300 new Sprite pointers and store them in MySprite
    for (int index = 0; index < 300; index++)
        MySprites[index] = new Sprite( new Surface("assets/ctankbase.tga"), 16 );
}

Remember to make sure you added your for loop and altered the draw in your tick routine, and run your program to see what you get. Debug if you need to.

Now... Don't Panic: you do really have 300 tanks on screen. And it was a little slow to fire up (can you guess why?). But did you notice your draw routine draws every one of them at 0,0 ?

So they are all drawn on the same location, on top of each other...Of course we know we can use variables to store our x and y cords: we can use arrays to store these variables too.

Remember at the start I said that arrays were a simple way to store "things", that's because Arrays are very flexible, and can store all kinds of different Data types. But they can only store one type of thing per array. Here we have been working with quite a complex type; a pointer to a sprite object Sprite*. But we can also use Arrays for simple data types you are familiar with, like int and float. Lets make some Arrays for screen coords. Put these values in global space under your MySprites[300] :

    int Xcoords[300];
    int Ycoords[300];

Now in exactly the same way as the Sprite* had no value when the program ran, these arrays are also most likely empty or have rubbish data in them. So we have to fill them with values that stop each tank being drawn over the one before it.

If we do this, it means that Xcoords[3] and Ycoords[3] can be used with MySprites[3]. And instead of using a hard number 3, we can use an index variable.

Assignment

  1. Using for loops, initialize your Xcoords and Ycoords to have incremental values in them (for example index*10), then alter your draw routine in the tick function to use your Xcoods and Ycoords the same way you used variables to move your tank around in past tutorials. Be careful not to initialiase with a value that's too large.
  2. Add a small section of code before your draw routine, to move your tanks around the screen the same way you did with a single tank. This time use a for loop and change the variable references to your X and Y array to an Array index. Also try and use an array of floats for smooth movement.

NOTE If your computer is a little slow it may be that 300 tanks is too many, reduce the number to 30. A good way to do this in a flexible manner is using a #define command at the very start of your code in game.cpp:

#define NUMBER_OF_TANKS 30

Now what this does is set an internal variable called NUMBER_OF_TANKS and make it 30. Now, change all those references to 300 in your code to NUMBER_OF_TANKS. After you did this, you will only ever have to make one change to the value defined by NUMBER_OF_TANKS everywhere in your program. So if you want 20 tanks, simply change the 30 to 20. This is very useful, and stops you from having to change the 5 entries (originally 300) each time you want to alter the number of tanks. Try it.

Oh...

The reason your code was a little slow to fire up is that it was loading 300 images before it got to draw anything, even on a very fast computer this takes a little bit of time to do as disk access is very slow. Something to remember in future but for now its not too important.


Comments

Commenting will be coming soon. In the meantime, feel free to create a discussion topic on the forums.