An efficiant game loop

Started by
38 comments, last by Genjix 19 years, 2 months ago
So far, I've been using the standard noob game loop. My loops look like this:


while(gamerunning)
{

    switch(gamestate)
    {

        case TITLE:
             // do title stuff.
             break;

        case GAME:
             // do game stuff.
             break;

        case OVER:
             // do ending stuff.
             break;

    }

}


It works, but I realize it's not the most efficiant method. I was thinking of using linked list for game loops. I'm interested in how you guys handle the game loop. Some sample code would be appreciated. Thanks!
-----------------------------Play Stompy's Revenge! Now!
Advertisement
I think a good node for a game state link list would be a struct like this:

typedef struct GAME_STATE{    void (*handlefunc)(void);    GAME_STATE *last, *next;} GAME_STATE;


this would allow for less conditional statements.
-----------------------------Play Stompy's Revenge! Now!
I've implemented a basic kernel system, where you add tasks you want to run. So I simply implement any system that needs to run continuously as a task, add it to the kernel, and I'm set. I also implemented priorities and the ability to stop, restart, pause, and resume tasks, which makes it easy to turn things on and off. So your main game loop turns into something like this:
while(kernel->Run() == false) /* returns true when all tasks done */{  // Any additional processing here}

Tasks also implement a Run method, so when that returns true the kernel puts the task in a special stop list (doesn't remove it for efficiency in case another agent wants to restart the task). As long as there are tasks that are either running or suspended, the kernel runs.
That makes sense. I'll take this method into consideration.

In simple games like Pong, it is okay to use a switch statement. However, large projects like RPGs have so many states that this process would make no logical sense.

That is why I'm interested in hearing everybody's take on the subject. It is something that can be done in many different ways. By hearing theories and examples on the subject, I can formulate my own method.

and if all else fails...I can just take one of your methods.[grin]

-----------------------------Play Stompy's Revenge! Now!
Another good thing about the kernel setup is that you can also chose to derive a special sub-kernel object, which is a task itself. That way, you can add sub-kernels as tasks to parent kernels, and right away you've got yourself robust, nested task loops. Any process that requires a loop, turn into a task, add it to a kernel/sub-kernel, and the whole thing runs like a charm.

This is actually what many developers do in large games, simply because it's an easy, consistent setup that works well and cuts down on large loops and switch statements. And you can encapsulate data for each task, which makes everyone smile. I personally like to see it as the equivalent of functors [function objects], for loops. Looptors anyone? [smile]
I did mine entirely differently. I have a class Scene, which is basically the root node of my scene graph. This node can have a script attached to it, like every other node in my scene graph. The script bound to the Scene node acts as the overall management script for the scene, meaning I'd load "title.nsg", "level1.nsg", "menu.nsg", or whatever, and each one of those can have its own script to define its behaviour. They can share scripts of course, so each level can use the same script to manage the in-game behaviour. This decentralises my game engine, and puts total control in the hands of the scripts. As I have implemented scripts using actual compiled dll's (with the possible option of incorporating an interpreted script langauge at a later date, allowing you to use either), there isn't a performance hit.

It's a little hard to explain properly without explaining the entire core architecture of my engine.
Wow, that's awesome nemesis![wow]
-----------------------------Play Stompy's Revenge! Now!
As we're all sharing - here's my current implementation (although the code still needs cleaning up). Uses the factory method and a stack of states.

First the statemanager factory .h file:
#pragma once#include <stack>#include <map>#include <string>#include <boost/shared_ptr.hpp>#include <singleton.h>class State;class SingletonStateManager{public:// Init and de-init functions	SingletonStateManager(void);	~SingletonStateManager(void);	void Init();	void Kill();	typedef boost::shared_ptr<State> (*CreateNewStateCallback)();	bool RegisterState(std::string stateName, CreateNewStateCallback newStateFn);// Utility functions	void PushState(std::string whichState);	void PopState();	void Update();// Accessors and mutators	void Exiting() {exiting_ = true;}	bool IsExiting() const {return exiting_;}	boost::shared_ptr<State> GetCurrentState() const {return stateStack_.top();};private:	std::stack<boost::shared_ptr<State> > stateStack_;	typedef std::map<std::string, CreateNewStateCallback> theAvailableStatesTypedef_;	theAvailableStatesTypedef_ theAvailableStates_;	bool exiting_;// TODO : Hack until improve inputmanager	bool changedThisFrame_;};typedef Loki::SingletonHolder<SingletonStateManager> StateManager;


And then the state base class, from which all game states are derived:

#pragma once#include "entitymanager.h"#include "inputmanager.h"#include <string>#include <vector>class State{public:	State(void);	virtual ~State(void);	virtual void OnEntry() = 0;	virtual void Update() = 0;	virtual void OnExit() = 0;	virtual void RegisterInterestKeyboard(const std::vector<int>& interestingCommands, CanBeControlledByKeyboard* interestedParty);	virtual void RegisterInterestMouse(const std::vector<int>& interestingCommands, CanBeControlledByMouse* interestedParty);	virtual void ProcessInput();	EntityManager& GetEM() {return entityManager_;}protected:// This setup allows easy pushing and popping of states	EntityManager entityManager_;	InputManager inputManager_;};


And finally, a state implementation:

#include ".\stategamepathfind.h"#include "statemanager.h"#include "logmanager.h"#include <vector>#include <boost/any.hpp>// Register this state with statemanagernamespace{	boost::shared_ptr<State> RegisterStateGamePathFind() 	{			boost::shared_ptr<State> thisState(new StateGamePathFind);		return thisState;	};	const bool registerStateGamePathFind = StateManager::Instance().RegisterState("PathFind", RegisterStateGamePathFind);}StateGamePathFind::StateGamePathFind(void){}StateGamePathFind::~StateGamePathFind(void){}void StateGamePathFind::OnEntry(){	LogManager::Instance().Write(1, "Starting PathFind State");	entityManager_.LoadLevel("./resources/level1.txt");	std::vector<boost::any> theVec;	theVec.push_back(D3DXVECTOR2(50, 50));	theVec.push_back(0);	entityManager_.PushNewEntity("Player", theVec);		theVec.clear();	theVec.push_back(D3DXVECTOR2(750, 550));	theVec.push_back(1);	entityManager_.PushNewEntity("Player", theVec);	theVec.clear();	theVec.push_back(D3DXVECTOR2(50, 550));	theVec.push_back(2);	entityManager_.PushNewEntity("Player", theVec);	theVec.clear();	theVec.push_back(D3DXVECTOR2(750, 50));	theVec.push_back(3);	entityManager_.PushNewEntity("Player", theVec);}void StateGamePathFind::Update(){	entityManager_.UpdateEntities();	entityManager_.RenderEntities();}void StateGamePathFind::OnExit(){	LogManager::Instance().Write(1, "Killing PathFind State");}


It's still a bit hacky in places, and there's some stuff that I don't like yet, but it's working for me at the moment. Plus I never really got my head around the kernel based system - I can never work out how to compartmentalise my tasks. Maybe one day.

HTH,
Jim.
To me, the most efficient game loop is the one where all of your states are handled like ingame events. My menu is drawn using the exact same control path as any other part of the game, sure some systems aren't needed for things like menus and endgame sequences etc [like scene logic, which is pluggable in my engine, so you just swap it to the menu logic from the game logic], but these things are less time intensive than the ingame counterparts.

Also, because I use the same path as when I render the game, I can make my menus do anything that the game can do, and the menus receive all of the benefits of optimisations made to make the game run quicker.

In fact, I think that this is how games like UT2004 work with their menu systems .<br><br>Anywho, <br>//end rant<br><br>CJM
I use one game loop per scene instead of one game loop to handle everything

i am using inheritance and its slightly different than this but here is an example of what I mean

class Level_mainMenu{  graphics gfx;   int render();  int load();   int run(); // game loop is in here}; // so it would be one class for each level or scene WinMain(){  Level_mainMenu mainmenu;  Level_scene1 level1;  Level_scene2 level2;   int x = mainmenu.run(); // run the main menus game loop   switch(x)  {  case 1:    {      level1.run(); // level 1 has its own game loop      break;    }  case 2:    {      level2.run(); // and so does level 2      break;    }  }}

This topic is closed to new replies.

Advertisement