I use a series of state machines in my WIP in C++.
It would help to see your code.
Part of the trick in Tier 2 is understanding a key difference in how the Sync() command works in Tier 2 vs Tier 1. In Tier 1 you can use the command anywhere and it will process all input (user/keyboard/device) and output. In Tier 2, inputs are grabbed only once at either the beginning or end of the app::Loop() method (I don't know which). Outside of the app::Loop() the Sync() command only updates display stuff and physics.
This is what my template.cpp file looks like:
// Includes, namespace and prototypes
#include "template.h"
#include "ta_pointer.h"
#include "ta_oops.h"
#include "ta_display.h"
#include "the_player.h"
#include "the_globals.h"
#include "game_functions.h"
#include "ta_debug_console.h"
using namespace AGK;
app App;
/////////////////////////////////////////
// CONSTANTS ////////////////////////////
/////////////////////////////////////////
// game states
#define _AS_DISPLAY_INIT_ 1
#define _AS_MAIN_INIT_ 2
#define _AS_MAIN_PLAY_ 3
#define _AS_MAIN_DONE_ 4
#define _AS_BAD_STATE_ 5
// indicate whether using DebugConsole
#define _USE_DEBUG_CONSOLE_ false
/////////////////////////////////////////
// PROTOTYPES ///////////////////////////
/////////////////////////////////////////
#ifdef IDE_ANDROID
extern void exit(int);
#endif
/////////////////////////////////////////
// CLASS IMPLEMENTATION /////////////////
/////////////////////////////////////////
// Begin app, called once at the start
void app::Begin( void )
{
// initialise the debug console
if (_USE_DEBUG_CONSOLE_) TADebugConsole::init_console("HelpYeeYeeGetHome","com.triassicassociates.helpyeeyeegethome.syslog");
// assume okay for now
did_end = false;
app_state = _AS_DISPLAY_INIT_;
app_err_msg = "";
// set the platform data
TADisplay::setPlatformInfo(false,false);
// do startup function
if (initialiseTheApp(WINDOW_TITLE)) hadAnAppError("Failed to initialise properly:");
}
// Main loop, called every frame
void app::Loop ( void )
{
// check for ended
if (app::did_end) return;
// get seconds per frame and frames per second
g_frametime = agk::GetFrameTime();
g_fps = agk::ScreenFPS();
// figure out the factor to multiply speeds by
g_fixspeed = g_frametime / g_targettime;
TADisplay::printAGKmessage("\nFPS:%0.1f",g_fps);
// process based on state
switch (app_state)
{
case _AS_MAIN_PLAY_:
// handle the main processing stuff
switch (mainProcessingLoop())
{
case 0:
// do the physics, done this way so that we can control
// pausing and restarting
the_player::step_physics(g_fixspeed);
break;
case 1:
// player pressed 'Exit'
// set new state
app_state = _AS_MAIN_DONE_;
break;
case 2:
// an error occurred while starting a game
hadAnAppError("Game did not start properly:");
break;
case 3:
// an error occured while initialising options
hadAnAppError("Failed to initialise options:");
break;
}
break;
case _AS_MAIN_DONE_:
// we want to make sure everything is cleared away
endTheGame();
// close down
closeThisApp();
// done, we want to make sure we skip the Sync() call
return;
case _AS_BAD_STATE_:
// display message and done
inBadState();
break;
case _AS_MAIN_INIT_:
// do initialisation
if (initialiseTheGame()) hadAnAppError("Failed to initialise properly:");
else app_state = _AS_MAIN_PLAY_;
break;
case _AS_DISPLAY_INIT_:
// we want to make sure that the display syncs right now
// so that our splash screen displays and so that
// our display is correctly 'formatted'
// before we start initialisation
agk::Sync();
agk::Sync();
// start actual game initialisation in the next cycle
app_state = _AS_MAIN_INIT_;
break;
}
// sync display
agk::Sync();
}
// Called when the app ends
void app::End ( void )
{
// clean up, close debug output
TADebugConsole::close_console();
}
void app::closeThisApp(bool noforce/*=true*/)
{
// if in debug mode we want to leave the consoles displayed
if (noforce&&_USE_DEBUG_CONSOLE_)
{
// do a couple of sync calls to clear inputs
agk::Sync();
agk::Sync();
// indicate 'error', but it won't be visible
hadAnAppError("Shutdown functions done.\nWaiting so that events can be\nviewed in DebugConsole.");
return;
}
// indicate done
app::did_end = true;
// completely exit the app
#ifdef AGKWINDOWS
PostQuitMessage(0);
#endif
#ifdef AGKIOS
// forcing a quit in iOS is against recommended guidelines - use HOME button
// the exit button is disabled on AGKIOS builds
// but if you want to do so, this is the code
agk::MasterReset();
exit(0);
#endif
#ifdef IDE_ANDROID
// similar to iOS, an exit button should not be done
// but if you want to do so, this is the code
agk::MasterReset();
exit(0);
#endif
#ifdef IDE_MAC
glfwCloseWindow();
#endif
}
void app::checkForExit()
{
// if pointer pressed, we are done
if (TAPointer::gotPress()) closeThisApp(false);
}
void app::hadAnAppError(const char* errmsg)
{
// save the message
app_err_msg = "\n\n\n";
app_err_msg += errmsg;
app_err_msg += "\n\n";
app_err_msg += ta_oops::get_the_oops();
app_err_msg += "\n\n\nTouch anywhere to exit";
// save the state as bad
app_state = _AS_BAD_STATE_;
// show a message
agk::Print(app_err_msg.c_str());
}
void app::inBadState()
{
// check for exit
app::checkForExit();
// show a message
agk::Print(app_err_msg.c_str());
}
As you might notice, this is set up to compile in multiple environments.
In the _AS_MAIN_PLAY_ state, the function mainProcessingLoop() is called to get the state for the game playing. mainProcessingLoop() is defined in my game_functions.cpp file (which is 2495 lines long, so I am not posting that here).
I am attaching a simple state machine demo I did a while back. It doesn't include the AppGameKit core files, just the ones that I wrote for the demo. It shows a simplified version of what I do in my WIP.
Cheers,
Ancient Lady