Nov 11
Tutorial nr.2
This tutorial has been written by Nyxojaele
CREATING GAMESTATES FOR THE LTE GAME ENGINE
(Copy + Paste the block of text inside the square brackets [] to find the section you want to read)
Table of Contents:
Intro..................[INTR]
Theory.................[THRY]
Application............[APCT]
Singletons.............[SNGL]
Abstract Gamestate.....[ABGS]
A Single State.........[STAT]
Gamestate Handler......[HNDL]
Modified Base Code.....[BASE]
Final Code.............[FULL]
Closing comments.......[CLSE]
[INTR]
This tutorial assumes that you have completed my last tutorial <CREATING A “BAREBONES” APPLICATION IN THE LTE GAME ENGINE>, and have an intermediate understanding of C++, although I will try to explain everything to death so that even beginners can get gamestates running in their applications, since they are so incredibly useful. Additionally, there will be a portion of code I will NOT be explaining because it goes outside the scope of this tutorial. It is only included because of the necessity to use it. Said section of code is in [SNGL]
Gamestates are one of the first things that should be implemented into any serious game. They easily allow the programmer to change “modes” of the game, for example, switching between the titlescreen, and gameplay. Or opening your inventory while the game pauses. Or switching from gameplay into a cinematic sequence. Unless your game is incredibly simple, such as that number guessing game that we’ve all made back in our first C++ programming classes, using the dos prompt, I’d highly recommend using game states to better manage not only what’s happening in the game, but also the RAM usage, and event handling.
[THRY]
Each gamestate that we create will be inherited from an abstract class that requires each state define some basic functions to be used properly:
enter(): This function is called every time this state is entered. It will do any loading/initializing that this state requires
exit(): This function is called every time this state is exited. It will do any cleanup/RAM freeing that this state requires.
pause(): This function is called every time this state needs to be set aside to be resumed later.
resume(): This function is called every time this state is returned to an operational status from being set aside.
Generally, these 4 functions can be paired up. enter()/exit(), and pause()/resume(). Anytime we enter() one state, we must either exit() or pause() the last state. If we exit()ed the last state, then to use it again, we must enter() it again. If we paus()ed the last state, we must resume() it to continue operation of it.
Handling gamestates is done via a “state stack”. Stacks 101: Think of a stack as a stack of plates beside your sink. Everytime somebody finishes eating and puts a new plate on the stack, they are “pushing” a plate on the stack. Everytime you take one off the stack to wash it, you are “popping” a plate off the stack. The nature of this stack is that you cannot get to the bottom plate unless you first “pop” all the plates off the top of the stack first.
We will be programming a gamestate handler that will handle our stack, and will basically be blind to everything except the top state in the stack. That means that all information passed to our state handler will be passed on ONLY to whatever state is at the top of the stack. Additionally, our handler will be able to “push” and “pop” states onto and off of the stack as we request. There are 2 basic ways to change states that our handler will support:
changeState(), which will exit() the top state of our stack (if it exists), then pop it off of the stack, and push a new state onto the stack, and enter() it. Example: When our game starts up, it initializes itself with the “Intro” state, which is the first state pushed onto our state stack. (Since there wasn’t any state on the stack already, nothing was popped off of the stack). Another example: When our intro sequence is finished, we pop the intro state off of the stack, and push our gameplay state onto the stack.
pushState()/popState(), which will do exactly as they describe. This pair of functions will allow us to push or pop a state without having to do the other. Calling pushState() will call our stack’s top state’s pause(), then push a new state onto the stack, and call it’s enter(). Calling popState() will call our stack’s top state’s exit(), then pop it off the stack, and if there’s another state under it, call that state’s resume(). The primary use for this would be for a “pause” gamestate. Example: When we press the start button, the game will push our paused state onto the top of the stack. The gameplay state that used to be the top will still be there, but since the paused state will be on top of it, only the paused state will receive any information from the state handler. Our paused state generally ignores it all except if you press start again to resume, at which point it will pop the paused state off of the stack, and the gameplay state underneath will start receiving all the information being passed to our game handler again.
Additional functions that our gamestates will have, that will extend their functionality:
createScene(): For state-specific logic/movement/etc…
OnEvent(): This allows for state-specific controls/GUI reactions/etc..
Our gamestate handler will have it’s own createScene() function called every frame, and will pass that call on to whichever state happens to be at the top of the stack. Additionally, our gamestate handler will be the only registered event handler for the LTE GE, and will pass these events on to whichever state happens to be at the top of the stack.
[APCT]
So let’s make something so we can understand it better, ne?
The first thing we’ll do is define our abstract class. Since it’s abstract, we can’t actually instantiate it, but we can inherit other classes from it. This is where we define how a gamestate must look to our gamestate handler.
[SNGL]
This portion of code I will not be explaining since it goes outside the scope of this tutorial, and is included only because it is necessary to make the rest of this work properly. All that needs to be known about this at this point, if you don’t know what a singleton is, is that anything that is a singleton, can only have 1 copy of itself existant at any given time. Useful for example, for our gamestate handler, and for each individual gamestate. We should never have more than 1 gamestate handler, or more than 1 intro gamestate, gameplay gamestate, etc..
singleton.h
This is a simple implementation of a singleton class
#ifndef __SINGLETON_H__
#define __SINGLETON_H__
template<class T> class Singleton : public T {
public:
static T * getInstance();
static void deleteInstance();
protected:
Singleton() : T() {}
virtual ~Singleton() {}
private:
static Singleton * sInstance;
};
template<class T> Singleton<T> *Singleton<T>::sInstance = 0;
template<class T> T *Singleton<T>::getInstance() {
if (sInstance == 0) {
sInstance = new Singleton();
atexit(deleteInstance);
}
return sInstance;
}
template<class T> void Singleton<T>::deleteInstance() {
if (sInstance) {
delete sInstance;
}
sInstance = 0;
}
#define SINGLETON_CLS(cls) Singleton<cls>
#define GET_INSTANCE(cls) (SINGLETON_CLS(cls)::getInstance())
#endif
[ABGS]
gamestate.h
Every header file should have this at the beginning (obviously tailored so each header file has a unique definition). This is a standard coding practice to avoid tons of compiler issues with including files multiple times, etc.. It’s easy to do, and saves tons of headaches. Put this at the beginning of the header:
#ifndef __GAMESTATE_H__
#define __GAMESTATE_H__
and this at the end of the header:
#endif
In between is where we put EVERYTHING for this header.
Since our gamestates are going to be capable of handling events, we’re going to inherit them from IEventReceiver, which is a class in the LTE GE specifically designed for handling LTE GE events. These events include GUI events, mouse movement/clicking, button input, audio events, log file events (not typically needed in LTE GE but they exist…), and user defined events. The main thing we’ll be concerned with at this point, will be mouse and button input, and GUI events. We are including our “singleton.h” file so that later on we can call singletons of our gamestates to be used. “gamestatehandler.h” is also included, since that’s the class we’re actually calling the singleton up of. So our next piece of code will look like this:
#include "singleton.h"
#include <engine.h>
#include "gamestatehandler.h"using namespace engine;
class gameState : public IEventReceiver {
Now we define all those functions we talked about earlier that every gamestate must have. For those of us who don’t know classes very well, each function is followed by “= 0;” to tell the compiler that any class inherited from this one MUST define it’s own version of this function.
public:
virtual void enter() = 0;
virtual void exit() = 0;virtual void pause() = 0;
virtual void resume() = 0;
virtual bool createScene() = 0;
virtual bool OnEvent(SEvent e) = 0;
virtual bool OnCatch(inEvent e) = 0;
At this point we’re going to throw in a few more functions merely for ease of use. Each of these functions in itself doesn’t actually do anything, but passes the function call to the gamestate handler to handle. This just makes it easier so we can tell a gamestate that we want to change states, instead of having to get a pointer to our gamestate handler, and tell it directly. Ease of use, nothing more.
void changeState(gameState *state) {
GET_INSTANCE(gamestateHandler)->changeState(state);
}
void pushState(gameState *state) {
GET_INSTANCE(gamestateHandler)->pushState(state);
}
void popState() {
GET_INSTANCE(gamestateHandler)->popState();
}
The last thing we do with our abstract class is our constructor: Simple stuff
protected:
gameState() {}
};
[STAT]
Okay, now that we have an abstract class defining our gamestates, let’s actually MAKE a gamestate.
Here, I’m going to put together some skeleton code just to make each gamestate WORK. Every gamestate can be created in this exact same way, then added to later to make them act as you need them to.
First off is our header file for our state. We’re going to be making 2 seperate states to switch back and forth between, and our first is going to be an “intro” state, although it’s not actually going to do anything intro-y. Note that since we’re going to be creating a new .cpp file, it will compile into a new object (.o file), so make sure to add another entry to your makefile!
introstate.h
After our standard header file stuff,
#ifndef __INTROSTATE_H__
#define __INTROSTATE_H__
we will include our abstract class header since we’re going to inherit from it:
#include "state_gamestate.h"using namespace engine;
class introState : public gameState {
Now we simply forward declare all of our functions to be defined in our .cpp file:
public:
introState();void enter();
void exit();
void pause();
void resume();
bool createScene();
bool OnEvent(SEvent e);
bool OnCatch(inEvent e);
};
#endif
Header file for our intro state is complete. Simple no? Now we can make an exact copy of that file, and name it “gameplaystate.h”. In our new file, we’ll change just a couple things to make it a header for our 2nd gamestate: First, we’ll change our header protection macros. We change the lines
#ifndef __INTROSTATE_H__
#define __INTROSTATE_H__
to
#ifndef __GAMEPLAYSTATE_H__
#define __GAMEPLAYSTATE_H__
And then simply change our class definition/constructor names:
class gameplayState : public gameState {
public:
gameplayState();
Now we can create our .cpp files, where all the actual work of the states will take place:
introstate.cpp
First, we include our class header, and a header for our gamestate handler (which we haven’t made yet), and a header for a file we haven’t created yet, “gameplaystate.h”. This will eventually be the header for our 2nd gamestate, to which we can switch to and from.
#include "singleton.h"
#include "gamestatehandler.h"
#include "introstate.h"
#include "gameplaystate.h"
Now we’ll have an empty constructor (it’s only here so we can initialize things that we may want to add later, other than that, it’s not used at all in this tutorial). You’ll notice it calls the abstract class’s constructor too, this is so we can have the abstract class initialize something if all gamestates need it. It’s all just here if you need to use it later.
introState::introState(): gameState() {}
Now our entry code. You can basically think of this function as an initScene() function for this state, since it’s called before this state is actually active. This is also empty for the most part- I’m not creating a game for you, just the functions you need! The one thing I have in here is a call to a function call that isn’t needed for any purpose other than to give us a visual indicator for which gamestate is currently active. This function call merely sets the background color to red. So we know if we see a red background, that we’re in the intro gamestate.
void introState::enter() {
GET_INSTANCE(gamestateHandler)->setBGColor(SColor(255, 255, 0, 0));
}
Our exit code. This can be thought of as our killScene() code for this state specifically, since it’s called whenever this state is exited. This is where we would clean up RAM, etc..
void introState::exit() {}
Now the pause code. This is the first thing that might be strange to you, but I’ve explained it a little bit earlier. This will be called whenever you don’t want to completely exit the state, usually so you can continue later from where you left off. Typically, you would call functions that will set the in game timer to stop increasing (effectively pausing any physics occurring in this state, character movements, etc…), and otherwise put your various game objects into a “paused” state. Admittedly, pause() and resume() won’t be needed at all for our introstate (who lets you pause their intro sequences?), but since they’re required functions from the abstract gamestate class, we need to include them, even if they don’t do anything.
void introState::pause() {}
void introState::resume() {}
Okay, this is our code for handling this specific state. Everything you would normally put into our normal createScene() function, that applies to this state, would go here. This function will be called every frame during the time that this state is at the top of the gamestate handler’s stack.
bool introState::createScene() {}
Event handler: Here is where we can define how we want to handle the LTE GE events in this state. We can ignore whichever events don’t apply to this state, but I’m going to supply a skeleton set of code that’ll make it easy to implement reactions to any of the events sent by the LTE GE. It’s very simple, all it’s doing is checking what kind of event it is, and allowing you to put different reactions in based on what type it is. Nothing special. Since every state has one of these, each state can react differently to the events. This means neat things like being able to have different sets of controls for each state, and giving the mouse different functions in each state. Be creative^^
For purposes of giving as a visual indicator of changing states, there’s some simple code that will read a little bit of the event information when you press a button. If you’ve pressed the RButton, the application will change states to the gameplay state.
bool introState::OnEvent(SEvent e) {
switch (e.EventType) {
case EET_GUI_EVENT:
break;
case EET_MOUSE_INPUT_EVENT:
break;
case EET_KEY_INPUT_EVENT: {
switch(e.KeyInput.Key) {
case KEY_RBUTTON: {
GET_INSTANCE(gamestateHandler)->changeState(GET_INSTANCE(gameplayState));
break;
}
default: {
break;
}
}
break;
}
case EET_LOG_TEXT_EVENT:
break;
case EET_AUDIO_EVENT:
break;
case EET_USER_EVENT:
break;
default:
break;
}
return false;
}
So that’s it for our skeleton intro state. It does absolutely nothing, since we haven’t defined the contents of the core functions: enter(), exit(), pause(), resume(), createScene(), and OnEvent(). But it’s all there and ready for you to define whatever it is that you want this state to do!
Again, for our 2nd state, we can make a direct copy of our “introstate.cpp” file, naming our copy “gameplaystate.cpp”, and modify our copy’s contents a bit to reflect that it’s now a part of a new class. First of all, all of our class function signatures need to be changed from introState:: to gameplayState::. Additionally we will change our enter() function to give us a different colored background, and help us visually identify which state is current.
void gameplayState::enter() {
GET_INSTANCE(gamestateHandler)->setBGColor(SColor(255, 0, 0, 255));
}
After that, we just have to change the event handler to detect a different button (This time, the LButton), and to change to a different state. So we change the lines
case KEY_RBUTTON: {
GET_INSTANCE(gamestateHandler)->changeState(GET_INSTANCE(gameplayState));
to
case KEY_LBUTTON: {
GET_INSTANCE(gamestateHandler)->changeState(GET_INSTANCE(introState));
Most of the code that’s different between the 2 states is merely code that is only here for example purposes: Visually identifying which state is current, and giving a different control scheme to each state.
[HNDL]
So this is all fine and dandy, but what do we DO with this new-fangled gamestate? Well, this is where our [until now] mythical gamestate handler comes into play! This class is going to be what connects all these fancy gamestates to the actual application they’re going to be a part of. What we’re going to do is modify our core code to refer to our gamestate handler’s functions instead of the global functions we had defined before, and our gamestate handler will then pass those function calls to whichever gamestate is on the top of it’s stack. Additionally, our gamestate handler will have all the functions required to manipulate our gamestate stack to whatever we need.
gamestatehandler.h
Standard startup stuff. We also include <stack> since that’s what our gamestates will be stored in
#ifndef __GAMESTATEHANDLER_H__
#define __GAMESTATEHANDLER_H__#include <engine.h>
#include <stack>
using namespace engine;
using namespace core;
using namespace scene;
using namespace video;
using namespace io;
using namespace gui;
using namespace audio;
Now we’ll forward declare our gamestate class so we can refer to it in this header without including the whole header for the class.
class gameState;
Our gamestate handler class will be inherited from LTE GE’s IEventReceiver, because it’s going to be the primary receiver of events sent by the LTE GE (these events are always received via OnEvent() ).
class gamestateHandler: public IEventReceiver {
public:
gamestateHandler();
virtual ~gamestateHandler();
These are the functions that will actually handle the gamestate stack for us.
//State stuff
const gameState * getCurrentState() { return mStates.top(); }
bool changeState(gameState *state);
bool pushState(gameState *state);
void popState();
Our next 2 public functions are only here for visually determining which gamestate is active at the time. With one, we simply store an SColor variable that is called later on by the other.
void setBGColor(SColor color) { mBGColor = color; }
SColor getBGColor() { return mBGColor; }
Now our function to pass on the createScene() call, and our function to receive events (because we inherited from IEventReceiver).
bool createScene();
protected:
bool OnEvent(SEvent e);
Here’s our variable for storing the color mentioned earlier
SColor mBGColor;
And lastly, a stack of plate-err, states^^ Something to note is that you should never insert() into an std::stack object. It’s not safe. push() and pop() are safe- if there’s an error, the stack remains unchanged.
std::stack<gameState *> mStates;
};
#endif
gamestatehandler.cpp
So let’s define these fancy functions now! First we’ll include our header file, and a header for the first state we want to use, as well as our singleton header since we want to call up singletons of these things.
#include "singleton.h"
#include "gamestate.h"
#include "introstate.h"
Empty constructor/destructor for if you want to alter them later:
gamestateHandler::gamestateHandler() {}
gamestateHandler::~gamestateHandler() {}
Now the meaty part; Here’s where we handle our state stack. This function starts by checking if there’s a state on the stack currently. If there is, it’ll exit() the state, then pop it off the stack.
bool gamestateHandler::changeState(gameState *state) {
//Cleanup the current state
if (!mStates.empty()) {
mStates.top()->exit();
mStates.pop();
}
So we’ve cleared out the last state, now let’s introduce the new state. First we push the state onto the stack, and if it successfully pushes on, we’ll enter() the state.
//Store and init the new state
mStates.push(state);
if (mStates.top() == state) {
mStates.top()->enter();
}
return true;
}
There we go, that’s all there is to it. Changing states is as easy as calling up that function, and passing in the singleton of the state we want (we’ll see this later when we initialize our application)
Next up is where we push a state onto the stack (usually for pausing). Very simple. If there’s a state on the stack, we pause() it, push our new state onto the stack, and enter() our new state if it pushed onto the stack successfully..
bool gamestateHandler::pushState(gameState *state) {
//Pause current state
if (!mStates.empty()) {
mStates.top()->pause();
}
//Store and init the new state
mStates.push(state);
if (mStates.top() == state) {
mStates.top()->enter();
}
return true;
}
Now how we pop a state off the stack (typically for resuming the state under our current state). First there’s a state on the top of the stack, we exit() it, and if there’s another state on the top of the stack now, we resume() that state.
void gamestateHandler::popState() {
//Clean up the current state
if (!mStates.empty()) {
mStates.top()->exit();
mStates.pop();
}
//Resume previous state
if (!mStates.empty()) {
mStates.top()->resume();
}
}
Okay, here’s where we receive events. When we go alter our base code to use our gamestates, we’ll be making a small change that will make the LTE GE pass all events to our gamestate handler class. This class will pass out those events in turn, to the state at the top of the stack (that state gets ALL the lovin’!). As you can see, it’s the same thing I had written in the OnEvent() function for our introstate, but for each event type, it’s checking if there’s a state on the top of the stack, and passing the event to that state if it exists. You may be wondering why I’m not just blindly passing the event on, ne? Well, what if we decide that log events don’t need to go to the top state? Or maybe if they need to be passed to a completely different object altogether? Well we can easily do that now. As you can see in this code, our log events are just being dropped. No use passing them on if they’re useless to our states, ne?
bool gamestateHandler::OnEvent(SEvent e) {
switch (e.EventType) {
case EET_GUI_EVENT: {
if (!mStates.empty()) {
mStates.top()->OnEvent(e);
}
break;
}
case EET_MOUSE_INPUT_EVENT: {
if (!mStates.empty()) {
mStates.top()->OnEvent(e);
}
return false;
}
case EET_KEY_INPUT_EVENT: {
if (!mStates.empty()) {
mStates.top()->OnEvent(e);
}
break;
}
case EET_LOG_TEXT_EVENT: {
//Don't even pass this around, nothing to do with it.
break;
}
case EET_AUDIO_EVENT: {
if (!mStates.empty()) {
mStates.top()->OnEvent(e);
}
break;
}
case EET_USER_EVENT: {
if (!mStates.empty()) {
mStates.top()->OnEvent(e);
}
break;
}
default: {
break;
}
}
return false;
}
This function is called every frame. We don’t do much with it here, but we pass it on to the state at the top of the stack, if it exists, so the state can do it’s own logic/movement/etc…
bool gamestateHandler::createScene() {
if (!mStates.empty()) {
return mStates.top()->createScene();
}
return true;
}
[BASE]
So now that we have this nifty gamestate handler, how do we USE it? Some simple modifications to our barebones application will put this baby to some good use.
First thing we need to do is instantiate our singleton of our gamestateHandler. After we include the appropriate header files
#include "singleton.h"
#include "gamestateHandler.h"
#include "introstate.h"
A simple call at the beginning of our entrypoint will give us what we need:
GET_INSTANCE(gamestateHandler);
But since the first time we call GET_INSTANCE() on a class, it’ll create the class for us (this is specific to my implementation of singletons), we can coincidentally use it at the same time as it’s first needed. And the first time we need it is when we create our LTE GE device, because we’ll be setting our gamestate handler as the default event receiver, which is defined when you create a device for the LTE GE. So what we’ll do is change this line
device = createDevice(NULL, true);
to this:
device = createDevice(GET_INSTANCE(gamestateHandler), true);
Before we start our game loop, we’ll wanna make sure we’ve set the game into a valid state (or we’ll segfault, which is umm… bad.), so we throw in this line. The “GET_INSTANCE” portion is from my singleton code I threw in earlier. It simply grabs a pointer to our singleton object.
if (!GET_INSTANCE(gamestateHandler)->changeState(GET_INSTANCE(introState)))
return 1;
And now inside our gameloop, the visual indication for gamestate that I’ve been mentioning. When we call driver->beginScene(), one of the parameters passed in is the color of the background. Instead of hard-coding a value as before, we’ll retrieve the color stored in our gamestate handler.
driver->beginScene(true, true, GET_INSTANCE(gamestateHandler)->getBGColor());
Also we have to make sure we call up our gamestate handler’s createScene() (and we can actually remove our global function altogether!)
GET_INSTANCE(gamestateHandler)->createScene();
[FULL]
Here’s the complete files for this project:
main.cpp
#include <engine.h> #define PSP_ENABLE_DEBUG
#include "common.h"
#include "singleton.h"
#include "gamestateHandler.h"
#include "introstate.h"
using namespace engine;
using namespace core;
using namespace scene;
using namespace video;
using namespace io;
using namespace gui;
using namespace audio;
engineDevice *device = NULL;
IVideoDriver *driver = NULL;
ISceneManager *smgr = NULL;
IGUIEnvironment *guienv = NULL;
void initScene() {
}
void killScene() {
}
int engineMain(unsigned int argc, void *argv) {
setupPSP();
device = createDevice(GET_INSTANCE(gamestateHandler), true);
driver = device->getVideoDriver();
smgr = device->getSceneManager();
guienv = device->getGUIEnvironment();
if (!GET_INSTANCE(gamestateHandler)->changeState(GET_INSTANCE(introState))) {
return 1;
}
initScene();
while(device->run()) {
driver->beginScene(true, true, GET_INSTANCE(gamestateHandler)->getBGColor());
smgr->drawAll();
GET_INSTANCE(gamestateHandler)->createScene();
guienv->drawAll();
driver->endScene();
}
killScene();
sceKernelSleepThreadCB();
return 0;
}
singleton.h
#ifndef __SINGLETON_H__
#define __SINGLETON_H__
template<class T> class Singleton : public T {
public:
static T * getInstance();
static void deleteInstance();
protected:
Singleton() : T() {}
virtual ~Singleton() {}
private:
static Singleton * sInstance;
};
template<class T> Singleton<T> *Singleton<T>::sInstance = 0;
template<class T> T *Singleton<T>::getInstance() {
if (sInstance == 0) {
sInstance = new Singleton();
atexit(deleteInstance);
}
return sInstance;
}
template<class T> void Singleton<T>::deleteInstance() {
if (sInstance) {
delete sInstance;
}
sInstance = 0;
}
#define SINGLETON_CLS(cls) Singleton<cls>
#define GET_INSTANCE(cls) (SINGLETON_CLS(cls)::getInstance())
#endif
gamestate.h
#ifndef __GAMESTATE_H__
#define __GAMESTATE_H__#include "singleton.h"
#include <engine.h>
#include "gamestatehandler.h"
using namespace engine;
class gameState : public IEventReceiver {
public:
virtual void enter() = 0;
virtual void exit() = 0;
virtual void pause() = 0;
virtual void resume() = 0;
virtual bool createScene() = 0;
virtual bool OnEvent(SEvent e) = 0;
void changeState(gameState *state) { GET_INSTANCE(gamestateHandler)->changeState(state); }
void pushState(gameState *state) { GET_INSTANCE(gamestateHandler)->pushState(state); }
void popState() { GET_INSTANCE(gamestateHandler)->popState(); }
protected:
gameState() {}
};
#endif
introstate.h
#ifndef __INTROSTATE_H__
#define __INTROSTATE_H__#include "gamestate.h"
using namespace engine;
class introState : public gameState {
public:
introState();
void enter();
void exit();
void pause();
void resume();
bool createScene();
bool OnEvent(SEvent e);
};
#endif
introstate.cpp
#include "singleton.h"
#include "introstate.h"
#include "gamestatehandler.h"
#include "gameplaystate.h"introState::introState(): gameState() {}
void introState::enter() {
GET_INSTANCE(gamestateHandler)->setBGColor(SColor(255, 255, 0, 0));
}
void introState::exit() {}
void introState::pause() {}
void introState::resume() {}
bool introState::createScene() {
return true;
}
bool introState::OnEvent(SEvent e) {
switch (e.EventType) {
case EET_GUI_EVENT:
break;
case EET_MOUSE_INPUT_EVENT:
break;
case EET_KEY_INPUT_EVENT: {
switch(e.KeyInput.Key) {
case KEY_RBUTTON: {
GET_INSTANCE(gamestateHandler)->changeState(GET_INSTANCE(gameplayState));
break;
}
default: {
break;
}
}
break;
}
case EET_LOG_TEXT_EVENT:
break;
case EET_AUDIO_EVENT:
break;
case EET_USER_EVENT:
break;
default:
break;
}
return false;
}
gameplaystate.h
#ifndef __GAMEPLAYSTATE_H__
#define __GAMEPLAYSTATE_H__#include "gamestate.h"
using namespace engine;
class gameplayState : public gameState {
public:
gameplayState();
void enter();
void exit();
void pause();
void resume();
bool createScene();
bool OnEvent(SEvent e);
};
#endif
gameplaystate.cpp
#include "singleton.h"
#include "introstate.h"
#include "gamestatehandler.h"
#include "gameplaystate.h"gameplayState::gameplayState(): gameState() {}
void gameplayState::enter() {
GET_INSTANCE(gamestateHandler)->setBGColor(SColor(255, 0, 0, 255));
}
void gameplayState::exit() {}
void gameplayState::pause() {}
void gameplayState::resume() {}
bool gameplayState::createScene() {
return true;
}
bool gameplayState::OnEvent(SEvent e) {
switch (e.EventType) {
case EET_GUI_EVENT:
break;
case EET_MOUSE_INPUT_EVENT:
break;
case EET_KEY_INPUT_EVENT: {
switch(e.KeyInput.Key) {
case KEY_LBUTTON: {
GET_INSTANCE(gamestateHandler)->changeState(GET_INSTANCE(introState));
break;
}
default: {
break;
}
}
break;
}
case EET_LOG_TEXT_EVENT:
break;
case EET_AUDIO_EVENT:
break;
case EET_USER_EVENT:
break;
default:
break;
}
return false;
}
gamestatehandler.h
#ifndef __GAMESTATEHANDLER_H__
#define __GAMESTATEHANDLER_H__#include <engine.h>
#include <stack>
using namespace engine;
using namespace core;
using namespace scene;
using namespace video;
using namespace io;
using namespace gui;
using namespace audio;
class gameState;
class gamestateHandler: public IEventReceiver {
public:
gamestateHandler();
virtual ~gamestateHandler();
//State stuff
const gameState * getCurrentState() { return mStates.top(); }
bool changeState(gameState *state);
bool pushState(gameState *state);
void popState();
void setBGColor(SColor color) { mBGColor = color; }
SColor getBGColor() { return mBGColor; }
bool createScene();
protected:
bool OnEvent(SEvent e);
SColor mBGColor;
std::stack<gameState *> mStates;
};
#endif
gamestatehandler.cpp
#include "singleton.h"
#include "gamestate.h"
#include "introstate.h"gamestateHandler::gamestateHandler() {}
gamestateHandler::~gamestateHandler() {}
bool gamestateHandler::changeState(gameState *state) {
//Cleanup the current state
if (!mStates.empty()) {
mStates.top()->exit();
mStates.pop();
}
//Store and init the new state
mStates.push(state);
if (mStates.top() == state) {
mStates.top()->enter();
}
return true;
}
bool gamestateHandler::pushState(gameState *state) {
//Pause current state
if (!mStates.empty()) {
mStates.top()->pause();
}
//Store and init the new state
mStates.push(state);
if (mStates.top() == state) {
mStates.top()->enter();
}
return true;
}
void gamestateHandler::popState() {
//Clean up the current state
if (!mStates.empty()) {
mStates.top()->exit();
mStates.pop();
}
//Resume previous state
if (!mStates.empty()) {
mStates.top()->resume();
}
}
bool gamestateHandler::OnEvent(SEvent e) {
switch (e.EventType) {
case EET_GUI_EVENT: {
if (!mStates.empty()) {
mStates.top()->OnEvent(e);
}
break;
}
case EET_MOUSE_INPUT_EVENT: {
if (!mStates.empty()) {
mStates.top()->OnEvent(e);
}
return false;
}
case EET_KEY_INPUT_EVENT: {
if (!mStates.empty()) {
mStates.top()->OnEvent(e);
}
break;
}
case EET_LOG_TEXT_EVENT: {
//Don't even pass this around, nothing to do with it.
break;
}
case EET_AUDIO_EVENT: {
if (!mStates.empty()) {
mStates.top()->OnEvent(e);
}
break;
}
case EET_USER_EVENT: {
if (!mStates.empty()) {
mStates.top()->OnEvent(e);
}
break;
}
default: {
break;
}
}
return false;
}
bool gamestateHandler::createScene() {
if (!mStates.empty()) {
return mStates.top()->createScene();
}
return true;
}
The only other file you’ll need is “common.h”, which comes with the LTE GE examples.
[CLSE]
Our example has simple operation, yet very powerful potential. Simply press the R Shoulder Button when the screen is red (intro state), to change it to blue (gameplay state), or press the L Shoulder Button when the screen is blue, to change it to red. Every time the screen changes colors, your application is changing states. And every time it’s changing states, it’s also changing which createScene() and OnEvent() are being called while the application is running.
This simple implementation should give you plenty to expand upon, but should be all you really need to at least get started with coding whatever sort of game it is that you want to code.
If these tutorials are well received, I may write some more on other aspects of video game programming.