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.
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.
3.145.58.246