4
\$\begingroup\$

I have a question about game loops. I understand that you shouldn't have a static loop, say 100ms and set something's speed to 1px/frame so it moves 10px/sec. You should have a speed and multiple that by the elapsed time so frame rate does not effect game speed. This works fine for values that can be floats, but what if I need integer values? Say shields recharging or something. I have a damage system is my game and I would really love to stay away from float damage values, both for memory and game design reasons.

However if I just have a running total of elapsed time, and I recharge one point once this elapsed time is over 1 second then over the course of time won't I have some consistency errors between recharging over one set of 100 real seconds and another set of 100 real seconds because those two sets may not have the same number of frames in them. This questions also comes up in rate of fire. If I need a turret to fire every .5seconds, won't there be some variance if I do a running total of elapsed time? Sometimes it may fire every .501 seconds and other time .510 seconds?

I guess my question is, how exact are games really? Do most games just do a check every frame and say, "Well your rate of fire is .5 seconds, and its been .504 since you last fired, so fire now." If so would it work out for the next fire cycle to only check for .496 seconds to average out? I know it most games under 10 minutes this would barely be noticeable, but over the long term this may actually mater in some cases.

\$\endgroup\$
1
  • \$\begingroup\$ if you're worried about shooting each .5 seconds exactly, then don't, you aren't losing miliseconds by firing at 0.504 instead of 0.500, you will still fire 4 times each 2 seconds, maybe each 2.004 seconds if the last shot comes late, but it isn't anything i'd worry about, especially if you end up shooting hundereds or thousands of times, the difference between shots or frames in two different runs, no matter the length, is going to be 1 or 0, if you need higher accuracy then, well, tough luck, timers are precise, but also the data needs time to be processed \$\endgroup\$
    – dreta
    Commented Mar 12, 2012 at 21:57

5 Answers 5

10
\$\begingroup\$

I have a damage system is my game and I would really love to stay away from float damage values, both for memory and game design reasons.

My first instinct is to question your assumptions. It's extremely unlikely that using floats rather than ints for something like this is going to affect your memory usage in any noticeable way. On most systems, they take up the same amount of memory anyway, and on any other system you probably won't have enough values in question for it to make a difference. (Unless you have millions upon millions of these values?)

Also, it sounds like you may just be suffering from a bit of inertia. If you need a turret to fire every 0.5 seconds, that's a job for floating point values. Using integers for that in some way is incorrect.

The same might be said for the damage value, though this could be debated. Do you REALLY need your damage value to be an integer? Or is that just how you've been envisioning it in your mind? Most games would model a damage value as a float and simply display it to the player as an integer value. I'm betting that you are massively overthinking this issue, and should start thinking about what would be easier to work with, rather than chasing some idea of what is "correct" (which may not even be right in the first place)

That said, if you still decide that you want to use integers, the best thing in the case you describe (recharging an integral value, for instance) is to link it with a float that describes how often to recharge, and an integer that describes how much to recharge by, and a float that describes how much time has passed since the last recharge:

int myValue;
int rechargeValue;
float timePassed;
float rechargeTime;

void Update( float TimeDelta )
{
    timePassed += TimeDelta;
    if( timePassed >= rechargeTime )
    {
        timePassed -= rechargeTime;   //Save the remainder for later
        myValue += rechargeValue;
    }
}

Something like this will ensure that your value recharges at the right rate, and saving the remainder ensures that the rate remains steady over time.

If you're concerned about your game becoming less accurate over time, then be sure to store your elapsed time values in accurate data types like unsigned ints, and doubles, and only use floats for your time deltas.

\$\endgroup\$
2
  • 3
    \$\begingroup\$ +1 for a good answer and a nice proposition how to solve recharging. You could even use a while instead of an if, so that it would also work if TimeDelta suddenly exceeds rechargeTime multiple times. \$\endgroup\$
    – bummzack
    Commented Mar 12, 2012 at 19:38
  • \$\begingroup\$ @bummzack Great point, I missed that! \$\endgroup\$
    – Raptormeat
    Commented Mar 14, 2012 at 2:19
3
\$\begingroup\$

Memory reasons? In day to day C++ a float is 4 bytes, an int is 4 bytes, there is no memory reason. For example, to maintain a float value for shields for one million enemies is only 4M RAM, typical low end PCs have at least 2G so this kind of data storage isn't really a problem. Trying to process one million enemies, that's a problem =)

As for game design please keep in mind that what you show the player is only a representation of the underlying simulation, if you simulate +1 shield/second then your simulation works in fractional seconds but you only show the integer representation.

Most games do carry over left-over time in systems, particle emitters work this way to avoid stuttering for example or the +1 shield regeneration above. I think the deciding factor should not be "how small a number doesn't matter" but "what am I modeling?"

\$\endgroup\$
2
  • 2
    \$\begingroup\$ (Nitpickery: OP could easily be storing the shield value in a byte rather than a full int, so if there are several other byte values being stored then there could be a difference in memory usage - though I obviously agree with you that it still won't be an issue until there are so many enemies that other concerns become much more critical.) \$\endgroup\$ Commented Mar 12, 2012 at 18:20
  • 2
    \$\begingroup\$ @StevenStadnicki I totally agree with your nitpick! but OP is probably not aware of the struct member alignment and padding options required to ensure that byte remains a byte of RAM so I aim at the simplest example. \$\endgroup\$ Commented Mar 12, 2012 at 18:31
2
\$\begingroup\$

Within Stendhal RPG have something similar, we simulate real time by having turns with a short duration. The server works turn wise, each turn is 300 ms for us. Now if we have for example food that regenerates a player over time, we do not calculate how many ms till the next healing should happen. We count the number of turns, that need to pass till the next time the player gets healed.

To handle this there is a TurnNotifier, which checks each turn, what to do. What to do is defined in implementations of an interface TurnListener:

public class TurnNotifier {

     private final Map<Integer, Set<TurnListener>> register = new HashMap<Integer, Set<TurnListener>>();

    public void onTurn(Integer turn) {
        Set<TurnListener> nowToHandle = register.remove(turn);
        for(TurnListener tl : nowToHandle) {
            tl.onTurnReached(turn);
        }
    }
}

The TurnListener looks sth like this:

public interface TurnListener {

    void onTurnReached(Integer turn);

}

With this concept you are able to abstract from actual time passed and whenever the TurnListener is called, you know, that enough time has passed to do what is necessary.

I don't think it is important to be as precise as possible.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ Most useful quote ever: "I don't think it is important to be as precise as possible. Just be as precise as necessary." \$\endgroup\$ Commented Mar 12, 2012 at 19:27
  • \$\begingroup\$ :P ;) You are right, maybe a bit too esoteric, removed the second part. I only wanted to emphasize that maximum precision is not important. \$\endgroup\$
    – Markus
    Commented Mar 12, 2012 at 19:48
1
\$\begingroup\$

If you want your shield percentage (for instance) to actually be an integer from zero to one hundred, then there's little you can do; as you note, you'll be prone to error on things like recharge simply because your 'resolution' isn't fine-grained enough for what you need.

But I think you're confusing an integer value with an integer view - simply because you present shields to the player as a whole number between 0 and 100 doesn't mean that you need to represent it that way! Indeed, reasons like your recharge example are exactly why even values that look like integers to the player are often represented as floats (or, at least, 'higher-resolution' fixed point numbers - for instance, imagine that shields scale from 0 to 1,000,000) and rounded (or truncated) for purposes of showing to the player.

You say you 'would like to stay away from float damage values for memory reasons' - how many enemies are you expecting to keep track of? Unless you're processing far more enemies than most games can dream of (literally tens of thousands at once if not more), then it's hard to believe that using four bytes instead of one for a handful of values will have any substantial impact at all on your memory footprint. This is really a drop in the bucket, and I can almost guarantee that you'll have far more important things to worry about memory-wise.

\$\endgroup\$
0
\$\begingroup\$

I understand that you shouldn't have a static loop, say 100ms and set something's speed to 1px/frame so it moves 10px/sec. You should have a speed and multiple that by the elapsed time so frame rate does not effect game speed.

I disagree, or that is, I disagree to the extent that variable step is a performance optimization at the cost of making a lot of things slightly harder to handle. For most small scale developers that is not a good tradeoff.

The easy fixed step solution is to run the game mechanics at 120 Hz (good number for 60 Hz screens). But you must keep the rendering flexible, an easy way of doing this is to set up a loop that first advance the game mechanics one step, then render a frame if the mechanics are ahead of schedule, and then if necessary wait until the mechanics are back on schedule.

Integers
As others have mentioned there is no need to use integers for the sake of performance, but for the sake of gameplay they are often the best choice, in some cases even as implicit fixed point values.

Specifically for shield recharging and similar slowly changing values it may be a good idea to use a fraction accumulator. Instead of adding some value to the shield every step, add a value to an integer accumulator every step, and whenever the accumulator reaches a given threshold, subtract that threshold from the accumulator and add 1 to the shield. If your mechanics run at 120 Hz, and you decide that the threshold should be 1200 then you can make the shield recharging be any multiple of 0.1 per second, and you can change it simply by changing how much you add to the accumulator each step.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .