What would be the most efficient and modern way of handling states in C++ class?
At the moment I am using multiple bools, but the more I states I add the harder is to maintain. For example if I have a class called VideoPlayer and it has four bools defining different states. If we add set methods for each bool we need to unset all the other bools.
class VideoPlayer
{
public:
void play()
{
play_ = true;
pause_ = false;
stop_ = false;
reset_ = false;
}
void stop()
{
stop_ = true;
play_ = false;
pause_ = false;
reset_ = false;
}
private:
bool play_;
bool pause_;
bool stop_;
bool reset_;
};
Your design suffers from it being easy to be in a bad state (e.g. what if both stop_ and play_ are both true?
You should use an enum to define a set of finite-states of which only 1 can be active at any given point in time.
C++'s enum types are somewhat different from Swift, Java and C#'s enum: they're unscoped and are more permissive with implicit conversions - behaving somewhat similarly to #define.
C++11 adds enum class which is very similar to C#'s enum. There is no built-in functionality similar to the flexibility of Java or Swift's enums, however.
You will want something like this:
enum class PlayerState {
Stopped,
Playing,
Paused
}
If Reset is not a state, but a verb, then it should not be in the enum definition.
class VideoPlayer {
private:
PlayerState state;
public:
VideoPlayer() :
state( PlayerState::Stopped )
{
}
void play() {
switch( this->state ) {
case PlayerState::Stopped:
// start video here, only change `state` when complete.
// you might want to add a "BeginPlaying" (e.g. `LoadingFileFromDisk`) state value too.
this->state = PlayerState.Playing;
break;
case PlayerState::Playing:
throw VideoPlayerStateExeception("Already playing");
case PlayerState::Paused:
this->resume();
break;
}
}
}
I would use C++17's std::variant. States will become types with data.
struct Playing {
float time_from_start;
};
struct Paused {
float time_from_start;
};
struct Stopped {};
struct MediaPlayer {
std::variant<
Playing,
Paused,
Stopped
> state;
void play() {
state = Playing{};
}
void pause() {
state.visit([&](auto&& s){
using S = std::decay_t<decltype(s)>;
float time = 0;
if constexpr (std::is_same_v<S, Playing>) {
time = s.time_from_start;
}
state = Paused{time};
});
}
};
One could even add some other useful states:
struct ABLoop {
float a, b, time_from_a;
};
Related
I have problems finding the right place for an actor and a timer used in a state machine.
I found some inspiration from this site about the state pattern:
State Design Pattern in Modern C++ and created a small example:
Simple door state machine
There might be more transitions possible but I kept it short and simple.
class Door
{
void open() {}
void close() {}
};
Events:
class EventOpenDoor
{
public:
OpenDoor(Door* door) : m_pDoor(door) {}
Door* m_pDoor;
};
class EventOpenDoorTemporary
{
public:
EventOpenDoorTemporary(Door* door) : m_pDoor(door) {}
Door* m_pDoor;
};
class EventOpenDoorTimeout
{
public:
EventOpenDoorTimeout(Door* door) : m_pDoor(door) {}
Door* m_pDoor;
};
class EventCloseDoor
{
public:
EventCloseDoor(Door* door) : m_pDoor(door) {}
Door* m_pDoor;
};
using Event = std::variant<EventOpenDoor,
EventOpenDoorTemporary,
EventOpenDoorTimeout,
EventCloseDoor>;
States:
class StateClosed {};
class StateOpen {};
class StateTemporaryOpen {};
using State = std::variant<StateClosed,
StateOpen,
StateTemporaryOpen>;
Transitions (not complete):
struct Transitions {
std::optional<State> operator()(StateClosed &s, const EventOpenDoor &e) {
if (e.m_pDoor)
{
e.m_pDoor->open();
}
auto newState = StateOpen{};
return newState;
}
std::optional<State> operator()(StateClosed &s, const EventOpenDoorTemporary &e) {
if (e.m_pDoor)
{
e.m_pDoor->open();
**// start timer here?**
}
auto newState = StateOpen{};
return newState;
}
std::optional<State> operator()(StateTemporaryOpen &s, const EventOpenDoorTimeout &e) {
if (e.m_pDoor)
{
e.m_pDoor->close();
}
auto newState = StateOpen{};
return newState;
}
std::optional<State> operator()(StateTemporaryOpen &s, const EventOpenDoor &e) {
if (e.m_pDoor)
{
e.m_pDoor->open();
**// stop active timer here?**
}
auto newState = StateOpen{};
return newState;
}
/* --- default ---------------- */
template <typename State_t, typename Event_t>
std::optional<State> operator()(State_t &s, const Event_t &e) const {
// "Unknown transition!";
return std::nullopt;
}
};
Door controller:
template <typename StateVariant, typename EventVariant, typename Transitions>
class DoorController {
StateVariant m_curr_state;
void dispatch(const EventVariant &Event)
{
std::optional<StateVariant> new_state = visit(Transitions{this}, m_curr_state, Event);
if (new_state)
{
m_curr_state = *move(new_state);
}
}
public:
template <typename... Events>
void handle(Events... e)
{ (dispatch(e), ...); }
void setState(StateVariant s)
{
m_curr_state = s;
}
};
The events can be triggered by a client which holds an instance to the "DoorController"
door_controller->handle(EventOpenDoor{door*});
In the events I pass a pointer to the door itself so it's available in the transitions. The door is operated within the transitons only.
I have problems now with modelling the 20s timeout/timer. Where to have such a timer, which triggers the transition to close the door?
Having a timer within the door instance means, I have a circular dependency, because in case of a timeout it needs to call "handle()" of the "door_controller".
I can break the circular dependency with a forward declarations.
But is there a better solution?
Maybe I have modelled it not well. I'm open to improving suggetions.
Thanks a lot!
This isn't going to be the best answer, but I have more questions than answers.
Some of your choices seem odd. I presume there's a complicated reason why you're storing state based on a variant rather than using an enum class State{}, for instance.
I also get nervous when I see raw pointers in modern C++. I'd feel a whole lot better with smart pointers.
When I've done state machines, the events I can handle always subclass from a common Event class -- or I might even just use a single class and give it as many distinct data fields are required for the things that I need to handle. It's a little odd that you use unrelated classes and depend on a dispatch method. Does that even work? Aren't you pushing objects onto an event queue? How do you end up calling that dispatch method with random objects?
You don't show your event loop, but maybe you have a state machine without an event loop. Is it a state machine then? Or maybe you didn't show it. Maybe you can have a state machine without an event loop, but I thought the two concepts were tied together.
Background:
I come from a C background and just started using C++ for embedded projects.
I have written a single firmware that is able to handle multiple devices, depending on how it is configured via a graphical interface before the end user gets the device.
Each device type is represented by a class.
Many parameters are shared across multiple (but not all) types of devices (i.e. battery level), and some functions as well (i.e. read something from EEPROM which is device-type specific).
Goal:
To be able to access functions and variables through a pointer without having to cast the void pointer constantly.
Shouldn't I know what type of device I am using before calling a function?
Well yes I do, but let's consider the following.
void * _type;
uint8_t deviceType = getDeviceTypeFromEEPROM();
//somewhere at the beginning
switch(deviceType)
{
case DEVICE_A:
_type = new DeviceA();
break;
case DEVICE_B:
_type = new DeviceB();
break;
}
//somewhere during execution.
//I want to avoid having to do something like this. 2 types in example, in reality I have over 10.
//I would prefer not to put a switch and cast every time I need flexibility.
switch(deviceType)
{
case DEVICE_A:
static_cast<DeviceA*>(_type)->readEEPROM();
break;
case DEVICE_B:
static_cast<DeviceB*>(_type)->readEEPROM();
break;
}
Contemplated Solution:
Have a base class and using virtual functions. What about variables? Would I need to have virtual functions to access the variables that reside in the different classes?
Other Solutions:
Open to any alternative solutions.
Here's a sketch of how I might implement an OOP-style solution; nothing fancy, just an enumeration of the different types of parameter your program supports, and an IDevice interface that represents any device that knows how to set or get values of those types. Then you can implement subclasses as necessary for devices with parameters that are specific to that device type:
enum class ParameterType {
BatteryLevel,
Uptime,
SerialNumber,
DeviceAProprietarySetting,
DeviceBProprietarySetting,
// [... add more values here as necessary...]
};
typedef std::variant<int, float, std::string> ParameterValue;
// Abstract interface to any device
// (so you can pass around IDevice* pointers instead of void* pointers)
class IDevice
{
public:
IDevice() {}
virtual ~IDevice() {}
/** Subclass should implement this to write the specified parameter-value
* into (retValue) and return true... or if it cannot (e.g. because it
* doesn't support that particular parameter-value), it should return
* false to indicate failure.
*/
virtual bool GetValue(ParameterType pt, ParameterValue & retValue) const = 0;
/** Subclass should implement this to accept the specified parameter-value
* from (newValue) and return true... or if it cannot (e.g. because it
* doesn't support that particular parameter-value), it should return
* false to indicate failure to set anything.
*/
virtual bool SetValue(ParameterType pt, const ParameterValue & newValue) = 0;
// more virtual methods could be added here, if there are other actions
// that most of your devices support
};
// Since most devices have these parameters, we'll move them up into a shared
// base class to avoid having to reimplement that functionality for every device
class TypicalDevice : public IDevice
{
TypicalDevice() {}
virtual bool GetValue(ParameterType pt, ParameterValue & retValue) const
{
switch(pt)
{
case ParameterType::BatteryLevel: retValue = _batteryLevel; return true;
case ParameterType::Uptime: retValue = _uptime; return true;
case ParameterType::SerialNumber: retValue = _serialNumber; return true;
default: return false; // ParameterType not found!
}
}
virtual bool SetValue(ParameterType pt, const ParameterValue & newValue)
{
switch(pt)
{
case ParameterType::BatteryLevel: _batteryLevel = std::get<float>(newValue); return true;
case ParameterType::Uptime: _uptime; = std::get<int>(newValue) return true;
case ParameterType::SerialNumber: _serialNumber = std::get<std::string>(newValue); return true;
default: return false; // ParameterType not found!
}
}
private:
float _batteryLevel;
int _uptime; // in seconds?
std::string _serialNumber;
};
// Implementation for some specific device-type
class DeviceA : public TypicalDevice
{
DeviceA() {}
virtual bool GetValue(ParameterType pt, std::variant & retValue) const
{
switch(pt)
{
case ParameterType::DeviceAProprietarySetting: retValue = _deviceAProprietarySetting; return true;
default:
return TypicalDevice::GetValue(pt, retValue);
}
}
virtual bool SetValue(ParameterType pt, const std::variant & newValue)
{
switch(pt)
{
case ParameterType::DeviceAProprietarySetting: _deviceAProprietarySetting = std::get<float>(newValue); return true;
default:
return TypicalDevice::GetValue(pt, retValue);
}
}
private:
float _deviceAProprietarySetting;
};
I am building a tile engine in C++. What is the most efficient way to store the logical properties of the individual tiles in the game? I understand the rendering side of the program, but I am having trouble taking a simple id number and turning it into an actual set of properties ( like whether or not a tile is walkable or flammable or can trigger an event, etc. )
One idea is to have a tile object that has the potential to be any kind of tile, and turns on certain boolean "switches" based on the type ( note that the following is mostly just pseudocode and not meant to actually compile):
class Tile
{
private:
int m_type;
bool m_walkable;
// etc...
public:
Tile( int type ) : m_type( type)
{
if( type == 0 )
{
m_walkable = true;
} else if( type == 1 ) {
m_walkable = false;
}
// etc etc would probably be a switch
// statement but you get the idea
}
};
Personally, I do not like this idea; I think it would be much more elegant for each type of tile to have its own data structure. I imagine using some kind of inheritance based system but I just can't seem to put it all together. Fundamentally, I think it should look something like this:
enum class TileType
{
TILE_TYPE null, // 0
TILE_TYPE floor, // 1
TILE_TYPE wall, // 2
// etc ...
};
class BTile
{
private:
// Location and dimensions of tile
int m_xOffset;
int m_yOffset;
int m_width;
int m_height;
// Type of tile, initialized to 0 for base class
TileType m_type;
public:
// ...
};
class Floor : public BTile
{
private:
TileType = 1;
bool walkable = true;
// etc...
};
class Wall : public BTile
{
private:
TileType = 2;
bool walkable = false;
};
Something like this would feel much more organized and flexible, while also allowing me to plug Floor and Wall objects into any kind of function expecting a Tile object. The problem is that I just cannot seem to put this all together in a way that actually works - for example, how can I provide a specific tile type with the tile it is associated with? If I am reading a text file into my program for example, how can I get from 001 to Tile->Floor? Any advice or input on this subject would be greatly appreciated.
Try using a factory method.
The simplest way to do this is to use a switch.
Tile* createTile(TileType tileType) {
switch(tileType) {
case TileType.floor: return new Floor;
case TileType.wall: return new Wall;
}
return nullptr;
}
This is usually not recommended as you have to update the factory each time you add a new type.
Alternatively you could use some trick to register the types to the factory.
One approach is the one described here. There are a lot more strategies.
Why are you reinventing OO? Objects already have a type, no need for TileType. You may want to read up on virtual functions.
There are several approaches here, depending on what exactly you want the tiles in your game to do. You can go the object oriented way, by having distinct classes for the different tile types that you have, or you can go simpler and just have a bitset that represent the different abilities your tiles will have.
This choice will depend on what you want to do.
Bitset only
Oftentimes, the bitset-only variant is enough. To do that you'll need something along those lines:
You most probably want a set of flags which will represent different abilities of your tiles (e.g IsWalkable, IsWater, etc). Something similar to this:
struct TileFlag
{
bool m_IsWalkable : 1;
bool m_IsWater : 1;
//other flags you might need
};
With this in mind, your Tiles would be something along those lines (Texture and Texture manager are there just for the example):
struct Tile
{
void Render();
void Serialize(const boost::property_tree::ptree& tileData)
{
m_Flags.m_IsWalkable = tileData.get<bool>("walkable", false);
m_Flags.m_IsWater = tileData.get<bool>("water", false);
std::string texturePath = tileData.get<std::string>("texturePath", "");
m_TileTexture = TextureManager::GetOrLoad(texturePath);
}
TileFlags m_Flags;
std::shared_ptr<Texture> m_TileTexture;
};
You would need some kind of registry, where you contain all of your tiles, so that your levels can reference the tiles. This registry can be as simple as an std::map.
Some example code:
struct TileRegistry
{
void LoadTiles(const boost::property_tree::ptree& tiles)
{
for (boost::property_tree::ptree::value_type& tileType : tiles.get_child("tileTypes"))
{
std::unique_ptr<Tile> newTile = std::make_unique<Tile>();
newTile->Serialize(tileType.second);
m_Tiles[tileType.first] = std::move(newTile);
}
}
Tile* FindTile(const std::string& tileType)
{
Tile* result = nullptr;
auto search = m_Tiles.find(tileType);
if (search != m_Tiles.end()) {
result = search->second.get();
}
return result;
}
std::map<std::string, std::unique_ptr<Tile>> m_Tiles;
};
Then, when you load your levels, you just search for the Tile Type in the TileRegistry, and you'll get a pointer to your Tile.
OOP Style object inheritance
This approach would borrow a lot from the previous one, with the biggest difference being in how you are going to create your tiles. You are going to need some kind of Factory, as #artcorpse mentions.
If you want to go a bit more generic, you can do some automation magic with a few macros:
struct TileFactory
{
static std::map<std::string, CreateFunctionPtr> m_FactoryFunctors;
std::unique_ptr<ITile> CreateTile(const std::string& tileType)
{
std::unique_ptr<ITile> result;
auto search = m_FactoryFunctors.find(tileType);
if (search != m_FactoryFunctors.end()) {
auto creationFunctionPtr = search->second;
result = creationFunctionPtr(); //Notice the function invocation here
}
return result;
}
};
template<typename T>
struct TileRegistrator
{
TileRegistrator(const std::string& tileTypeName){
TileFactory::m_FactoryFunctors[tileTypeName] = &T::CreateTile;
}
};
#define DECLARE_TILE_TYPE(TileType) \
static std::unique_ptr<ITile> CreateTile() { return std::make_unique<TileType>();} \
static const TileRegistrator<TileType> s_Registrator;
#define DEFINE_TILE_TYPE(TileType) \
const TileRegistrator<TileType> TileType::s_Registrator = {#TileType};
And how you use those macros:
struct ITile
{
virtual ~ITile() = default; //Don't forget a virtual destructor when you have object which can be accessed by pointer to Base!
virtual bool IsWalkable() const = 0;
virtual bool IsSailable() const = 0;
virtual void Serialize(const boost::property_tree::ptree& tileData) = 0;
};
In your .h files, e.g. OceanTile.h:
struct OceanTile : public ITile
{
DECLARE_TILE_TYPE(OceanTile);
bool IsWalkable() const override;
bool IsSailable() const override;
void Serialize(const boost::property_tree::ptree& tileData) override;
int m_WavesIntensity{0};
};
In your .cpp files, e.g. OceanTile.cpp:
DEFINE_TILE_TYPE(OceanTile)
bool OceanTile::IsWalkable() const {
return false;
}
bool OceanTile::IsSailable() const {
return true;
}
void OceanTile::Serialize(const boost::property_tree::ptree& tileData) {
m_WavesIntensity = tileData.get<int>("WavesIntensity", 0);
}
And creating a new tile object, asuming you know its type as a string (e.g. coming from a data file is very simple:
void LoadTiles(const boost::property_tree::ptree& levelData)
{
for (boost::property_tree::ptree::value_type& tile : levelData.get_child("levelTiles"))
{
std::unique_ptr<ITile> newTile = TileFactory::CreateTile(tile->first);
newTile->Serialize(tile.second);
//Do whatever you want to do with your Tile - maybe store it in some vector of all tiles for the level or something
}
}
Disclaimer: I have not compiled or tested any of the above code, but hopefully it can give you an idea on how to get started. There might be any number of bugs hiding there.
I encourage you to start with the Bitset only option, as this is enough for a lot of different types of games, and is much simpler to work with and reason about.
Hope this gives you some start :)
I have a problem. I've written a GPS module that can detect the type of the message on the fly and configure them if needed. I've done it by composition of several classes. To make code a little more independent from the platform (stm32) I created a IStreamDevice interface that has baic i/o operations. It works. Everything appers to be great, but the classs are apparently coupled. That't why I have several question:
How can I avoid the passing IStreamDevice to all devices?
How can I make the whole design more platform-independent (and os-independent)? We have plans to move to another OS in the nearest future. It is POSIX compliant. I think I will be able to implement my IStreamDevice interface there (the buses I can aend up using are UART and SPI. In my current version I use only UART). Am I wrong?
class IStreamDevice
{
public:
virtual ~IStreamDevice() {}
virtual uint32_t read(uint8_t* data, uint32_t size) = 0;
virtual uint32_t write(const uint8_t* data, uint32_t size) = 0;
virtual uint32_t bytesToRead() const = 0;
virtual uint32_t bytesToWrite() const = 0;
};
class GPSModule {
public:
GPSModule(periph::IStreamDevice *source);
~GPSModule();
void turnDevice1Messages();
void turnDevice2Messages();
void configureDevice1(...);
void configureDevice2(...);
void Scan();
private:
Device1Configurator *_device1_configurator;
Device2Configurator *_device2_configurator;
StreamDeviceScanner*_scanner;
periph::IStreamDevice *_source;
};
GPSModule::GPSModule(periph::IStreamDevice *source): _source(source)
{
_scanner= new StreamDeviceScanner(_source);
_device1_configurator = new Device1Configurator(_source);
_device2_configurator = new Device2Configurator(_source);
}
GPSModule::~GPSModule()
{
delete _scanner;
}
void GPSModule::Scan()
{
_scanner->Scan();
}
void GPSModule::turnDevice1Messages() {
_device1_configurator->turnMessages();
}
class StreamDeviceScanner{
public:
StreamDeviceScanner(periph::IStreamDevice *source);
~StreamDeviceScanner();
void Scan();
private:
typedef enum
{
WAITING_SYNC,
WAITING_DEVICE1_MSG,
WAITING_DEVICE2_MSG
} states_t;
periph::IStreamDevice *_source;
ProtocolScanner *_protocol_scanner;
states_t _state;
private:
states_t _catchSync();
uint32_t _read(uint8_t* data, uint32_t length) { return _source->read(data,length); }
uint32_t _bytesToRead() const { return _source->bytesToRead(); }
};
StreamDeviceScanner::StreamDeviceScanner(periph::IStreamDevice *source):
_source(source),
_state(WAITING_SYNC)
{
_protocol_scanner = new ProtocolScanner(source);
}
StreamDeviceScanner::~StreamDeviceScanner()
{
delete _protocol_scanner;
}
void StreamDeviceScanner::Scan()
{
while (_source->bytesToRead()) {
switch (_state)
{
case WAITING_SYNC:
_state = _catchSync();
break;
case WAITING_DEVICE1_MSG:
_protocol_scanner->Device1Scan()
_state = WAITING_SYNC;
break;
case WAITING_DEVICE2_MSG:
_protocol_scanner->Device2Scan()
_state = WAITING_SYNC;
break;
}
}
}
class ProtocolScanner {
private:
Device1Scanner *_Device1Scanner;
Device2Scanner *_Device2Scanner;
public:
ProtocolScanner(periph::IStreamDevice *source)
{
_Device1Scanner = new Device1Scanner(source);
_Device2Scanner = new Device2Scanner(source);
}
~ProtocolScanner()
{
delete _Device1Scanner;
delete _Device1Scanner;
}
bool Device1Scan() const { return _Device1Scanner->Scan(); }
bool Device2Scan() const { return _Device2Scanner->Scan(); }
};
class Device1Scanner {
public:
Device1Scanner(periph::IStreamDevice *source);
~Device1Scanner();
bool Scan();
private:
enum { BUFFER_LENGTH = 8192 };
typedef enum {
Waiting_Header,
Waiting_Payload,
Waiting_Checksum
} state_t;
uint8_t _buffer[BUFFER_LENGTH];
periph::IStreamDevice *_source;
state_t _state;
Device1Parser *_destination;
Device1Scanner::NovatelMessage _message;
private:
uint32_t _read(uint8_t* data, uint32_t size) { return _source->read(data,size); }
const uint32_t _bytesToRead() const { return _source->bytesToRead(); }
bool _receiveHeader();
bool _receivePayload();
bool _receiveChecksum();
bool _validChecksum() const;
};
Device2Scanner looks exactly the same. I'd like to hear everything that anyone has to say about the design.
I don't see any inherent problem with your design. Your IStreamWriter interface seems like a proper abstraction of the underlying bus, without being dependent on specific bus details. That complies with the Dependency Inversion principle and with design-by-contract approach. I also don't see tight coupling in your classes. You're accessing the bus via its handler, according to the interface specification, without dependency on the implementation of the actual bus handling class.
There is nothing platform dependent in the shown code. If the bus handling differs per platform, there is not much you can do except providing a different implementations for IStreamWriter according to platform.
I'm writing a game, and I want to model its different states (the Game Maker analogy would be frames, I guess) in a clean, object-oriented way. Previously, I've done it in the following way:
class Game
{
enum AppStates
{
APP_STARTING,
APP_TITLE,
APP_NEWGAME,
APP_NEWLEVEL,
APP_PLAYING,
APP_PAUSED,
APP_ENDED
};
typedef AppState(Game::*StateFn)();
typedef std::vector<StateFn> StateFnArray;
void Run()
{
// StateFn's to be registered here
AppState lastState(APP_STARTING);
while(lastState != APP_ENDED)
{
lastState = GetCycle_(lastState);
}
// cleanup
}
protected:
// define StateFn's here
AppState GetCycle_(AppState a)
{
// pick StateFn based on passed variable, call it and return its result.
}
StateFnArray states_;
};
This was hardly manageble for a smaller project. All the variables that the states were using were dumped in the Game class, however I'd want to keep object-orientedness to a maximum, only exposing variables that are shared by more than one state. I also want to be able to initialize a new state when switching to it rather than having to do it in the state that's just finishing (as it might have multiple outcomes - APP_PLAYING can switch to APP_PAUSED, APP_GAMEOVER, APP_NEWLEVEL, etc.).
I thought of something like this (CAUTION! FUZZY STUFF!):
struct AppState
{
enum { LAST_STATE = -1; }
typedef int StateID;
typedef std::vector<AppState*> StateArray;
static bool Add(AppState *state, StateID desiredID);
// return false if desiredID is an id already assigned to
static void Execute(StateID state)
{
while(id != LAST_STATE)
{
// bounds check etc.
states_[id]->Execute();
}
}
AppState() {};
virtual ~AppState() {};
virtual StateID Execute() =0; // return the ID for the next state to be executed
protected:
static StageArray stages_;
};
The problem here is that the class and instance levels are getting jumbled up (static vs virtual). The states need to inherit from AppState, but - how I'd imagine - most of them would be classes with all-static members, or, at least I won't need more than one instance from one class (TitleState, LevelIntroState, PlayingState, GameOverState, EndSequenceState, EditorState... - pausing would no longer be a state, rather than being taken care of in the states where it makes sense).
How can it be done elegantly and efficiently?
The following article gives a nice, simple way to manage game states:
http://gamedevgeek.com/tutorials/managing-game-states-in-c/
Basically, you maintain a stack of game states, and just run the top state. You're right that many states would only have one instance, but this isn't really a problem. Actually, though, many of the states you're talking about could have multiple instances. E.g.:
push TitleState
push MenuState
push LevelIntroState
change_to PlayingState
change_to GameOverState
pop (back to MenuState)
... and you can start over with a new instance of LevelIntroState, and so on.
I'm using some type of factory pattern combined with a state pattern in my soon-to-be game.
The code might be a bit messy but I'll try to clean it up.
This is the class you'll derive all states from, like the menu, the game or whatever.
class GameState {
public:
virtual ~GameState() { }
virtual void Logic() = 0;
virtual void Render() = 0;
};
This class will be your interface for handling the different states. You can dynamically add and id dynamically.
class State {
public:
State();
virtual ~State();
void Init();
void Shutdown();
void SetNext( std::string next_state );
void Exit();
bool Logic();
void Render();
protected:
bool Change();
std::string state_id;
std::string next_state;
GameState *current_state;
std::vector<std::string> state_ids;
StateFactory *state_factory;
bool is_init;
};
I'm using a functor to handle the creation of different GameState derivatives.
class BasicStateFunctor {
public:
virtual GameState *operator ()() = 0;
};
template<class T>
class StateFunctor : public BasicStateFunctor {
public:
StateFunctor() { }
GameState *operator ()() {
return new T;
}
typedef T type;
};
Lastly a factory which will store and manage the different states.
class StateFactory {
public:
StateFactory();
virtual ~StateFactory();
bool CheckState( std::string id );
GameState *GetState( std::string id );
template<class T> void AddState( std::string id );
private:
typedef std::map<std::string, BasicStateFunctor*>::iterator StateIt;
std::map<std::string, BasicStateFunctor*> state_map;
};
In your definition file:
Here I did leave out a lot of stuff, but hopefully you'll get the idea.
bool StateFactory::CheckState( std::string id )
{
StateIt it = state_map.find( id );
if( it != state_map.end() )
return true;
else
return false;
}
GameState *StateFactory::GetState( std::string id )
{
StateIt it = state_map.find( id );
if( it != state_map.end() )
{
return (*(*it).second)();
} else {
//handle error here
}
template<class T> void StateFactory::AddState( std::string id )
{
StateFunctor<T> *f = new StateFunctor<T>();
state_map.insert( state_map.end(), std::make_pair( id, f ) );
}
void State::Init()
{
state_factory = new StateFactory();
state_factory->AddState<Game>( "game" );
current_state = state_factory->GetState( "game" );
is_init = true;
}
void State::SetNext( std::string new_state )
{
//if the user doesn't want to exit
if( next_state != "exit" ) {
next_state = new_state;
}
}
bool State::Change()
{
//if the state needs to be changed
if( next_state != "" && next_state != "exit" )
{
//if we're not about to exit( destructor will call delete on current_state ),
//call destructor if it's a valid new state
if( next_state != "exit" && state_factory->CheckState( next_state ) )
{
delete current_state;
current_state = state_factory->GetState( next_state );
}
else if( next_state == "exit" )
{
return true;
}
state_id = next_state;
//set NULL so state doesn't have to be changed
next_state = "";
}
return false;
}
bool State::Logic()
{
current_state->Logic();
return Change();
}
And here's how you use it:
Initialize and add the different states, I'm doing it in the Init().
State.Init();
//remember, here's the Init() code:
state_factory = new StateFactory();
state_factory->AddState<Game>( "game" );
current_state = state_factory->GetState( "game" );
is_init = true;
For the frame function
State.Logic(); //Here I'm returning true when I want to quit
And for the rendering function
State.Render();
This may not be perfect but it works fine for me.
To further advance the design you'd want to add Singleton for State and maybe make the StateFactory as a hidden class inside State.
Here is my solution:
Every state is like a small game, so I manage a set of games on a stack.
Events bubble the stack up until someone stops them (so "games" further up don't see them anymore). This allows me to zoom the map via plus/minus while in a menu. OTOH, Esc stops the bubbling early since the first open menu swallows it.
Each "game" on the stack has the same methods: handleUserEvent(), keyDown(), keyUp(), mousePressed(), mouseReleased(), mouseMotion(), update() (internal calculations before rendering), draw() (rendering), prepare() (optimize rendering by caching something in a texture that's just stamped on the target surface in draw())
For rendering, I'm using layers with priorities. So each game will render on a transparent canvas and the layer renderer will render them in the correct order. This way, each game can update its own layer without bothering what everyone else is doing.
I use a Game State manager with a list of GameStates, where each Item in the list is a "GameState Object" that implements IGameState and has two methods .render() and .HandleInput()
This GameStateManager is implemented as a singleton so any state can jump to any another state by calling
GameStateManager.gi().setState("main menu")
And the main loop looks something like this
while(isRunning)
{
GameStateManager.gi().getCurrentState().handleKeyboard(keysobject);
GameStateManager.gi().getCurrentState().handleMouse(mouseobject);
GameStateManager.gi().getCurrentState().render(screenobject);
}
That way to create states, just create a new class that implements IGameState, and add it to the GameStateManager.
(Note: This is a really handy way to make mini-games within your main game)