Creating a randomly generated scrolling background

We will create the function that makes a background in a separate file. We will ensure the background will be available (in scope) to the main function by using a vertex array reference.

As we will be writing other functions that share data with the main function, we will write them all in their own .cpp files. We will provide prototypes for these functions in a new header file that we will include (with an include directive) in ZombieArena.cpp.

To achieve this, let's first make the new header file. Right-click Header Files in the Solution Explorer and select Add | New Item.... In the Add New Item window, highlight (by left-clicking) Header File (.h), and then in the Name field type ZombieArena.h. Finally click the Add button. We are now ready to code the header file for our new function.

In this new ZombieArena.h header file, add the following highlighted code including the function prototype:

#pragma once 
using namespace sf;
int createBackground(VertexArray& rVA, IntRect arena);

The previous code enables us to write the definition of a function called createBackground. To match the prototype, the function must return an int value and receive as parameters a VertexArray reference and an IntRect object.

Now we can create a new .cpp file in which we will code the function definition. Right-click Source Files in the Solution Explorer and select Add | New Item.... In the Add New Item window, highlight (by left-clicking) C++ File ( .cpp ), and then in the Name field type CreateBackground.cpp. Finally click the Add button. We are now ready to code the function definition that will create our background.

Add the following code to the CreateBackground.cpp file and then we will review it:

#include "stdafx.h" 
#include "ZombieArena.h" 
 
int createBackground(VertexArray& rVA, IntRect arena) 
{ 
   // Anything we do to rVA we are actually doing 
   // to background (in the main function) 
    
   // How big is each tile/texture 
   const int TILE_SIZE = 50; 
   const int TILE_TYPES = 3; 
   const int VERTS_IN_QUAD = 4; 
 
   int worldWidth = arena.width / TILE_SIZE; 
   int worldHeight = arena.height / TILE_SIZE; 
 
   // What type of primitive are we using? 
   rVA.setPrimitiveType(Quads); 
 
   // Set the size of the vertex array 
   rVA.resize(worldWidth * worldHeight * VERTS_IN_QUAD); 
 
   // Start at the beginning of the vertex array 
   int currentVertex = 0; 
 
   return TILE_SIZE; 
} 

In the previous code, we write the function signature as well as the opening and closing curly brackets that mark out the function body.

Within the function body we declare and initialize three new int constants to hold values that we will need to refer to throughout the rest of the function. They are TILE_SIZE, TILE_TYPES, and VERTS_IN_QUAD. The TILE_SIZE constant refers to the size in pixels of each tile within the sprite sheet. TILE_TYPES refers to the number of different tiles within the sprite sheet. We could add more tiles into our sprite sheet, change TILE_TYPES to match, and the code we are about to write would still work. VERTS_IN_QUAD refers to the fact that there are four vertices in every quad. It is less error-prone to use this constant compared to repeatedly typing the number 4, which is less clear.

We then declare and initialize two int variables, worldWidth and worldHeight. These variables might appear blindingly obvious as to their use. They are betrayed by their names but it is worth pointing out that they refer to the width and height of the world in number of tiles, not pixels. The worldWidth and worldHeight variables are initialized by dividing the height and width of the passed-in arena by the constant, TILE_SIZE.

Next, we get to use our reference for the first time. Remember that anything we do to rVA we are really doing to the variable that was passed-in which is in scope in the main function (or will be when we code it).

First we prepare the vertex array to use quads using rVA.setType, and then we make it just the right size by calling rVA.resize. To the resize function we pass in the result of worldWidth * worldHeight * VERTS_IN_QUAD, which equates to exactly the number of vertices that our vertex array will have when we are done preparing it.

The last line of code declares and initializes currentVertex to zero. We will use currentVertex as we loop through the vertex array initializing all the vertices.

We can now write the first part of a nested for loop that will prepare the vertex array. Add the following highlighted code, and based on what we have learnt about vertex arrays, try and work out what it does:

   // Start at the beginning of the vertex array 
   int currentVertex = 0; 
 
   for (int w = 0; w < worldWidth; w++)
   {
     for (int h = 0; h < worldHeight; h++)
     {
        // Position each vertex in the current quad
        rVA[currentVertex + 0].position =
            Vector2f(w * TILE_SIZE, h * TILE_SIZE);

        rVA[currentVertex + 1].position =
           Vector2f((w * TILE_SIZE) + TILE_SIZE, h * TILE_SIZE);

        rVA[currentVertex + 2].position =
           Vector2f((w * TILE_SIZE) + TILE_SIZE, (h * TILE_SIZE)
            + TILE_SIZE);

        rVA[currentVertex + 3].position =
           Vector2f((w * TILE_SIZE), (h * TILE_SIZE)
            + TILE_SIZE);

        // Position ready for the next for vertices
        currentVertex = currentVertex + VERTS_IN_QUAD;
     }
   } 
 
   return TILE_SIZE; 
} 

The code that we just added steps through the vertex array by using a nested for loop that first steps through the first four vertices. currentVertex + 1, currentVertex + 2, and so on.

We access each vertex in the array using array notation. rvA[currentVertex + 0].. and so on. Using array notation we call the position function rvA[currentVertex + 0].position....

Into the position function we pass the horizontal and vertical coordinates of each vertex. We can work these coordinates out programmatically by using a combination of w, h, and TILE_SIZE.

At the end of the previous code we position currentVertex ready for the next pass through the nested for loop by advancing it four places (adding four) with the code currentVertex = currentVertex + VERTS_IN_QUAD.

Of course, all this does is set the coordinates of our vertices; it doesn't assign a texture coordinate from the sprite sheet. This is what we will do next.

To make it absolutely clear where the new code goes I have shown it in context with all the code that we wrote a moment ago. Add and study the highlighted code:

for (int w = 0; w < worldWidth; w++) 
   { 
      for (int h = 0; h < worldHeight; h++) 
      { 
         // Position each vertex in the current quad 
         rVA[currentVertex + 0].position =  
            Vector2f(w * TILE_SIZE, h * TILE_SIZE); 
             
         rVA[currentVertex + 1].position = 
            Vector2f((w * TILE_SIZE) + TILE_SIZE, h * TILE_SIZE); 
             
         rVA[currentVertex + 2].position = 
            Vector2f((w * TILE_SIZE) + TILE_SIZE, (h * TILE_SIZE)  
            + TILE_SIZE); 
             
         rVA[currentVertex + 3].position = 
            Vector2f((w * TILE_SIZE), (h * TILE_SIZE)  
            + TILE_SIZE); 

        // Define the position in the Texture for current quad
        // Either grass, stone, bush or wall
        if (h == 0 || h == worldHeight-1 ||
            w == 0 || w == worldWidth-1)
        {
           // Use the wall texture
           rVA[currentVertex + 0].texCoords =
              Vector2f(0, 0 + TILE_TYPES * TILE_SIZE);
           rVA[currentVertex + 1].texCoords =
              Vector2f(TILE_SIZE, 0 +
              TILE_TYPES * TILE_SIZE);
           rVA[currentVertex + 2].texCoords =
              Vector2f(TILE_SIZE, TILE_SIZE +
              TILE_TYPES * TILE_SIZE);
           rVA[currentVertex + 3].texCoords =
              Vector2f(0, TILE_SIZE +
              TILE_TYPES * TILE_SIZE);
        } 
          
 
         // Position ready for the next for vertices 
         currentVertex = currentVertex + VERTS_IN_QUAD; 
      } 
   } 
 
   return TILE_SIZE; 
} 

The previous code sets up the coordinates within the sprite sheet that each vertex is related to. Notice the somewhat long if condition. The condition checks whether the current quad is either one of the very first or the very last quads in the arena. If it is then this means it is part of the boundary. We can then use a simple formula using TILE_SIZE and TILE_TYPES to target the wall texture from the sprite sheet.

Array notation and the texCoords member are initialized for each vertex in turn to assign the appropriate corner of the wall texture within the sprite sheet.

The following code is wrapped in an else block. This means that it will run each time through the nested for loop when the quad does not represent a border/wall tile. Add the highlighted code amongst the existing code and we can then examine it:

         // Define position in Texture for current quad 
         // Either grass, stone, bush or wall 
         if (h == 0 || h == worldHeight-1 || 
            w == 0 || w == worldWidth-1) 
         { 
            // Use the wall texture 
            rVA[currentVertex + 0].texCoords =  
               Vector2f(0, 0 + TILE_TYPES * TILE_SIZE); 
                
            rVA[currentVertex + 1].texCoords =  
               Vector2f(TILE_SIZE, 0 +  
               TILE_TYPES * TILE_SIZE); 
                
            rVA[currentVertex + 2].texCoords =  
               Vector2f(TILE_SIZE, TILE_SIZE +  
               TILE_TYPES * TILE_SIZE); 
                
            rVA[currentVertex + 3].texCoords =  
               Vector2f(0, TILE_SIZE +  
               TILE_TYPES * TILE_SIZE); 
         } 
        else
        {
           // Use a random floor texture
           srand((int)time(0) + h * w - h);
           int mOrG = (rand() % TILE_TYPES);
           int verticalOffset = mOrG * TILE_SIZE;

           rVA[currentVertex + 0].texCoords =
              Vector2f(0, 0 + verticalOffset);

           rVA[currentVertex + 1].texCoords =
              Vector2f(TILE_SIZE, 0 + verticalOffset);

           rVA[currentVertex + 2].texCoords =
              Vector2f(TILE_SIZE, TILE_SIZE + verticalOffset);

           rVA[currentVertex + 3].texCoords =
              Vector2f(0, TILE_SIZE + verticalOffset);
        }        
 
         // Position ready for the next for vertices 
         currentVertex = currentVertex + VERTS_IN_QUAD; 
      } 
   } 
 
   return TILE_SIZE; 
} 

The previous new code starts by seeding the random number generator with a formula that will be different each pass through the loop. Then the mOrG variable is initialized with a number between 0 and TILE_TYPES. This is just what we need to pick one of the tile types randomly.

Note

mOrG stands for mud or grass. The name is arbitrary.

Now we declare and initialize a variable called verticalOffset by multiplying mOrG by TileSize. We now have a vertical reference point within the sprite sheet to the starting height of the randomly chosen texture for the current quad.

Now we use a simple formula involving TILE_SIZE and verticalOffset to assign the precise coordinates of each corner of the texture to the appropriate vertex.

Now we can put our new function to work in the game engine.

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

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