The Holiness of the Update Cycle


Image of a space plane about to be hit by a fireball; there is a line T=0 in the middle and the plane is labelled chicken, fireball egg illustrating the problem of which came first.

Most games have what's called an "update cycle". One way or another, there are a bunch of entities/objects with an update () function, which is called periodically (usually every frame.) These might control enemy logic, particle positions, animations, etc.

Here is something that I have found to be incredibly helpful in reducing bugs.

Holy Means "Set Apart"

Consider that your entire update cycle is "holy"; that is, your objects are ONLY allowed to change their state meaningfully within it. For example:

  • Suppose you have an Enemy class, with an update () method and a hit (damage) method.
  • The hit (damage) method will typically have some logic to decide if the damage should destroy the object, and make it explode if necessary.
  • Then, there is a Bullet class, which within it's update () cycle checks to see if it's colliding with any Enemy objects, and if so, calls hit (5) for 5 damage.

How Something Gets Hit by a Bullet

Here's how I will structure this:

  • The hit (damage) method is outside the Enemy object's update method, so it's not allowed to meaningfully change the state of Enemy. In OO-terms, any changes it makes must be strictly private, and not change the public appearance of the object.
  • This means that the hit (damage) method is NOT allowed to explicitly kill the object; as well, its not even allowed to affect the outcome of a function such as isDead () (which might check if the hit points <= 0).
  • So, what you do is you have a request variable, such as "damage_accumulator" which accumulates the damage over a frame. the hit (damage) function just adds damage to the internal damage_accumulator.
  • Then, in the object's update () cycle, you perform hit_points -= damage_accumulator; damage_accumulator = 0; and a check like if (hit_points < 0) { explode (); };

Reasoning Why It's Better

I don't know exactly why this saves me so many headaches, but it does. For sure, it's something "like" a transactional system; changes are being collected (e.g., in the damage_accumulator variable, above) and then being applied later on, all at once. The update cycle for an object knows e.g., that the object will explode before it heals, or vice versa.

But I think it's actually more to do with unexpected side-effects where an object's external state changes more than once per frame. For instance, suppose object A had update () called, then object B's update () determined it needed to call A.changeSomething (). At this point, A has looked three different ways in the game's update cycle: before A.update (), after A.update () and after B.update ().

It definitely seems to be working for me-- maybe it'll help you, too!

2010-08-17


◀ Back