Sol's Graphics for Beginners
34 - Stages
In order to increase the game's replayability, as well as to increase some variety into gameplay, we'll introduce stages to the gameplay.
In practise this means that instead of having a linear progression of a thousand levels, we'll split the game levels into groups of, say, five levels, and let the player choose which set to play.
Additionally we can lock several of these sets, until certain score is achieved in some previous sets, giving the player a higher-level objective in playing the game.
In practise this means that we'll have to introduce a configuration file which tells the game which levels are part of which stage, as well as any metadata, such as stage names, pictures and such.
In order to be unlockable, we also need a save game mechanism to keep track of the player's progress across several game plays. This is desirable also because we already have some configuration options, namely the audio volume, which we'd like to store across game runs.
We'll need some new global variables, as well as a new structure. Add the following structure to gp.h:
// Stage structure
struct Stage
{
char *mLevels[5];
char *mName;
int mUnlockLimit;
int mHiscore;
};
And the following new variables..
// Number of stages available
extern int gStages;
// Array of stages
extern Stage *gStage;
// Pointer to the current stage
extern Stage *gCurrentStage;
Also remember to add the variables to main.cpp:
// Number of stages available
int gStages;
// Array of stages
Stage *gStage;
// Pointer to the current stage
Stage *gCurrentStage;
As usual, we'll keep the configuration file simple, like this:
Training grounds;0;level1.txt;level2.txt;level3.txt;level4.txt;level5.txt
Alphabets;0;levela.txt;levelb.txt;levelc.txt;leveld.txt;levele.txt
Slalom;1000;simple.txt;twister.txt;diagonal.txt;curvy.txt;wider.txt
Labyrinths;5000;a.txt;b.txt;c.txt;d.txt;e.txt;f.txt
In order to parse this, we'll first need a helper function (main.cpp, above loadresources()):
void loadtoken(char *aBuf, FILE *f, int eol)
{
int j = 0;
int c = 0;
while (c != ';' && c != eol && !feof(f))
{
c = fgetc(f);
if (c == eol || c > 31)
{
aBuf[j] = c;
j++;
}
}
aBuf[j-1] = 0;
}
This function loads a character string from the file until a newline or semicolon is found, and stores the string into the buffer given as a parameter. Note that the semicolon and eol characters are written to the buffer and then overwritten by the trailing zero. The code that uses this function (at the end of loadresources()) is as follows:
FILE *f = fopen("stages.txt", "r");
// Pass 1: find out the end-of-line character
int eol = 0;
while (eol != '\n' && eol != '\r')
{
eol = fgetc(f);
}
gStages = 1;
while (!feof(f))
{
if (fgetc(f) == eol)
gStages++;
}
fseek(f, 0, SEEK_SET);
gStage = new Stage[gStages];
// Pass 2: load the information
for (i = 0; i < gStages; i++)
{
char temp[256];
loadtoken(temp, f, eol);
gStage[i].mName = strdup(temp);
loadtoken(temp, f, eol);
gStage[i].mUnlockLimit = atoi(temp);
int j;
for (j = 0; j < 5; j++)
{
loadtoken(temp, f, eol);
gStage[i].mLevel[j] = strdup(temp);
}
gStage[i].mHiscore = 0;
}
fclose(f);
gCurrentStage = gStage;
Now that we've loaded the data, we'll need to put it to use. In game.cpp, function reset(), there's a do..while loop in which we're creating a new level#.txt filename, and opening that file. Ditch that and replace the whole while loop with:
char name[80];
f = fopen(gCurrentStage->mLevel[gCurrentLevel], "rb");
if (f == NULL)
{
exit(0);
}
The name-array must still be there since we're abusing it in the level loader. Next bit to change is what happens when we've solved the fifth level of the stage. This happens in main.cpp, main() function.
In the endlevel state, we're waiting for return or space, and when either is pressed, we're moving to STATE_ENTRY. Let's change this a bit:
case SDLK_RETURN:
case SDLK_SPACE:
if (gGameState == STATE_SPLASH)
{
gNextState = STATE_MAINMENU;
}
if (gGameState == STATE_ENDLEVEL)
{
if (gCurrentLevel == 4)
{
gNextState = STATE_ENDSTAGE;
}
else
{
gNextState = STATE_ENTRY;
}
}
break;
Next we'll need to create the stage selection menu. This follows the same pattern as the main and credits menus; first change changestate() STATE_STAGESELECT blocks to imgui_open and close..
Entering a state:
case STATE_STAGESELECT:
imgui_open();
break;
and leaving a state:
case STATE_STAGESELECT:
imgui_close();
break;
Then a new case in render():
case STATE_STAGESELECT:
renderstageselect();
break;
And finally, a new function to render the stage select:
void renderstageselect()
{
// Lock surface if needed
if (SDL_MUSTLOCK(gScreen))
if (SDL_LockSurface(gScreen) < 0)
return;
// Render the menu background effect
menubgeffect();
// Set up IMGUI block
imgui_begin();
int i;
int total = 0;
for (i = 0; i < gStages; i++)
total += gStage[i].mHiscore;
for (i = 0; i < gStages; i++)
{
int ypos = (HEIGHT / 2) - (gStages - i) * 20 + 40;
if (imgui_button(GEN_ID + 1000 * i, WIDTH / 2, ypos, 90, 20, gStage[i].mName))
{
if (gStage[i].mUnlockLimit <= total)
{
gCurrentStage = &gStage[i];
gCurrentLevel = 0;
gNextState = STATE_ENTRY;
}
}
if (gStage[i].mUnlockLimit > total)
{
drawstring(WIDTH / 2 - 90, ypos, "LOCKED");
}
else
{
char temp[80];
sprintf(temp, "%6d", gStage[i].mHiscore);
drawstring(WIDTH / 2 - 80, ypos, temp);
}
}
if (imgui_button(GEN_ID, (WIDTH - 100) / 2,(HEIGHT / 2) + 60, 100, 20, "Return to main"))
{
gNextState = STATE_MAINMENU;
}
imgui_end();
// Unlock if needed
if (SDL_MUSTLOCK(gScreen))
SDL_UnlockSurface(gScreen);
// Tell SDL to update the whole gScreen
SDL_UpdateRect(gScreen, 0, 0, WIDTH, HEIGHT);
// Don't hog all the CPU power
SDL_Delay(5);
}
There are a couple of things of note in this function. The most interesting bit is the IMGUI ID generation. Since all of the stage widgets are generated from the same line, we need to add to the id in order to make them unique.
The code in this function also shows whether a stage is locked, and enforces it by not letting player select a locked state. High scores of each stage are also shown.
We'll finally fill out the last of the new states in 35. Lives, Time and Game Over..
Having problems? Improvement ideas? Just want to discuss this tutorial? Try the forums!
Any comments etc. can be emailed to me.