Continuing from this post:
http://www.psp-programming.com/dev-forum/viewtopic.php?t=386Ok, in the third and final part we'll add scoring and collision detection to our game.
First of all let's open up our file game.c from part 2 of the tutorial.
#include <pspkernel.h>
#include <pspdisplay.h>
#include <pspctrl.h>
#include <stdio.h> //ADDITION FOR SPRINTF
#include </usr/include/sys/stat.h> //ADDITION FOR TIME
#include "graphics.h"
#define RGB(r, g, b) ((r)|((g)<<8)|((b)<<16))
Add the above two marked lines, the comments above and the following code will explain why we need them.
static int GetRandomNum(int lo, int hi)
{
SceKernelUtilsMt19937Context ctx;
sceKernelUtilsMt19937Init(&ctx, time(NULL)); //SEED TO TIME
u32 rand_val = sceKernelUtilsMt19937UInt(&ctx);
rand_val = lo + rand_val % hi;
return (int)rand_val;
}
We need to generate random numbers now, for the X position of our objects that Blinky collects for points. For this we will use the Mersenne Twister, which the PSP supports natively.
We set our
GetRandomNum function as static, which basically means it can only be called by other functions within the same file (game.c).
The
GetRandomNum function will give us a number between the two values that we pass it, you will see it utilised shortly.
void Game()
{
SceCtrlData pad;
int BlinkyPosX = 170;
int BlinkTimer = 0;
int Score = 0; //ADDITION
char ScoreAsChar[3]; //ADDITION
int DeadStars = 0; //ADDITION
Color White = RGB(255, 255, 255);
Color Black = RGB(0, 0, 0); //ADDITION
We declare a few more variables here.
Score is for the score,
ScoreAsChar is the score as a string (for displaying to the screen),
DeadStars is the number of dead stars (what Blinky collects for points) and finally a new black color.
Image* BlinkyImages[6];
BlinkyImages[0] = loadImage("./Images/Idle.png");
BlinkyImages[1] = loadImage("./Images/IdleBlink.png");
BlinkyImages[2] = loadImage("./Images/Left.png");
BlinkyImages[3] = loadImage("./Images/LeftBlink.png");
BlinkyImages[4] = loadImage("./Images/Right.png");
BlinkyImages[5] = loadImage("./Images/RightBlink.png");
Image* Star = loadImage("./Images/Star.png"); //ADDITION
int FrameCounter = 0;
//ADDITION
int StarPosX = GetRandomNum(20, 420);
int StarTimer = 0;
extern int DifficultySetting;
We then load up our image for the star, and generate a random number for it's position on the screen (
StarPosX), declare
StarTimer which we will check the value of to decide whether or not to blit a star and then declare that we wish to use the external integer
DifficultySetting, which was set when starting the game (Easy = 1, Medium = 2, Hard = 3).
Onto the start of our loop:
while(1)
{
sceCtrlReadBufferPositive(&pad, 1);
FrameCounter = 0;
StarTimer++; //ADDITION
BlinkTimer++;
//if (pad.Buttons & PSP_CTRL_START)
//{
//break;
//}
if (pad.Buttons & PSP_CTRL_LEFT)
{
BlinkyPosX -= 2;
FrameCounter = 2;
}
if (pad.Buttons & PSP_CTRL_RIGHT)
{
BlinkyPosX += 2;
FrameCounter = 4;
}
if (BlinkTimer > 200)
{
FrameCounter++;
}
if (BlinkTimer > 220)
{
BlinkTimer = 0;
}
if (BlinkyPosX < 0)
{
BlinkyPosX = 0;
}
else if (BlinkyPosX > (480 - 106))
{
BlinkyPosX = (480 - 106);
}
Nothing has changed in this part, apart from incrementing the
StarTimer variable. The code for the START button being pressed has also been commented out as this was just a temporary measure to allow us to exit back to the main menu, which is not needed now.
//ADDITION (COLLISION)
if (StarPosX > BlinkyPosX)
{
if (StarPosX + Star->imageWidth < BlinkyPosX + BlinkyImages[FrameCounter]->imageWidth)
{
if (StarTimer > 60)
{
Score++;
DeadStars++;
StarPosX = GetRandomNum(20, 240);
StarTimer = 0;
}
}
}
The above section of code is all new. It's the collision detection part of our game.
Basically we are running through a series of checks to see if Blinky has collided with a star. The first check (
StarPosX > BlinkyPosX) checks that the X position of the left hand edge of the star is greater than the X position of the left hand edge of Blinky, the second (
StarPosX + Star->imageWidth < BlinkyPosX + BlinkyImages[FrameCounter]->imageWidth) checks that the right hand edge of the star is lower than the right hand edge of Blinky, thus meaning that the star is within the bounds of Blinky. Finally we check that the value within the
StarTimer variable is greater than 60 ie. the star is visible on screen (we are having a 60 frame gap between blitting the stars, as you will see in the next section of code).
If all these condition are met we then increment the
Score variable by 1, the
DeadStars variable by 1, generate a new random number for the X position of the star and reset the
StarTimer value to 0.
fillScreenRect(White, 0, 0, 480, 272);
blitAlphaImageToScreen(0, 0, 106, 72, BlinkyImages[FrameCounter], BlinkyPosX, 170);
//ADDITION
if (StarTimer > (60 + (180/DifficultySetting)))
{
DeadStars++;
StarPosX = GetRandomNum(20, 420);
StarTimer = 0;
}
else if (StarTimer > 60)
{
blitAlphaImageToScreen(0, 0, 40, 35, Star, StarPosX, 200);
}
The first two lines of this are the same as before, everything else is new.
Firstly, we check if the value held in
StarTimer is greater than our 'need to create a new star' value
if (StarTimer > (60 + (180/DifficultySetting))). Remember that based on what the user chose,
DifficultySetting could be either 1 for Easy, 2 for Medium or 3 for hard, so the easier the difficulty chosen the more time the star is shown on screen.
This is really just a check to see if the star has 'expired' ie. has been on screen for the given length of time but has not been 'collected' by Blinky.
The
else if (StarTimer > 60) after this is just to give us a 60 frame gap before blitting a new star. When a star is created the
StarTimer is set to 0 and then incremented by 1 each frame, assuming 60 frames per second this creates approximately a 1 second gap between destroying the last star and showing the new one.
sprintf(ScoreAsChar, "%d", Score); //ADDITION
printTextScreen(460, 252, ScoreAsChar, Black); //ADDITION
Here we
sprintf the value held in
Score to the char variable
ScoreAsChar, so we can display it as text to the screen.
//ADDITION
if (DeadStars == 30)
{
fillScreenRect(White, 0, 0, 480, 272);
printTextScreen(150, 130, "Finished! You Scored", Black);
printTextScreen(230, 140, ScoreAsChar, Black);
flipScreen();
sceKernelDelayThread(5000000);
break;
}
This is our 'end of game' check. The value in
DeadStars is incremented by 1 every time a star is collected or expires. So when 30 stars have been displayed, it's game over.
This is fairly simple, we just fill the screen white, display a couple of lines showing that the game is finished, give the final score, 'pause' for 5 seconds and then issue a
break which will break us out of our
while(1) game loop.
sceDisplayWaitVblankStart();
flipScreen();
}
Same as before, wait for the drawing to finish, flip the screen and then end our while loop.
Assuming that the
break has executed from the 'end of game' check the following code is then executed:
//ADDITION
int i;
for (i = 0;i < 6;i++)
freeImage(BlinkyImages[i]);
freeImage(Star);
//END ADDITION
}
Basically we're using a loop to free the memory used by the images we loaded for Blinky, then freeing the memory for the Star image and finally ending our 'Game' function, thus bringing us back to the main menu again.
It's important to free the images as eventually the repeated loading of them everytime the game is started from the main menu would lead to us running out of available memory and causing the game to crash.
Your full code for game.c should now look like this:
#include <pspkernel.h>
#include <pspdisplay.h>
#include <pspctrl.h>
#include <stdio.h> //ADDITION FOR SPRINTF
#include </usr/include/sys/stat.h> //ADDITION FOR TIME
#include "graphics.h"
#define RGB(r, g, b) ((r)|((g)<<8)|((b)<<16))
static int GetRandomNum(int lo, int hi)
{
SceKernelUtilsMt19937Context ctx;
sceKernelUtilsMt19937Init(&ctx, time(NULL)); //SEED TO TIME
u32 rand_val = sceKernelUtilsMt19937UInt(&ctx);
rand_val = lo + rand_val % hi;
return (int)rand_val;
}
void Game()
{
SceCtrlData pad;
int BlinkyPosX = 170;
int BlinkTimer = 0;
int Score = 0; //ADDITION
char ScoreAsChar[3]; //ADDITION
int DeadStars = 0; //ADDITION
Color White = RGB(255, 255, 255);
Color Black = RGB(0, 0, 0); //ADDITION
Image* BlinkyImages[6];
BlinkyImages[0] = loadImage("./Images/Idle.png");
BlinkyImages[1] = loadImage("./Images/IdleBlink.png");
BlinkyImages[2] = loadImage("./Images/Left.png");
BlinkyImages[3] = loadImage("./Images/LeftBlink.png");
BlinkyImages[4] = loadImage("./Images/Right.png");
BlinkyImages[5] = loadImage("./Images/RightBlink.png");
Image* Star = loadImage("./Images/Star.png");
int FrameCounter = 0;
//ADDITION
int StarPosX = GetRandomNum(20, 420);
int StarTimer = 0;
extern int DifficultySetting;
while(1)
{
sceCtrlReadBufferPositive(&pad, 1);
FrameCounter = 0;
StarTimer++; //ADDITION
BlinkTimer++;
//if (pad.Buttons & PSP_CTRL_START)
//{
//break;
//}
if (pad.Buttons & PSP_CTRL_LEFT)
{
BlinkyPosX -= 2;
FrameCounter = 2;
}
if (pad.Buttons & PSP_CTRL_RIGHT)
{
BlinkyPosX += 2;
FrameCounter = 4;
}
if (BlinkTimer > 200)
{
FrameCounter++;
}
if (BlinkTimer > 220)
{
BlinkTimer = 0;
}
if (BlinkyPosX < 0)
{
BlinkyPosX = 0;
}
else if (BlinkyPosX > (480 - 106))
{
BlinkyPosX = (480 - 106);
}
//ADDITION (COLLISION)
if (StarPosX > BlinkyPosX)
{
if (StarPosX + Star->imageWidth < BlinkyPosX + BlinkyImages[FrameCounter]->imageWidth)
{
if (StarTimer > 60)
{
Score++;
DeadStars++;
StarPosX = GetRandomNum(20, 240);
StarTimer = 0;
}
}
}
fillScreenRect(White, 0, 0, 480, 272);
blitAlphaImageToScreen(0, 0, 106, 72, BlinkyImages[FrameCounter], BlinkyPosX, 170);
//ADDITION
if (StarTimer > (60 + (180/DifficultySetting)))
{
DeadStars++;
StarPosX = GetRandomNum(20, 420);
StarTimer = 0;
}
else if (StarTimer > 60)
{
blitAlphaImageToScreen(0, 0, 40, 35, Star, StarPosX, 200);
}
sprintf(ScoreAsChar, "%d", Score); //ADDITION
printTextScreen(460, 252, ScoreAsChar, Black); //ADDITION
//ADDITION
if (DeadStars == 30)
{
fillScreenRect(White, 0, 0, 480, 272);
printTextScreen(150, 130, "Finished! You Scored", Black);
printTextScreen(230, 140, ScoreAsChar, Black);
flipScreen();
sceKernelDelayThread(5000000);
break;
}
sceDisplayWaitVblankStart();
flipScreen();
}
//ADDITION
int i;
for (i = 0;i < 6;i++)
freeImage(BlinkyImages[i]);
freeImage(Star);
//END ADDITION
}
Makefile:
TARGET = GameTutorial3
OBJS = main.o graphics.o framebuffer.o game.o
CFLAGS = -O2 -G0 -Wall
CXXFLAGS = $(CFLAGS) -fno-exceptions -fno-rtti
ASFLAGS = $(CFLAGS)
LIBDIR =
LIBS = -lpspgu -lpng -lz -lm
LDFLAGS =
EXTRA_TARGETS = EBOOT.PBP
PSP_EBOOT_TITLE = Game Tutorial Part 3
PSPSDK=$(shell psp-config --pspsdk-path)
include $(PSPSDK)/lib/build.mak
Files for this tutorial can be found here:
http://www.psp-programming.com/animate/GameTutorial3.rarThe archive contains everything you need apart from the main.c, game.c, game.h and makefile.
Don't forget to copy the 'Images' folder to the 'GameTutorial3' folder (without the %).
There is one major issue with this game. If a randomly generated number for the X position of the star is within the position of Blinky, the user will score a 'free' point. I purposely left this in as I'd like you to try and figure out how to fix it (look at how the collision detection works for hints).
This is the final part for this series. My next tutorial set will focus again on creating a game from scratch but this time using pure GU and creating our own graphics library and functions.
Hope you enjoyed it!