What is the best way to allow a function to only be called once per frame

I have a combat system including explosions. When an explosion occurs, all colliders are found within its radius and if a damageable component is found, damage is done.

Multiple objects such as AI have more than one collider. So what is the best way to handle this so I don’t get the TakeDamage function called multiple times?

Right now I flag it with a “calledThisFrame” variable that is set to false each frame. This feels odd though and in the unlikely event of two explosions at the same frame, the function would not be called properly.

Is there a better way?

Considering your description, it isn’t likely you need a limit for once per frame. Explosions probably don’t occur on frame boundaries, they’re likely part of the physics system, which is once per FixedUpdate cycle, but I sense the problem goes deeper than that. Explosions don’t occur neatly fitted to either the Update or Physics cycle, but according to the explosion or explosions themselves. What you really require is once per explosion, without regard to when it happens or how many colliders are involved. Because of the potential of multiple colliders, it doubt this is exactly on a function boundary, but that depends on your design, and it appears by your description that is fashioned such that damage is accepted in one place (and that’s good).


This mimics several other paradigms from non-game scenarios in which an event occurs that can have multiple consequential events impacting receivers (the objects being hit by the explosion), and there must be a way to avoid repeated impacts. I’ll take a moment to detail this from the explosion and the object in order to outline the process of thought that occurs to me.


An explosion can occur at any time, and multiple explosions are possible (even likely) in close approximate time, with some potential for multiple explosions (in a more limited quantity by probability) could happen at exactly the same time (as measured by the available clock from Unity).


There are multiple objects that can receive the explosive force(s), each with potentially multiple colliders, leading to the possibility that multiple signals of damage might be received by a single object where only one (or the first) of those explosive notifications should be applied per explosion.


If I were writing this for my game, I’d consider giving each explosion a unique serial number when they occur, such that even when multiple explosions occur at the same moment in time, they are easily identified as a unique event by this identifier (an integer, controlled by some static counter incremented as each explosion is trigger).


For each object that may receive this explosion, I’d consider using a circular queue that could store a small structure indicating an explosion and a time the explosion notice is received. For each explosion being sensed by an object, a check can then be made by searching the circular queue to note if that explosion ID has already been registered. If it has not, then it is new, it is added to the circular queue (noting the time), and damage is calculated (possibly animated, etc). When subsequent notifications of that same explosion is sensed, it will be found in the circular queue and ignored.


When adding an entry to the circular queue, a maintenance pass will follow to purge entries in the queue from explosion notifications that are older than some threshold of time that is longer than the sustained effect of an explosion can last, to keep the size of the queue limited (probably no more than about 4 or 6 entries, or about 3 times the maximum number of simultaneous explosions that could be managed.


If you’re thinking about performance, I’ll give you a bit of a casual analysis. Small circular queues are naturally quick, because they are often implemented as arrays. Sequencing through a small number of entries in an array like this is very fast, as they are cache friend to the CPU, making the search of a sequence of less than 6 entries faster than looking up an entry in a Dictionary, and hardly more work than a method call or two (which isn’t as friendly to the cache). It works on the order of a quarter million or more entries per second, even on a modest device this side of 2010, and over a few million per second on modern high performance devices.


I can imagine at least two or three other means of doing this, none of which are significantly better. For example, I could consider the entire potential blast radius, initialize all potential objects as “false” (not having sensed an explosion), then let one explosion at a time happen, and trip a bool to “true” on the first “hit” so it only happens once. It seems obvious to me this would be slower depending on the number of objects to find in the blast radius.

Call the function inside the Update of a Singleton, or use a boolean to check if the method was called, if not called change the boolean to true and call the method, and in the LateUpdate reset the boolean value.

There are several solutions each with advantages / disadvantages. The easiest solution is to add an int variable to your damagable component that is simply set to Time.frameCount whenever you apply damage. Time.frameCount will automatically increase each frame. So you can do this inside your method

if (Time.frameCount > lastFrame)
{
    lastFrame = Time.frameCount;
    // apply damage
}

So when called again during the same frame nothing will happen.

Another way would be to collect all damageable script instances from all colliders and store them in a hashtable or dictionary. That way you easily filter out any duplicates. This is slightly more expensive but it might be useful when you actually need the list of damageable for something else.

Another solution may be to simply attach another trigger collider to each object which you place on a seperate layer. That way when doing the overlapsphere you can filter out only those colliders and since each object only has one you get each object only once. Keep in mind that you can put just a child object onto another layer.