2
\$\begingroup\$

I have some DOT (damage over time) implementation problems. My game runs on 30 FPS speed. Current implementation is: let's say the hero casts a spell which deals 1 damage per second. On every frame I do (pseudo code):

damage_done = getRandomDamage() * delta_time;

I accumulate damage and when it becomes more than 0, I subtract rounded damage from current health and so on. With 30 FPS and 1 DPS it will be 1/33 = 0.05... We know that floats are not precise enough to sum 30 circulating decimals and have exactly 1 in the end. But HP is a discrete value and that's why 1 DPS will not result in 1 damage after 1 second, because the value will be 0.9999... instead. It's not such a big deal when you have 100000 DPS, ±1 damage will not be noticeable. But what if I have 1,5 DPS? How do modern RPGs implement DOTs?

\$\endgroup\$
3
  • \$\begingroup\$ ...I'm confused about why you're tracking damage with floating point numbers, when health apparently only exists as integers in your system. Why not just use integers for damage, if that's what you want? \$\endgroup\$ Nov 19, 2012 at 21:55
  • \$\begingroup\$ How do you calculate 1 dps? \$\endgroup\$ Nov 20, 2012 at 5:24
  • \$\begingroup\$ 1 dps is 1 damage every 1 second. 2 dps is 1 damage every 0.5 seconds. 10 dps is 1 damage every 0.1 seconds. 0.1 dps is 1 damage every 10 seconds. The time between applications of 1 point of damage is 1.0/[dps] seconds. I'm just saying that if you're declaring that 'health' is an integer value, that you should be treating 'damage' as a strictly integer value as well, or you're going to run into this sort of issue everywhere. \$\endgroup\$ Nov 20, 2012 at 9:57

2 Answers 2

4
\$\begingroup\$

In my experience, i prefer to use a single "big" hit every so often than to accumulate a lot of very small differences.

I would recomend you to use a frequency (for the sake of simplicity i'd go with 1 second) and on your update loop check how much time has passed since the last time the dot has applied damage. Once you get to a time where you know damage has to be applied, you apply it, reset the counter and wait for the next tick of the dot.

float dot_frequency = DOT_FREQUENCY;
bool apply_first_tick_immediately = true; //<--With this you control the behavior as you asked

void on_dot_applied()
{
    if (apply_first_tick_immediately)
        time_since_last_tick = dot_frequency;
    else
        time_since_last_tick = 0;
}

void update(float elapsed_time)
{
    time_since_last_tick += elapsed_time;
    time_since_start += elapsed_time;
    if (time_since_last_tick >= dot_frequency)
    {
        apply_dot_damage();  // <-- here get your formula for dot damage
        time_since_last_tick -= dot_frequency;
    }
    if (time_since_start >= DOT_DURATION)
    {
        remove_dot();
    }
 }

I hope this helps.

EDIT: Added an example of how you can work with the "deal damage immediately" idea. Probably the on_dot_applied is an event that is called immediately as a dot is applied to setup certain effects of the specific dot, such as a fire particle effect, maybe a shader change.

\$\endgroup\$
3
  • \$\begingroup\$ But what if i have a fire pool where player damaged for 5 dps. If pool will "tick" only every second then player can run on it and leave without damage taken. I assume that but is it fair for players? \$\endgroup\$ Nov 19, 2012 at 12:03
  • \$\begingroup\$ If you always want at least one tick of such damage, simply start your "time_since_last_tick" in a value equal to your frequency. You may get a slightly more complicated exit condition in some cases, but as far as i can see is just a matter of shifting your ticks to start inmediately instead of waiting one tick. Other option that comes to mind, you could simply apply the first tick's damage as soon as a player enters into such pool and the rest of the damage could be managed as a dot. \$\endgroup\$
    – Carlos
    Nov 19, 2012 at 12:14
  • \$\begingroup\$ Hm, this is a good idea to reverse damage deal, deal then wait, instead of wait->deal damage. You can update your answer) \$\endgroup\$ Nov 19, 2012 at 12:19
2
\$\begingroup\$

If you're worried about imprecisions, you could simply apply the damage every second:

time_last_damage += delta_time;
if (time_last_damage >= 1.0f)
{
    time_elapsed = floor(time_last_damage);

    damage_done = getRandomDamage() * time_elapsed;

    time_last_damage -= time_elapsed; // keep fractional time
}
\$\endgroup\$
1
  • \$\begingroup\$ This is how DotA 2 does it - Once per half second. \$\endgroup\$ Nov 19, 2012 at 11:55

You must log in to answer this question.

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