Pong Game State
So we now have a neat little title screen, but to start implementing the actual game, we need to move beyond the TitleScreenState
. To do so, we need to detect the user input and change the engine’s current state when the START button is pressed.
# Exiting the Title Screen
That is done in the GameState::processUserInput method, which the state provided in the template project already overrides.
singleton class TitleScreenState : GameState
{
[...]
/// Process the provided user input.
/// @param userInput: Struct with the current user input information
override void processUserInput(const UserInput* userInput);
}
That method receives a pointer to a struct called UserInput that has the user input that was registered during the last game frame. This is the declaration of that struct with all the possible fields that can be polled during user input processing:
/// User's input
/// @memberof KeypadManager
typedef struct UserInput
{
/// All pressed key(s)
uint16 allKeys;
/// Pressed key(s) just in the last cycle
uint16 pressedKey;
/// Released key(s) just in the last cycle
uint16 releasedKey;
/// Held key(s)
uint16 holdKey;
/// How long the key(s) have been held (in game frames)
uint32 holdKeyDuration;
/// Previously pressed key(s)
uint16 previousKey;
/// Low power flag
uint16 powerFlag;
/// Dummy input to force user input processing even if
/// there is not a real one
uint16 dummyKey;
} UserInput;
So, to detect if the user pressed the START button, the pressedKey
attribute of the UserInput struct has to be tested against K_STA. If successful, we will make the engine change its current state to the GameState in which the actual game will run. Add the following to TitleScreenState::processUserInput
:
void TitleScreenState::processUserInput(const UserInput* userInput)
{
[...]
if(0 != (K_STA & userInput->pressedKey))
{
// Disable the keypad to prevent processing of more inputs
KeypadManager::disable();
// Tell the VUEngine to change the current state
VUEngine::changeState(GameState::safeCast(PongState::getInstance()));
}
}
Note: Do not forget to also add PongState.h to the list of #includes at the top of the file.
# Entering the Pong State
The main purpose of a GameState is to serve as the point of contact between VUEngine and the actual game. GameStates have a life cycle defined by the following interface:
/// Prepares the object to enter this state.
/// @param owner: Object that is entering in this state
virtual void enter(void* owner);
/// Updates the object in this state.
/// @param owner: Object that is in this state
virtual void execute(void* owner);
/// Prepares the object to exit this state.
/// @param owner: Object that is exiting this state
virtual void exit(void* owner);
/// Prepares the object to become inactive in this state.
/// @param owner: Object that is in this state
virtual void suspend(void* owner);
/// Prepares the object to become active in this state.
/// @param owner: Object that is in this state
virtual void resume(void* owner);
While it is possible to implement a whole game that runs solely in the execute
method, the flexibility of the engine shines when using Stages, which are Containers that have Actors as children.
When the engine’s StateMachine enters a new GameStates, it will call GameState::enter, where the state can be initialized.
But we just got ahead of ourselves a little bit. First, we need to create the PongState
in order to being able to transition to it. To do so, create the folder source/States/PongState and two files in it: source/States/PongState/PongState.c and source/States/PongState/PongState.h. In the latter, declare the PongState
class as shown below:
singleton class PongState : GameState
{
/// Method to get the singleton instance
/// @return PongState singleton
static PongState getInstance();
}
The PongState
will remain empty if we don’t add actors to it. For a Pong game, we will need a disk and two paddles, but since the paddles are the same, we’ll need only one. Create the actors in assets/Actor/Disk/ and assets/Actor/Paddle/ with the .actor file as it was done before to create the logo on the title screen.
Now, we need a StageSpec for the PongState
. Simply copy the TitleScreenStageSpec.c file, rename it to PongStageSpec.c and rename all the variables in it from TitleScreen*
to Pong*
. Finally, add the ActorSpecs for the disk and the paddle as we added the LogoActorSpec to the title screen:
[...]
extern ActorSpec DiskActorSpec;
extern ActorSpec PaddleActorSpec;
[...]
PositionedActorROMSpec PongStageActors[] =
{
{&DiskActorSpec, {0, 0, 0}, {0, 0, 0}, {1, 1, 1}, 0, NULL, NULL, NULL, false},
{&PaddleActorSpec, {-180, 0, 0}, {0, 0, 0}, {1, 1, 1}, 0, NULL, NULL, NULL, false},
{&PaddleActorSpec, {180, 0, 0}, {0, 0, 0}, {1, 1, 1}, 0, NULL, NULL, NULL, false},
{&LowPowerIndicatorActorSpec, {-192 + 8, 112 - 4, 0}, {0, 0, 0}, {1, 1, 1}, 0, NULL, NULL, NULL, false},
{NULL, {0, 0, 0}, {0, 0, 0}, {1, 1, 1}, 0, NULL, NULL, NULL, false},
};
Now that the StageSpec is ready, it can be passed to PongState::configureStage:
void PongState::enter(void* owner __attribute__((unused)))
{
Base::enter(this, owner);
// Load stage
PongState::configureStage(this, (StageSpec*)&PongStageSpec, NULL);
PongState::startClocks(this);
KeypadManager::enable();
}
Don’t forget to override the method in the header file:
singleton class PongState : GameState
{
/// Method to get the singleton instance
/// @return PongState singleton
static PongState getInstance();
/// Prepares the object to enter this state.
/// @param owner: Object that is entering in this state
override void enter(void* owner);
}
Also add a constructor and destructor to the PongState
’s implementation:
void PongState::constructor()
{
// Always explicitly call the base's constructor
Base::constructor();
}
void PongState::destructor()
{
// Always explicitly call the base's destructor
Base::destructor();
}
When the game is built and run, pressing START on the title screen will now transition to the PongState
, which will show the following.
While the engine remains in the same state, it will call GameState::execute once per game frame. So far, the PongState
doesn’t override the method, nor does it override the exit
method, which is called when the engine’s StateMachine changes to another GameState.
Additionally, the GameState defines the suspend
and resume
methods, which are intented to give the current GameState the opportunity to perform optional tasks for suspending and resuming it, like when pausing and unpausing the game.
To bring some life into our game, let’s make the Disk move next!