Jump to content
  • Advertisement
Sign in to follow this  
AnthonyVelazquez

C++ Program Cycle

This topic is 2599 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

How exactly would I be able to design a efficient game loop?
This is what I am using so far:
#include "Global.h"

int main()
{
Setup();

while(!done)
{
Input();
Logic();
Render();
}
return 0;
}
END_OF_MAIN()


Setup = Prepares Allegro, sound, keyboard, makes window
Input = Has control keys to exit the game
Logic = Empty so far
Render = Renders everything


I know everything in that loop will run over and over again, so where should I place the function to display my menu, if it will only appear once? How would I set it up?

Share this post


Link to post
Share on other sites
Advertisement
There are many ways to do this...a state system is just one of the many solutions.

For example in Logic()....

switch( state )
{
case Init:
DoInit();
break;

case Menu:
DoMenu();
break;

case StartGame:
DoStartGame();
break;

case UpdateGame:
DoUpdateGame();
break;

case EndGame:
DoEndGame();
break;
}

That kind of thing....but as I said there are many ways of acheiving what you need.

Share this post


Link to post
Share on other sites
[font="arial, verdana, tahoma, sans-serif"]Here's how I handle it:

Beyond initialization, anything that happens immediately that isn't dependent on player input or time should be setup prior to the main loop (e.g. a splash screen appears, or perhaps the menu pops right up).[/font]

Everything else is usually either directly or indirectly a result of player input or time. These situations can all be handled in the behavior of the objects involved. For example:

-Your input handling dictates that when the player presses mouse button 1 their weapon fires (let's pretend it's a rocket launcher)
-This sets up a schedule in whatever system you feel like keeping track of these things with that says "update this rocket every X milliseconds"
-Your loop checks the clock every frame to see if it's time to update any rockets that may be flying
-When your rocket reaches a position that the game deems to be a collision, it tells the rocket to go boom (collision would also be powered by the main loop (though, like the rocket movement, likely not every frame))
-When the rocket goes boom it checks to see if any player is in a certain radius of its detonation location and tells the player object to be hurt (which triggers animations, screen flashes, pain noises, etc.)

or more clearly:

-[Input] Triggers gun
-[Time] Triggers projectile movement
-[Time] Triggers collision detection which triggers boom which triggers pain which triggers animation, flashes, and owee sounds

And so on and so forth. The majority of game logic is usually just a big chain reaction among game objects kicked off by player input or time. Obviously there's all sorts of ways to do things differently, but this is how I do it and how I usually see it done.

Share this post


Link to post
Share on other sites
I do something a bit like this:


class BaseMode
{
public:
virtual ~BaseMode(){ }

virtual void Update(float delta)=0;
virtual void Render(float blend,GraphicsDevice &graphics)=0;
};

class Application
{
private:
GraphicsDevice graphics;
std::auto_ptr<BaseMode> mode;

public:
Application(){ /* initialise graphics and create a mode */ }

void Update(float delta){ if(mode.get()) mode.Update(delta); }
void Render(float blend){ if(mode.get()) mode.Render(blend,graphics); }
};

class MenuMode : public BaseMode
{
public:
virtual void Update(float delta){ /* menu logic */ }
virtual void Render(float blend,GraphicsDevice &graphics){ /* menu rendering */ }
};

class GameMode : public BaseMode
{
public:
virtual void Update(float delta){ /* game logic */ }
virtual void Render(float blend,GraphicsDevice &graphics){ /* game rendering */ }
};


The main loop then calls the Application::Update and Application::Render methods. Application takes care of deleting and creating modes as required.

It is a nice, shallow hierachy and allows for new modes (SplashScreenMode, HiScoreMode etc) to be added in quite easily. The downside is there is a fair bit of boilerplate involved in passing information through the Application object to the current mode. MouseDown, KeyDown and so on need to be in both the Application interface, the BaseMode interface and the actual concrete modes but personally I feel this is worthwhile for the flexibility offered by this approach.

Share this post


Link to post
Share on other sites
Yes, those are pretty much the standard idioms. But please also consider:

1) Does [font="Courier New"]Application[/font] really need to be a class? If so, you should really mark it as non-copyable, or else use a more sophisticated smart pointer that won't mess things up when you copy instances of the class. Granted, chances are you never intend to copy the instance, but better safe than sorry (it adds documentation, too).

2) You need a way to change state that isn't complicated, doesn't rely on massive Law of Demeter violations, etc. You also need a way to indicate back to the main loop that the Application is done. The simplest and most powerful way by far, IMO, is the "run and return successor" pattern.

What you do is:

a) Modify [font="Courier New"]BaseMode::update[/font] to return [font="Courier New"]BaseMode*[/font].
b) Set up the implementations so that they return 'this' if no state change is necessary; a newly constructed state object if one is necessary; or [font="Courier New"]NULL [/font]if the program should end.
c) In [font="Courier New"]Application::update()[/font], reseat the smart pointer with the return value. (Make sure it won't mess up if it's given the same raw pointer it already has.) Return whether or not this value was [font="Courier New"]NULL[/font].

That's it. I told you it was simple and powerful. Notice the proper separation of concerns: because we only allow for state changes through this mechanism, and we don't provide this mechanism for [font="Courier New"]Render()[/font], we never have to worry about rendering updating the global game state. Which is exactly what we want: drawing things should not affect their behaviour. (Now we only have to worry about it updating the internal state of the State object in ways that it shouldn't).

3) Sometimes the logical state transition will be "go back to a previous state, with all its state data preserved" (i.e. you can't just construct a new state of the same type as the state you were in, either because you don't know what that state was, or because you need to preserve more information). Please don't build a state stack. In my experience, it just complicates things and optimizes for the uncommon case.

We can do it much more simply:

a) Upgrade the smart pointers so they can share instances ([font="Courier New"]boost::shared_ptr [/font]should be fine).
b) Have the first state pass 'this' to the constructor of the second state.
c) The second state stores the passed pointer in a shared-pointer member somewhere. When it updates and decides it should "go back", it returns that value.

4) What if you don't want to create a new instance each time? Maybe your [font="Courier New"]State [/font]objects are expensive to construct. Maybe they're immutable, or else you always want to preserve their internal state. No problem. Just set up a pool of instances somewhere, and use all the same techniques, but just return pointers to pool instances as appropriate to transition to a "new" state.

Please don't use Singletons, no matter how strong the temptation. You might consider the Monostate pattern. If you later decide that it was a mis-step, that will be much easier to refactor - you just turn shared instance state into per-instance state, pretty much.

5)
MouseDown, KeyDown and so on need to be in both the Application interface, the BaseMode interface and the actual concrete modes[/quote]

No.

a) Create an [font="Courier New"]InputState [/font]class, and give the Application an instance.
b) Give [font="Courier New"]Application [/font]the interface needed to update and store all the input state. This is the point where you do any translation that's common to all modes (for example, you might translate certain key presses into virtual keys with your own names, and ignore all the others; you might calculate and store mouse movement speed, etc.)
c) Arrange for [font="Courier New"]Application [/font]to pass its [font="Courier New"]InputState [/font]by reference to [font="Courier New"]BaseMode::update()[/font].

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!