Actions that span several iterations of the game loop

Started by
1 comment, last by J306 18 years, 2 months ago
Hi. I'll start by outlining my level of knowledge: I've made an engine, as a project in a university game programming course. We learned how to create a 3D world which a player can walk around in, with collision detection. No special lighting or anything. We create our worlds in WorldCraft, then use a builder that we made to convert them to our own format which the engine reads. So basically I understand how to keep track of all objects in the world, and draw them all with every iteration of the game loop (we use OpenGL). We also learned how to make objects that have their own matrix, who's face coordinates are local to that matrix. That's about as far as my knowledge goes. We didn't learn about adding live animations or anything--which brings me here today. I've noticed that lots of times in a game engine, you want to perform an action across several iterations of the game loop. For example, if a player is moving and then the user takes their finger off the 'forward' button, normally you want to simulate the idea that the player had momentum, and have him/her gradually come to a stop. Making them slow down to a stop necessarily takes place a-little-at-a-time, across several iterations of the game loop, ie. they slow down a bit with every frame. The way my engine works, every object in the 3D world has a "tick()" function which gets called every iteration, and does whatever has to be done for that object (this is the equivalent to the update() function, for anyone who's read the "Enginuity" tutorials on GameDev). So when the player's walking forward, the tick function checks to see if the button is pressed, and if so calls a moveAhead() function to translate the player forward. When the player is "coming to a stop", things will be different. I know that I could just have a flag on the player object, called isSlowingDown or something, and just check that every tick. If it's on, the moveAhead() function moves the player by a little less every time, until he's stopped. But I'm smart enough to know this is a foolish way of doing it. Odds are that I'll have actions like this all over the place in my game, where you only want to do something across a finite number of game loop iterations. I can't start putting conditionals all over the place, so that every single iteration, every object checks whether or not it's in the midst of "slowing down", or "speeding up", "skidding sideways", "falling from the sky", and so on for every possible thing an object could do. This is not only messy code, it's innefficient. So I guess I have to abstract some commonality out of these gradually-occuring actions. So my guess is that the proper thing to do would be to create a "GradualAction" type, and just keep a data structure of all GradualAction's that are currently happening. Then, every iteration, just call the actions in this list. Is my assumption correct? If so, how do most engines go about this? My engine's set up so that the objects in the world perform actions on themselves, with a function for each action, e.g. the moveAhead() function that I mentioned. So my guess is that I should create this GradualAction type, consisting of little more than a tick() function. Then for each action make a class which implements GradualAction, and set tick() up to call the appropriate function(s) in the actual game objects. Then my game loop just has to call tick() for every GradualAction in the list. Richard Fine's Enginuity tutorials seem to be taking this route, using the ITask type to represent what I've been referring to as "actions". But I could only find up to part 5 of the series, so I never did find out how he goes about hooking an ITask to the actual functions of the world objects. Furthermore, he's handling his own game loop, so an ITask is meant to represent ALL actions. In the course I took, we set up our engines so they let OpenGL handle the game loop, just by calling glutMainLoop(). My code simply registers its functions with OpenGL, to be automatically called on every iteration. So stuff that happens every iteration for an indefinite period, like moving the player forward which happens as long the user is holding the forward button, is hooked into this mechanism. Hence I'm only looking for an abstraction for so-called gradual actions. Any help on this will be appreciated. By the way Richard's tutorial is at http://www.gamedev.net/reference/list.asp?categoryid=45 Justin
Advertisement
Quote:Original post by J306
When the player is "coming to a stop", things will be different. I know that I could just have a flag on the player object, called isSlowingDown or something...


Yes, you could ...

Quote:
So my guess is that the proper thing to do would be to create a "GradualAction" type, and just keep a data structure of all GradualAction's that are currently happening. Then, every iteration, just call the actions in this list. Is my assumption correct?


Well, you COULD use that method...

Quote:
If so, how do most engines go about this?


Not like that.

Normally for handling this kind of thing, you'd work by modelling intertia and friction of the player. This handles things like continuing after the player lets go of the forward key, not instantly reaching top speed when the player hits the forward key in the first place...

In a car, if you release the break and instantly floor the accelerator, does it immediately reach top speed? No. This is because of inertia, i.e. it takes a finite amount of time to change speed. This is acceleration.

I turns out, that modelling this is actually incredibly simple. There are two main methods of doing so, called "Euler integration" and "Verlet integration", but I used the former for a long time before realising that's what it was.

---

In the case of something like a FPS, friction doesn't just model friction, but also the player actively slowing down - but the engine doesn't care, we probably just consider it a stronger type of friction.

---

Needless to say, this is not a general mechanism, Euler integration (or Verlet) will only help you model intertia, not generalised timer tasks.

You were right in your assessment that a lot of tasks in games often need to be delayed or take more than one tick to complete.

Consider your FPS again - your character has a gun. He can fire it when the player hits the fire key. But it doesn't fire instantly, in actual fact there is a short delay while the character's finger gets on the trigger.

So you'd have to remember that the player had pressed fire, and remember how long ago that was. After the requisite time period, you'd then call the routine which actually creates the bullet and/or kills the monster as necessary (Some guns in FPS fire instantly, others have projectiles which fly at finite speed).

After the gun has been fired, the player can't fire it again instantly, he has to wait while the character reloads it (even if it's fully auto etc, just the delay is smaller). This of course, is a second delay, independent of the first.

What you'll want for the gun, is a finite state machine for the gun, say there are three states for the gun: ready, firing and reloading.

You'd need a timer which would normally be something like an int. This would store how much time is left before the current action is finished (in the case of ready, it isn't used).

Each tick, you'd check the gun state, decrement the counter if necessary, and update the state and/or call the fire routine. It's that simple.

For most other things, there's something like that too. A simple counter which is decremented and tested each tick usually suffices.

If you feel it's necessary, you could make a generalised state-machine handler. Alternatively, you could create a priority-queue scheduler which would know which things need to be actioned at which tick. This is more complicated, particularly given the fact that often actions might get cancelled (if player dies while the gun is reloading, the gun never gets reloaded).

If you have a very large number of timers running concurrently, a priority queue may be more efficient. But don't overcomplicate your code.

Mark
Good answer! Cheers.

ya, the player coming to a stop was a bad example. Thankfully you caught my drift with timed tasks.

This topic is closed to new replies.

Advertisement