Game loop question: how to solve this problem?

Started by
8 comments, last by MatrixTan 10 years, 11 months ago

Hi.

I was looking at this:

http://gamedevgeek.com/tutorials/managing-game-states-in-c/

in the context of making a "rogue-like" game. Namely, I was wondering whether this could be used to handle stuff like the inventory menus and so forth (using different "game states" for menu mode and playing mode). But then I ran into a snag: if you have a loop like this:

input

logic

render

where in the "main" state, each round of the loop would represent one turn (wait for input, do the command for that turn, repeat).

But then I notice a snag: if it changes to the "menu" state to get, say, an item to drop, and then changes back to the main state, the loop will try to execute a full input-included cycle, instead of immediately running the logic for the turn in which the item was dropped. So how does one avoid that?

Advertisement

Is this a problem that you're having or a problem that you're imagining running into?

void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.

I'm not sure if I follow your problem, but defer the switch change to the end. Instead of immediately changing state, request a state change and at the end of the frame, process it.

That way you ensure the same state processes all the input in its frame.

If what you means is that your game needs to temporarily switch to another state and then back within the same iteration (which would cause the 2nd state to process all input) then you have two solutions:


Input frameInput = input();
logic( frameInput );
render();

That way you can just run the other state passing a null input, this tends towards a more functional programming approach.

The other solution (you could apply both) is to queue a list of operations (i.e. 'drop object from inventory') the main logic state will have to process when it takes over again.

OK, maybe my explanation wasn't quite good. I wrote my post kind of hurriedly. Hopefully this is better:

The loop would look like (pseudocode) (note this loop is for a simple "text mode" roguelike with no continuous animations, etc. -- that would require a different kind of loop that runs many times per second):


while continuing: // 1 cycle = 1 turn in "playing" state
      input = inputSystem.getInput();
      gameState.logic(input);
      gameState.render();

The state changes happen at the end. Consider what happens in the loop when one presses a "d", for "drop item", say. The input system gets the "d" key press. This then goes on to the logic _for that game turn_. However, _we cannot complete the command without first getting a choice of item to drop from the inventory menu_. So, we must:

1. defer the logic for the rest of the turn (move monsters, etc.) until the menu completes

2. switch to the menu state

3. when the menu gets its input, switch back to the game state.

4. finish processing the command and handle the logic for the turn.

Now, assuming the state transitions happen at the end of the loop, consider what happens in (3). The loop completes its final cycle in "menu" state, then switches back to "playing" state. But our work is not done -- we still need to do step (4). Yet when the loop executes its next cycle, now back in "playing" state again, it goes to requesting input again, when it should go to finishing up the logic for the turn. And the logic function will start all over again from its beginning, with a new input... what happened to finishing up the command we wanted to do (drop the item), and handling the remaining logic that would follow it in the turn?

Depending on the game style, whether realtime, turnbased, multiplayer I can see lots of possibilities for resolution. I think the second option Matais presented would be the cleanest.


while(running)
{
   //Process input.
   //process events
   // update logic
   // render
}

So user presses d, the input is processed, a state change is requested, the rest of the logic is handled, and the scene is rendered. on the next iteration, the menustate allows the user to select the item for dropping, the scene is rendered again, and we return to the gamestate. on the next iteration, we now have an event to process(user dropped itemx) so we handle it. process the rest of the logic, and render our 3rd frame.

Depending on the game style, whether realtime, turnbased, multiplayer I can see lots of possibilities for resolution. I think the second option Matais presented would be the cleanest.


while(running)
{
   //Process input.
   //process events
   // update logic
   // render
}

So user presses d, the input is processed, a state change is requested, the rest of the logic is handled, and the scene is rendered. on the next iteration, the menustate allows the user to select the item for dropping, the scene is rendered again, and we return to the gamestate. on the next iteration, we now have an event to process(user dropped itemx) so we handle it. process the rest of the logic, and render our 3rd frame.

However, there's the problem: "a state change is requested, the rest of the logic is handled..." -- but we shouldn't do a turn's worth of logic until after we get the item to drop, since the drop happens as part of that turn. And also "on the next [3rd] iteration, we now have an event to process"... but on that iteration, before we get to "handle events", we go through "process input"... yet we shouldn't process more input until we have finished the drop.

Have you thought about this? At a higher level really you have 2 states: the main menu and the ingame state. The main game loop will handle these states and which one is active, and each of thoses states will each have it's own loop for rendering and logic. Now your ingame loop has 2 sub-states: in-the-action(this is where you can see the background and characters) as well as an inventory state. The logic in the ingame state will mainly handle switching between the two sub-states and determining whose turn it is. You need to distinguish between general inputs and a completed turn command. The inventory sub-state and the in-the-action sub-state can send commands to the ingame state to process. If this is a turn-based game, the commands won't be coming in every frame so you will have to process them as they come in. There isn't just one kind of logic but logic for different things. So you can't just lump it all together. Split the logic up and do the different kinds of logic at different times. I hope this helps.

Edited to be clearer.

Learn all about my current projects and watch some of the game development videos that I've made.

Squared Programming Home

New Personal Journal

@SquaredD: "The inventory sub-state and the in-the-action sub-state can send commands to the ingame state to process." However, I thought that game states were supposed to be kept separate. But am I right in thinking this communication is OK in this sense since what we are doing here is building a _hierarchy_, and states on the lower level of the hierarchy (the sub-states) are not directly communicating with each other, but are instead communicating with the states on the higher levels of the hierarchy, and so the coupling of sub-states is loose and not tight?

Also, do the sub-states contain their own loop or just render/logic/input (or just render/logic only?) functions? If just those functions only, then it still doesn't seem to solve the problem of suppressing the input for the loop cycle performed while in the "game action" substate that immediately follows the ending of the "inventory" substate when processing drop commands (since the logic for that cycle is not using new input, but rather completing a command in progress).

The input system gets the "d" key press. This then goes on to the logic _for that game turn_. However, _we cannot complete the command without first getting a choice of item to drop from the inventory menu_.

This is how I see the situation. The item-dropping is another state on top of the menu:

• State Stack [Gameplay -> Menu]

The menu state is on top of the stack, and so it's the only state being executed. Only the top-most state is ever executed by the state manager.
The menu state lets the user take one of these actions: Leave-Menu, Drop-Item, Equip-Item, etc.
- The menu's Input method keeps querying the input to take any of the above actions.

- In case key "d" is detected, the menu's Logic method pushes a new state to the stack, the Drop-Item state, and leaves its logic method immediately (so we don't risk pushing more states for other keys that were also detected for that frame). You don't necessarily need to leave the Logic method at this point if you do more stuff than just checking key actions - the important thing is that you stop taking action for other keys. You'd need to run the rest of the menu logic in case you have a 2D or 3D game and want animated characters and audio happening in the background while you're using the menu.

- The menu's Render method is then called, rendering the entire game by calling the rendering system. It draws all game text (in case of your text-only game).

After rendering, once the program returns to the main loop, the state manager keeps calling to top-most state. This time, instead of it being the menu, it's the Drop-Item state which was just pushed to the stack.

- So the state manager invokes the functions from the top-most state which is Drop-Item.


• State Stack [Gameplay -> Menu -> Drop-Item]

You know the Drop-Item state is only called if the user pressed "d" from the menu. So this state only keeps listening for whatever item the user wants to drop.
- The Drop-Item's Input method reads keyboard characters that are assigned to each slot in the menu.

- The Drop-Item's Logic method figures which item was selected and calls a chosenItem.Drop(), or InventoryDrop(chosenItem) etc. As soon as an item is detected and dropped, the Drop-Item state pops itself from the stack and leaves its Logic method.

- The Drop-Item's Render method is then called, drawing the screen text just like the menu was doing previously (same calls to the rendering system and all, so no visual difference except for new text that's being added from these actions), and then we go to the main loop.
- The state manager executes the top-most state, which is now the menu.


• State Stack [Gameplay -> Menu]

Menu is back executing, the item was dropped.

EDIT: Clarity.

in the menu state, you can start up the embedded message loop. Like this:

showMenu:

draw menu;

while(true):

input

logic

draw

break the embedded loop when the menu ended.

This topic is closed to new replies.

Advertisement