Best way to implement a <timed> buff/effect. Aka. Countdown timer

I’m sorry if a good solution to this already has been existed. I wasn’t able to find anything after a cursory search.

[PYTHON]

I’m creating a buff handling system, and I need a good way to detect if a “buff” have elapsed. So a countdown timer that operates on “ingame” time. Also I’d like to be able to get the elapsed and remaining time from the timer. I’m unsure whether the best method is using tasks, ClockObjects, deltatime, or a whole third method.

Each buff have a method named “Active” that should return true if the timer hasn’t elapsed, and False if it have.

A short snippet showing the logic would be greatly appreciated.

Simplified example of my code:

class TimedBuffCondition():
    def __init__(self, duration):
        self.duration = duration
        self.clock = ClockObject() #?

    @property
    def Active(self):
        pass

Hmm… I usually have all such things contain an “update” method that’s called, directly or indirectly, from a central “update” task.

However, if you’re not doing that, then the simplest solution might be to give your buff-class its own update task. In this task you could then maintain a timer-variable, which could be queried as desired.

You shouldn’t need a separate ClockObject per buff, I think–it should be fine to access the global ClockObject instance.

So, something like this:

class TimedBuffCondition():
    def __init__(self, duration):
        # I like to keep both the full duration and the current timer-value.
        # For one, this allows things like determining the
        # percentage of time remaining.
        self.duration = duration
        self.timer = duration
        self.task = taskMgr.add(self.update, "buff update task")

    def update(self, task):
        dt  = globalClock.getDt()

        self.timer -= dt

        if self.timer <= 0:
            # Enact whatever logic is appropriate to the buff running out
        
        return task.cont

Of course, don’t forget to clean up the class once you’re done with it, including removing the task!

Depending on the specific of your game, you might in part do this by returning “task.done” from the appropriate part of your “update” method, I believe.

1 Like

Thanks!

I usually have all such things contain an “update” method that’s called, directly or indirectly, from a central “update” task.

What is the advantages of doing it like that? I’m still early in development of this project, so if it has significant advantages, I can still change it fairly easily.

Honestly, I don’t know whether it is at all advantageous: it’s an approach that feels natural to me, and my instinct–whether accurate or not–is that proliferating tasks seems like it might have performance implications.

However, it may well be that the nested Python function-calls incurred result in greater overhead, and that using multiple tasks is more efficient.

So, I’ll leave it to others to weigh in on the relative advantages, I’m afraid; the “central update task” approach is simply my way.

1 Like

coincidentally, i was just doing some learning work with timers and ran across “doMethodLater”

it seems like it might work well, you can simply set-up the “buff-removal” task using doMethodLater and it would run automagically when the buff expired.

That can also work, indeed.

It does come with a caveat, however: if the objects that it’s intended to operate on–such as a character in a level–disappear before the time is up–say due to the level ending–then it may be left with invalid data.

In short, the task may fire after the indicated amount of time, even if the game is back at the menu, with no level in progress.

Against that case, it may be wise to remember to clean up any such tasks whenever the relevant objects are destroyed. (Much as would be the case if using a custom update-task.)

This needn’t be particularly complex, of course. If a buff is specific to a given character (that is, if a single instance isn’t shared between characters), then the responsibility of cleaning up the buffs can rest with the character. The character might call a “cleanup” method in the buff-class, which in turn might check for an active task and remove it, if called for.