As promised I'll be posting up a mini series of tutorials on how to make a (simple) full game which will incorporate things like menus, animation, collision etc.
Part 1 - A MenuA menu is an integral part of any game, they don't need to be complicated or fancy, just functional.
X will be our select/confirm button, O will be back/cancel.
We will have two menus within our game, a 'Start Game' menu, and a menu which allows you to choose the difficulty setting of the game.
I would recommend you having gone through all the tutorials on the main site before looking at this.
Create a new file named main.c
Let's start with the code, first our includes:
#include <pspkernel.h>
#include <pspdisplay.h>
#include <pspctrl.h>
#include "graphics.h"
You should know what most of these are by now; pspkernel.h is the base of all PSP programs, pspdisplay.h gives us access to the screen, pspctrl.h is for controls and graphics.h contains GU functions.
PSP_MODULE_INFO("Game Tutorial Part 1: Menu", 0, 1, 1);
Usual stuff, naming our program.
#define RGB(r, g, b) ((r)|((g)<<8)|((b)<<16))
This above line allows us to input our colors as RGB (Red/Green/Blue) values and it will convert them to native BGR (Blue/Green/Red) values.
/* Exit callback */
int exit_callback(int arg1, int arg2, void *common) {
sceKernelExitGame();
return 0;
}
/* Callback thread */
int CallbackThread(SceSize args, void *argp) {
int cbid;
cbid = sceKernelCreateCallback("Exit Callback", exit_callback, NULL);
sceKernelRegisterExitCallback(cbid);
sceKernelSleepThreadCB();
return 0;
}
/* Sets up the callback thread and returns its thread id */
int SetupCallbacks(void) {
int thid = 0;
thid = sceKernelCreateThread("update_thread", CallbackThread, 0x11, 0xFA0, 0, 0);
if(thid >= 0) {
sceKernelStartThread(thid, 0, 0);
}
return thid;
}
Again, normal stuff, our callbacks.
Before I go into the next bit of code it's worth mentioning the scope of variables.
We'll be dealing with two types of variables, local and global.
If a variable is declared within a function (including main()), it is local to that function. Variables of the same name may be declared and used within other functions without any conflicts.
If a variable is declared outside of a function (and before all functions within that program) it is a global variable and can be accessed and altered by any of the functions.
If you don't understand that fully don't worry, re-read the above few paragraphs again. I'll talk about why we need this variable to be global in a later part.
//GLOBAL VARIABLES
int DifficultySetting = 1;
Now, we'll start our main() function:
int main(void) {
SetupCallbacks();
initGraphics();
Again, normal stuff; start our main() function, setup the callbacks and initialise the graphics.
SceCtrlData pad, lastpad;
sceCtrlReadBufferPositive(&lastpad, 1);
Image* Background;
Background = loadImage("./Images/Background.png");
Color EasyColor = RGB(0, 0, 0);
Color MediumColor = RGB(0, 0, 0);
Color HardColor = RGB(0, 0, 0);
int OnStartGameMenu = 1;
int OnDifficultyMenu = 0;
The above are just our variable declarations, we will have two variables which hold our control input (pad & lastpad), then we read the controls and populate our lastpad variable (you'll see why we need the lastpad variable shortly), load an image for our menu background (Background) and declare three colors (EasyColor, MediumColor & HardColor) all black and finally we declare two integers (OnStartGameMenu & OnDifficultyMenu) which we will later use as checks for which 'part' of the menu we are on (1 equals on, 0 equals off). We could use a boolean or other variable to store this check, but I prefer integers.
extern int DifficultySetting;
Ok ok, calm down - I can hear you saying 'What the hell is extern?'. Remember when we declared the global variable DifficultySetting earlier on? The above line just declares that we are going to be using it in this function. If we didn't have the 'extern' in and we just declared it as 'int DifficultySetting', it would be a local variable to main() only.
The reason why I've used 'extern' as opposed to other methods associated with global variables will become apparent in a later part.
Onto our loop.
while(1) {
You should know what this does and why we have it by now. It creates our loop for reading controls, blitting images etc.
sceCtrlReadBufferPositive(&pad, 1);
Reads our controls and puts the result in the 'pad' variable.
Because we don't want our controls to 'repeat' we check what the last button pressed was:
if(pad.Buttons != lastpad.Buttons) {
lastpad = pad;
This translates to 'if current button pressed is not the same as the last button pressed do this...' Then we set the lastpad variable to the last button pressed.
if(pad.Buttons & PSP_CTRL_CROSS)
{
We're checking to see if the X button has been pressed.
if (OnStartGameMenu == 1)
{
OnStartGameMenu = 0;
OnDifficultyMenu = 1;
DifficultySetting = 1;
}
else if (OnDifficultyMenu == 1)
{
//HERE'S WHERE WE WOULD START THE ACTUAL GAME
}
}
Now we're seeing which menu we were on when X was pressed. if we are on the start game menu we turn it off, turn the difficulty menu on and make DifficultySetting = 1 (Easy). If however we were already on the difficulty menu, we would start the game (which we won't here as this is just the menu tutorial).
if(pad.Buttons & PSP_CTRL_CIRCLE)
{
if (OnDifficultyMenu == 1)
{
OnStartGameMenu = 1;
OnDifficultyMenu = 0;
}
}
Checking to see if the O button was pressed, if so we turn the difficulty menu off and the start game menu back on. Think of this as moving back an option as you would in a regular PSP game. Note how we don't have any code here for if O was pressed on the start menu - that's because we don't want the O button to do anything while we are on the start menu - just the difficulty menu.
if(pad.Buttons & PSP_CTRL_UP)
{
if (OnDifficultyMenu == 1)
{
DifficultySetting--;
if (DifficultySetting < 1)
DifficultySetting = 1;
}
}
Checking to see if the UP button was pressed. If so, and again this only applies to the difficulty menu, we select the difficulty setting above. The difficulty settings are:
Easy = 1
Medium = 2
Hard = 3
We also have a check to make sure that if we have Easy (1) selected already when we press UP, we will still have it selected.
if(pad.Buttons & PSP_CTRL_DOWN)
{
if (OnDifficultyMenu == 1)
{
DifficultySetting++;
if (DifficultySetting > 3)
DifficultySetting = 3;
}
}
}
Similar to the check for UP, we are incrementing the difficulty setting now and also have a check to ensure that we cannot go further than Hard (3).
That's it for the controls, now onto displaying things on the screen.
blitAlphaImageToScreen(0, 0 , 480, 272, Background, 0, 0);
Blitting our menu background image. We have this before the following checks as we want it to be displayed regardless of whether we are on the start menu or the difficulty menu.
if (OnStartGameMenu == 1)
{
printTextScreen(170, 140, "Start Game", RGB(191, 0, 0));
}
If we're on the start menu, the text 'Start Game' will be displayed in red.
else if (OnDifficultyMenu == 1)
{
if (DifficultySetting == 1)
{
EasyColor = RGB(191, 0, 0);
MediumColor = RGB(0, 0, 0);
HardColor = RGB(0, 0, 0);
}
else if (DifficultySetting == 2)
{
EasyColor = RGB(0, 0, 0);
MediumColor = RGB(191, 0, 0);
HardColor = RGB(0, 0, 0);
}
else if (DifficultySetting == 3)
{
EasyColor = RGB(0, 0, 0);
MediumColor = RGB(0, 0, 0);
HardColor = RGB(191, 0, 0);
}
We're defining the colors we're going to use for the difficulty menu here. All it's doing is saying 'if we selected Easy (1) then make Easy red, if we selected Medium (2) make Medium red etc. and making the other non-selected items black.
printTextScreen(170, 140, "Easy", EasyColor);
printTextScreen(170, 150, "Medium", MediumColor);
printTextScreen(170, 160, "Hard", HardColor);
}
The text for the difficulty menu is being displayed, coloured to what we declared above based on our current selection.
sceDisplayWaitVblankStart();
flipScreen();
}
Waiting for the Vblank and then flipping the off-screen so it's displayed. Standard double-buffering stuff.
Let's wrap it up.
sceKernelSleepThread();
return 0;
}
Your full code should look something like this:
#include <pspkernel.h>
#include <pspdisplay.h>
#include <pspctrl.h>
#include "graphics.h"
PSP_MODULE_INFO("Game Tutorial Part 1: Menu", 0, 1, 1);
#define RGB(r, g, b) ((r)|((g)<<8)|((b)<<16))
/* Exit callback */
int exit_callback(int arg1, int arg2, void *common) {
sceKernelExitGame();
return 0;
}
/* Callback thread */
int CallbackThread(SceSize args, void *argp) {
int cbid;
cbid = sceKernelCreateCallback("Exit Callback", exit_callback, NULL);
sceKernelRegisterExitCallback(cbid);
sceKernelSleepThreadCB();
return 0;
}
/* Sets up the callback thread and returns its thread id */
int SetupCallbacks(void) {
int thid = 0;
thid = sceKernelCreateThread("update_thread", CallbackThread, 0x11, 0xFA0, 0, 0);
if(thid >= 0) {
sceKernelStartThread(thid, 0, 0);
}
return thid;
}
//GLOBAL VARIABLES
int DifficultySetting = 1;
int main(void) {
SetupCallbacks();
initGraphics();
SceCtrlData pad, lastpad;
sceCtrlReadBufferPositive(&lastpad, 1);
Image* Background;
Background = loadImage("./Images/Background.png");
Color EasyColor = RGB(0, 0, 0);
Color MediumColor = RGB(0, 0, 0);
Color HardColor = RGB(0, 0, 0);
int OnStartGameMenu = 1;
int OnDifficultyMenu = 0;
extern int DifficultySetting;
while(1) {
sceCtrlReadBufferPositive(&pad, 1);
if(pad.Buttons != lastpad.Buttons) {
lastpad = pad;
if(pad.Buttons & PSP_CTRL_CROSS)
{
if (OnStartGameMenu == 1)
{
OnStartGameMenu = 0;
OnDifficultyMenu = 1;
DifficultySetting = 1;
}
else if (OnDifficultyMenu == 1)
{
//HERE'S WHERE WE WOULD START THE ACTUAL GAME
}
}
if(pad.Buttons & PSP_CTRL_CIRCLE)
{
if (OnDifficultyMenu == 1)
{
OnStartGameMenu = 1;
OnDifficultyMenu = 0;
}
}
if(pad.Buttons & PSP_CTRL_UP)
{
if (OnDifficultyMenu == 1)
{
DifficultySetting--;
if (DifficultySetting < 1)
DifficultySetting = 1;
}
}
if(pad.Buttons & PSP_CTRL_DOWN)
{
if (OnDifficultyMenu == 1)
{
DifficultySetting++;
if (DifficultySetting > 3)
DifficultySetting = 3;
}
}
}
blitAlphaImageToScreen(0, 0 , 480, 272, Background, 0, 0);
if (OnStartGameMenu == 1)
{
printTextScreen(170, 140, "Start Game", RGB(191, 0, 0));
}
else if (OnDifficultyMenu == 1)
{
if (DifficultySetting == 1)
{
EasyColor = RGB(191, 0, 0);
MediumColor = RGB(0, 0, 0);
HardColor = RGB(0, 0, 0);
}
else if (DifficultySetting == 2)
{
EasyColor = RGB(0, 0, 0);
MediumColor = RGB(191, 0, 0);
HardColor = RGB(0, 0, 0);
}
else if (DifficultySetting == 3)
{
EasyColor = RGB(0, 0, 0);
MediumColor = RGB(0, 0, 0);
HardColor = RGB(191, 0, 0);
}
printTextScreen(170, 140, "Easy", EasyColor);
printTextScreen(170, 150, "Medium", MediumColor);
printTextScreen(170, 160, "Hard", HardColor);
}
sceDisplayWaitVblankStart();
flipScreen();
}
sceKernelSleepThread();
return 0;
}
Makefile (Again, standard makefile stuff - nothing too advanced here)
TARGET = GameTutorial1
OBJS = main.o graphics.o framebuffer.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 1
PSPSDK=$(shell psp-config --pspsdk-path)
include $(PSPSDK)/lib/build.mak
Here's a link to the files needed for this tutorial (graphics.c, graphics.h, framebuffer.c, framebuffer.h, Background.png).
Place the whole 'Images' folder in the 'GameTutorial1' folder after you compile (the whole 'Images' folder - not just the Background.png!)
http://www.psp-programming.com/animate/GameTutorial1.rarNext tutorial part will involve loading the game, animating the character and performing a few checks.
Part 2 in now up:
http://www.psp-programming.com/dev-forum/viewtopic.php?t=386Stay tuned!