Sign in to follow this  

GameState is a bad method nowadays ?

This topic is 825 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi,

I'm still using game state to have different state like menu and in-game for example.

The modern way is to use only scene and code actor to do something for this actual scene.

Is it a bad method nowadays to use game state ?

Thanks

Edited by Alundra

Share this post


Link to post
Share on other sites

Generally speaking it is a bad idea because you may want to be making use of multiple states at the same time. Imagine a game where you can drop down a console to read logging info and enter commands while the game is running. Perhaps it could even have a game menu opened at the same time on top of all that other stuff. A good example of this is Valve's Source engine. Team Fortress 2 can have the game running while a console, title screen, server browser and options window are all open at the same time, and that's not the end of it.

Share this post


Link to post
Share on other sites

Threads are unrelated to states.

 

Not necessarily. I imagine a main loop that iterates over multiple states could potentially hang in certain circumstances and this could be avoided by associating a thread with each particular state.

 

My thoughts being said, could you elaborate on what you mean by that?

Share this post


Link to post
Share on other sites

Threads are unrelated to states.

Not necessarily. I imagine a main loop that iterates over multiple states could potentially hang in certain circumstances and this could be avoided by associating a thread with each particular state.

 

My thoughts being said, could you elaborate on what you mean by that?

You use state machines at places where you can do several things, and you need to keep track what you are doing right now. You always do one thing at a time with a state machine. A state machine is not active by itself, it's just a value in memory somewhere, representing "this is what you are doing now".

 

For example, an NPC can walk, sit, and attack. It can do all these things, but only one thing at a time. Depending on the state, you display a different sprite.

 

Another example is in the game loop, where you have one game loop to display the main menu, the high score, and the game display. You can display all these things, but always one thing at a time. A use of the latter can be key handling. Suppose I press "x". If I do that in the main menu, the program should exit. If I press "x" in the game, I should go down a level. In the key handler you get the state of the game to decide what to do.

 

A thread is "a point of execution", statements are being executed, decisions are being made, things are being done. You play music, display graphics, compute next action of the NPC. You use state machines to see where you left off the last time. "oh, this NPC is attacking, but nearly dead. Perhaps he should hide instead?", and you change the state of the NPC to "finding a place to hide and rest". The next time you visit the NPC routine, you move it towards safety, as its state told you that is what it should do.

 

In short, threads are "actors". They run, and move the game forward, playing music, computing next move of the NPC, etc. State machines are used to keep track of what you were doing, so a thread can jump in at the right spot, and decide the next move to make.

 

 

Technically, a state machine is anything with a state of its own, it remembers what it is "doing" now. Even a single variable can be seen as a state machine (it keeps track of its value right now, and it can have only one value at a time). In general however, the term "state machine" is used for more elaborate structures. A NPC state machine for example. An NPC can be sitting, standing, attacking, etc. Attached to these states ("modes") is a whole set of other behaviours. Display of graphics, movement, decision making of how to go from here, interaction with player or other NPCs, etc.

Without a coordinated idea of the state of the NPC, it's quite impossible to make it look convincing.

 

 

In the design of a game, you also use state machines. In that case, you are not worried about "what am I doing now" (since there is no "now" while designing), but more about "what can an NPC do, what can happen, and how to react".

The states of a state machine act as a structuring mechanism here, you can organize things by state, and reason about each state separately, fleshing out the details. You can also drop all details of each state, and reason about changing states, or overall behaviour through time.

Share this post


Link to post
Share on other sites
This is deprecated code from my library (i.e. several years old and simply scrapped), but it might be useful to someone:
 
state.hpp
/* BDAPI - Brain Damage API - Version 0.2.7.0 */
#ifndef BDAPI_UTILITY_STATE_HPP
#define BDAPI_UTILITY_STATE_HPP
#include "bdapi.hpp"

/* includes */

// standard
#include <deque>

// bdapi
#include "utility/container.hpp"

// namespaces
namespace bdapi   {
namespace utility {

/* state class */

class state
{
	result (*function_initialize)() = NULL;
	result (*function_resume    )() = NULL;
	result (*function_iterate   )() = NULL;
	result (*function_suspend   )() = NULL;
	result (*function_kill      )() = NULL;

	string name;

	bl initialized = false;
	bl active      = false;

	s64 initialize_count = 0;
	s64 resume_count     = 0;
	s64 iterate_count    = 0;
	s64 suspend_count    = 0;
	s64 kill_count       = 0;

public: // get

	inline const string& get_name          () const { return name; };

	inline const bl& is_initialized        () const { return initialized; };
	inline const bl& is_active             () const { return active;      };

	inline const s64& get_initialize_count () const { return initialize_count; };
	inline const s64& get_resume_count     () const { return resume_count;     };
	inline const s64& get_iterate_count    () const { return iterate_count;    };
	inline const s64& get_suspend_count    () const { return suspend_count;    };
	inline const s64& get_kill_count       () const { return kill_count;       };

public: // initialization

	state () {};
	state (
		string name,
		result(*function_initialize)(),
		result(*function_resume)(),
		result(*function_iterate)(),
		result(*function_suspend)(),
		result(*function_kill)() );
	state ( const state& copy );
 ~state () {};

	result initialize (
		string name,
		result(*function_initialize)(),
		result(*function_resume)(),
		result(*function_iterate)(),
		result(*function_suspend)(),
		result(*function_kill)() );

public: // core

	result do_initialize ();
	result do_resume     ();
	result do_iterate    ();
	result do_suspend    ();
	result do_kill       ();

};

/* state handler class */

class state_handler
{
	typedef container< state*, std::deque > state_deque;

	state_deque states;

	state* current_state = NULL;

public: // get

	inline const state_deque& get_states () const { return states; };

	inline state* get_current_state      () const { return current_state; };

public: // initialization

	state_handler () {};
	state_handler ( state* initial_state      );
	state_handler ( const state_handler& copy );
 ~state_handler () {};

	result initialize ( state* initial_state );

public: // core

	result do_initialize ();
	result do_resume     ();
	result do_iterate    ();
	result do_suspend    ();
	result do_kill       ();

	result change_state ( state* new_state );
	result end_state    (                  );

public: // query

	string get_name (       ) const;
	string get_name ( s32 i ) const;

	s64 get_initialize_count (       ) const;
	s64 get_initialize_count ( s32 i ) const;
	s64 get_resume_count     (       ) const;
	s64 get_resume_count     ( s32 i ) const;
	s64 get_iterate_count    (       ) const;
	s64 get_iterate_count    ( s32 i ) const;
	s64 get_suspend_count    (       ) const;
	s64 get_suspend_count    ( s32 i ) const;
	s64 get_kill_count       (       ) const;
	s64 get_kill_count       ( s32 i ) const;

	s32 get_size () const;

	bl is_initialized (       ) const;
	bl is_initialized ( s32 i ) const;
	bl is_active      (       ) const;
	bl is_active      ( s32 i ) const;

	bl is_empty () const;
};

/* end */

}
}

#endif

state.cpp
/* BDAPI - Brain Damage API - Version 0.2.7.0 */
#ifndef BDAPI_UTILITY_STATE_UTILITY_CPP
#define BDAPI_UTILITY_STATE_UTILITY_CPP
#include "utility/state.hpp"

/* includes */

// namespaces
namespace bdapi   {
namespace utility {

/* state class */

// constructors
state::state( string name, result(*function_initialize)(), result(*function_resume)(),
result(*function_iterate)(), result(*function_suspend)(), result(*function_kill)() )
{
	initialize( name, function_initialize, function_resume, function_iterate, function_suspend,
		function_kill );
}
state::state( const state& copy )
{
	function_initialize = copy.function_initialize;
	function_resume     = copy.function_resume;
	function_iterate    = copy.function_iterate;
	function_suspend    = copy.function_suspend;
	function_kill       = copy.function_kill;

	name = copy.name;

	initialized = copy.initialized;
	active      = copy.active;

	initialize_count = copy.initialize_count;
	resume_count     = copy.resume_count;
	iterate_count    = copy.iterate_count;
	suspend_count    = copy.suspend_count;
	kill_count       = copy.kill_count;
}

// initializers
result state::initialize( string name, result(*function_initialize)(), result(*function_resume)(),
result(*function_iterate)(), result(*function_suspend)(), result(*function_kill)() )
{
	this->function_initialize = function_initialize;
	this->function_resume     = function_resume;
	this->function_iterate    = function_iterate;
	this->function_suspend    = function_suspend;
	this->function_kill       = function_kill;

	this->name = name;

	return 1;
}

// do initialize
result state::do_initialize()
{
	if( !initialized )
	{
		result initialize_result = (*function_initialize)();

		if( !initialize_result )
		{
			BD_WRITE_ERROR( "Failed to initialize state ~FE\"%s\" "
				"~F8[~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8]",
				name.c_str(), initialize_count, resume_count, iterate_count, suspend_count, kill_count );

			return 0;
		}
		else
		{
			initialize_count++;

			initialized = true;

			return do_resume();
		}
	}

	BD_WRITE_ERROR( "Failed to initialize state ~FE\"%s\" "
		"~F8[~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8] "
		"~F7as it has already been initialized",
		name.c_str(), initialize_count, resume_count, iterate_count, suspend_count, kill_count );

	return 0;
}

// do resume
result state::do_resume()
{
	if( !initialized )
	{
		BD_WRITE_ERROR( "Failed to resume state ~FE\"%s\" "
			"~F8[~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8] "
			"~F7as it has not been initialized",
			name.c_str(), initialize_count, resume_count, iterate_count, suspend_count, kill_count );

		return 0;
	}

	if( !active )
	{
		result resume_result = (*function_resume)();

		if( !resume_result )
		{
			BD_WRITE_ERROR( "Failed to resume state ~FE\"%s\" "
				"~F8[~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8]",
				name.c_str(), initialize_count, resume_count, iterate_count, suspend_count, kill_count );

			return 0;
		}
		else
		{
			resume_count++;

			active = true;

			return resume_result;
		}
	}

	BD_WRITE_ERROR( "Failed to resume state ~FE\"%s\" "
		"~F8[~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8] "
		"~F7as it is active",
		name.c_str(), initialize_count, resume_count, iterate_count, suspend_count, kill_count );

	return 0;
}

// do iterate
result state::do_iterate()
{
	if( !initialized )
	{
		BD_WRITE_ERROR( "Failed to iterate state ~FE\"%s\" "
			"~F8[~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8] "
			"~F7as it has not been initialized",
			name.c_str(), initialize_count, resume_count, iterate_count, suspend_count, kill_count );

		return 0;
	}

	if( !active )
	{
		BD_WRITE_ERROR( "Failed to iterate state ~FE\"%s\" "
			"~F8[~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8] "
			"~F7as it inactive",
			name.c_str(), initialize_count, resume_count, iterate_count, suspend_count, kill_count );

		return 0;
	}

	result iterate_result = (*function_iterate)();

	if( !iterate_result )
	{
		BD_WRITE_ERROR( "Failed to iterate state ~FE\"%s\" "
			"~F8[~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8]",
			name.c_str(), initialize_count, resume_count, iterate_count, suspend_count, kill_count );

		return 0;
	}
	else
	{
		iterate_count++;

		return iterate_result;
	}
}

// do suspend
result state::do_suspend()
{
	if( !initialized )
	{
		BD_WRITE_ERROR( "Failed to suspend state ~FE\"%s\" "
			"~F8[~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8] "
			"~F7as it has not been initialized",
			name.c_str(), initialize_count, resume_count, iterate_count, suspend_count, kill_count );

		return 0;
	}

	if( !active )
	{
		BD_WRITE_ERROR( "Failed to suspend state ~FE\"%s\" "
			"~F8[~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8] "
			"~F7as it inactive",
			name.c_str(), initialize_count, resume_count, iterate_count, suspend_count, kill_count );

		return 0;
	}

	result suspend_result = (*function_suspend)();

	if( !suspend_result )
	{
		BD_WRITE_ERROR( "Failed to suspend state ~FE\"%s\" "
			"~F8[~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8]",
			name.c_str(), initialize_count, resume_count, iterate_count, suspend_count, kill_count );

		return 0;
	}
	else
	{
		suspend_count++;

		active = false;

		return suspend_result;
	}
}

// do kill
result state::do_kill()
{
	if( !initialized )
	{
		BD_WRITE_ERROR( "Failed to terminate state ~FE\"%s\" "
			"~F8[~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8] "
			"~F7as it has not been initialized",
			name.c_str(), initialize_count, resume_count, iterate_count, suspend_count, kill_count );

		return 0;
	}

	if( !do_suspend() )
	{
		BD_WRITE_ERROR( "Failed to terminate state ~FE\"%s\" "
			"~F8[~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8] "
			"~F7as it is active",
			name.c_str(), initialize_count, resume_count, iterate_count, suspend_count, kill_count );

		return 0;
	}

	result result_kill = (*function_kill)();

	if( !result_kill )
	{
		BD_WRITE_ERROR( "Failed to terminate state ~FE\"%s\" "
			"~F8[~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8.~FF%i~F8]",
			name.c_str(), initialize_count, resume_count, iterate_count, suspend_count, kill_count );

		return 0;
	}
	else
	{
		kill_count++;

		initialized = false;
		active      = false;

		return result_kill;
	}
}

/* state handler class */

// constructors
state_handler::state_handler( state* initial_state )
{
	initialize( initial_state );
}
state_handler::state_handler( const state_handler& copy )
{
	states = copy.states;

	current_state = copy.current_state;
}

// initializers
result state_handler::initialize( state* initial_state )
{
	if( !initial_state )
	{
		BD_WRITE_ERROR( "Initial state has not been allocated" );

		return 0;
	}

	states.insert( initial_state );

	current_state = states.get_back();

	return 1;
}

// do initialize
result state_handler::do_initialize()
{
	if( !current_state )
	{
		BD_WRITE_ERROR( "Initial state has not been allocated" );

		return 0;
	}

	return current_state->do_initialize();
}

// do resume
result state_handler::do_resume()
{
	if( !current_state )
	{
		BD_WRITE_ERROR( "Initial state has not been allocated" );

		return 0;
	}

	return current_state->do_resume();
}

// do iterate
result state_handler::do_iterate()
{
	if( !current_state )
	{
		BD_WRITE_ERROR( "Initial state has not been allocated" );

		return 0;
	}

	return current_state->do_iterate();
}

// do suspend
result state_handler::do_suspend()
{
	if( !current_state )
	{
		BD_WRITE_ERROR( "Initial state has not been allocated" );

		return 0;
	}

	return current_state->do_suspend();
}

// do kill
result state_handler::do_kill()
{
	if( !current_state )
	{
		BD_WRITE_ERROR( "Initial state has not been allocated" );

		return 0;
	}

	return current_state->do_kill();
}

// change state
result state_handler::change_state( state* new_state )
{
	if( !new_state )
	{
		BD_WRITE_ERROR( "New state has not been allocated" );

		return 0;
	}

	if( !is_empty() )
	{
		if( !do_suspend() )
		{
			BD_WRITE_ERROR( "Failed to suspend current state" );

			return 0;
		}

		for_i( states )
		{
			if( new_state == *i )
			{
				states.insert( new_state );

				current_state = states.get_back();

				return do_resume();
			}
		}
	}

	states.insert( new_state );

	current_state = states.get_back();

	return do_initialize();
}

// end state
result state_handler::end_state()
{
	if( is_empty() )
	{
		BD_WRITE_ERROR( "State handler is empty" );

		return 0;
	}

	if( !do_kill() )
	{
		BD_WRITE_ERROR( "Failed to terminate current state" );

		return 0;
	}

	states.pop_back();

	if( is_empty() )
	{
		current_state = NULL;

		return 1;
	}

	while( !states.get_back()->is_initialized() )
	{
		states.pop_back();

		if( is_empty() )
		{
			current_state = NULL;

			return 1;
		}
	}

	current_state = states.get_back();

	return do_resume();
}

// get name
string state_handler::get_name() const
{
	if( !current_state )
	{
		return "";
	}
	else
	{
		return current_state->get_name();
	}
}
string state_handler::get_name( s32 i ) const
{
	if( is_empty() )
	{
		return "";
	}
	else
	{
		return states[i]->get_name();
	}
}

// get initialize count
s64 state_handler::get_initialize_count() const
{
	if( !current_state )
	{
		return -1;
	}
	else
	{
		return current_state->get_initialize_count();
	}
}
s64 state_handler::get_initialize_count( s32 i ) const
{
	if( is_empty() )
	{
		return -1;
	}
	else
	{
		return states[i]->get_initialize_count();
	}
}

// get resume count
s64 state_handler::get_resume_count() const
{
	if( !current_state )
	{
		return -1;
	}
	else
	{
		return current_state->get_resume_count();
	}
}
s64 state_handler::get_resume_count( s32 i ) const
{
	if( is_empty() )
	{
		return -1;
	}
	else
	{
		return states[i]->get_resume_count();
	}
}

// get iterate count
s64 state_handler::get_iterate_count() const
{
	if( !current_state )
	{
		return -1;
	}
	else
	{
		return current_state->get_iterate_count();
	}
}
s64 state_handler::get_iterate_count( s32 i ) const
{
	if( is_empty() )
	{
		return -1;
	}
	else
	{
		return states[i]->get_iterate_count();
	}
}

// get suspend count
s64 state_handler::get_suspend_count() const
{
	if( !current_state )
	{
		return -1;
	}
	else
	{
		return current_state->get_suspend_count();
	}
}
s64 state_handler::get_suspend_count( s32 i ) const
{
	if( is_empty() )
	{
		return -1;
	}
	else
	{
		return states[i]->get_suspend_count();
	}
}

// get kill count
s64 state_handler::get_kill_count() const
{
	if( !current_state )
	{
		return -1;
	}
	else
	{
		return current_state->get_kill_count();
	}
}
s64 state_handler::get_kill_count( s32 i ) const
{
	if( is_empty() )
	{
		return -1;
	}
	else
	{
		return states[i]->get_kill_count();
	}
}

// get size
s32 state_handler::get_size() const
{
	return states.get_size();
}

// is initialized
bl state_handler::is_initialized() const
{
	if( !current_state )
	{
		return false;
	}
	else
	{
		return current_state->is_initialized();
	}
}
bl state_handler::is_initialized( s32 i ) const
{
	if( is_empty() )
	{
		return false;
	}
	else
	{
		return states[i]->is_initialized();
	}
}

// is active
bl state_handler::is_active() const
{
	if( !current_state )
	{
		return false;
	}
	else
	{
		return current_state->is_active();
	}
}
bl state_handler::is_active( s32 i ) const
{
	if( is_empty() )
	{
		return false;
	}
	else
	{
		return states[i]->is_active();
	}
}

// is empty
bl state_handler::is_empty() const
{
	return states.is_empty();
}

/* end */

}
}

#endif

I am aware that my younger coding style causes offense to some programmers, but please keep in mind that my framework no longer uses this code at all. Even though this code commits many cardinal sins against coding etiquette, it remains more or less comprehensible (I think). Edited by sigvatr

Share this post


Link to post
Share on other sites

i use implicit call hierarchy and state variables to control program flow. 

 

so runprog runs the main game menu (continue, tutorial, new, load, help, quit) which in turn calls rungame (the main game loop). so those "state changes" are already handled implicitly by call order. but the main game loop can be in FPS mode, or SIMs-style do-an-action mode, which is indicated by a state variable. FPS and action modes each have their own render, input, and update routines. but the popup in-game menu for example is simply called by the input handler, its not a separate "state". 

 

i tend to use states when something needs to run in more than one mode, with different input, render, and/or update methods for its various modes.

 

but when the state transitions (control flow) are simply "call subroutine and return control to caller when done", implicit call hierarchy handles that automatically without the need for state variables.   same idea for call sub1, then sub2, then sub3. call order implicitly defines the state transitions (control flow) from sub1 to sub2 to sub3.

 

a good acid test might be "is control flow variable?" if yes, then a state variable may be called for, if not, odds are its overkill.

 

note that almost any program could be written entirely as states - its a general method for abstract handing of control flow in a program. but just the same way that everything in a game should not necessarily be an object, everything in a game should not necessarily be a "game state".

 

when i think of "game state", i think of total war's strategic map mode, vs it's tactical battle engine. now that's an example of a game with two true "game states". or like FPSRPG mode and TheSIMs mode in Caveman v3.0.

 

multiple simultaneous state variables (a variation on hierarchical finite state machines) can be very useful for things like the multi-state editing in unity. I use them a lot in AI programming.   the advantage is that you can be in more than one state at once - i'm sitting _and_ talking, etc. or i'm in FPS mode, and location = cave | cavern | outside | upatree (all of which have different render, input, and/or update methods).

Edited by Norman Barrows

Share this post


Link to post
Share on other sites

I use gamestates to separate game code, but I also like to use flags that can also activate code within a state incase I need to use it alongside another state. IE, drawing a GUI menu on top of the game itself

Share this post


Link to post
Share on other sites

I took the idea from cocos2D: my engine run states that are exclusive (one at a time) that runs layers (n at a time). So I can have layers like HUD, inGame, PauseMenu, and just add/remove than from the current state (MainMenu, CurrentLevel) and they will work on theyr own.

Share this post


Link to post
Share on other sites

I think part of OP's misconception is simply terminology.

 

In a scenegraph, scene means something entirely different than a state -- but Unity's concept of a scene is more akin to a state (though it contains a scenegraph). But if you have a scenegraph style 'scene', sure, go ahead and create a gameobject/actor/bob to act as your menu or whatever.

 

I tend to prefer a more structured state-machine approach (really, a stack of state machines) for myself, but I could see how certain styles of game (e.g. those with UI mapped to in-game geometry) interaction map better from the gameobject/actor approach.

Share this post


Link to post
Share on other sites

This topic is 825 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this