Heartbeat (fixed hertz reoccuring tick)
by Fenrir Wolf · in Torque Game Builder · 07/21/2005 (6:04 pm) · 27 replies
Lately, I've found the need for a heartbeat function in T2D. While I could simulate it with a reoccuring schedule, I am never sure how accurate the schedules are on a fast, reoccuring basis.
There's fxSceneGraph2D::onSceneUpdate, but this is only called once per frame. Which can cause wildly differing results depending on the ingame framerate. Currently, my logic lives in onSceneUpdate and when the game's frame rate drops, everything is thrown out of whack.
I've been poking around in DemoGame::processTimeEvent and have been tempted to add some kind of heartbeat or per-tick script function call, but I was curious what everybody's thoughts are on this? I need my heartbeat event to be called at an exact rate, regardless of ingame framerate. (I am syncronizing things based on this event.)
There's fxSceneGraph2D::onSceneUpdate, but this is only called once per frame. Which can cause wildly differing results depending on the ingame framerate. Currently, my logic lives in onSceneUpdate and when the game's frame rate drops, everything is thrown out of whack.
I've been poking around in DemoGame::processTimeEvent and have been tempted to add some kind of heartbeat or per-tick script function call, but I was curious what everybody's thoughts are on this? I need my heartbeat event to be called at an exact rate, regardless of ingame framerate. (I am syncronizing things based on this event.)
#2
I was thinking about the milliseconds method in onSceneUpdate, for I assume the scene graph clock is at least per-tick accurate.
Maybe someone can point schedule loading debug info out to me? (I know there's a few debug stats collected by the engine itself, not sure if things like number of schedules active and so on are part of them.) Right now, I am using a fair number of schedules to control various events in my game, such as animations, with very low firing times (sometimes less than 20ms). I presume a schedule will always fire even if it might be a little bit late (in real-world time)... Being that Torque is all single-thread, I could see it being no other way. No chance for schedule racing.
In short, I suppose I just need to do more research, and I was curious if anyone else had already done some of the legwork first. :) I might try a few methods and see what generates the most accurate, predictable results based on wildly-variant framerates.
07/25/2005 (2:22 am)
Hmm, looking back, I see I didn't phrase that right. I actually don't care if the event is exact in real-world time units. (Say, for timing sounds as you mention.) I just want it to occur at the same frequency regardless of ingame iteration rate. In other words, if the game runs 2x as slow, that event should also fire 2x as slow. Using timing based on onSceneUpdate is somewhat useless for something like this right now because it alters calling frequency based on framerate.I was thinking about the milliseconds method in onSceneUpdate, for I assume the scene graph clock is at least per-tick accurate.
Maybe someone can point schedule loading debug info out to me? (I know there's a few debug stats collected by the engine itself, not sure if things like number of schedules active and so on are part of them.) Right now, I am using a fair number of schedules to control various events in my game, such as animations, with very low firing times (sometimes less than 20ms). I presume a schedule will always fire even if it might be a little bit late (in real-world time)... Being that Torque is all single-thread, I could see it being no other way. No chance for schedule racing.
In short, I suppose I just need to do more research, and I was curious if anyone else had already done some of the legwork first. :) I might try a few methods and see what generates the most accurate, predictable results based on wildly-variant framerates.
#3
07/25/2005 (10:01 am)
If scheduling was tied to the sceneGraph time, then you could be more assured that events would be in sync with the game. As it is, they can indeed get all out of whack and mess up your events if the framerate gets low.
#4
I'm pretty sure that you don't want what your second sentence says, as it is obviously the opposite of what your first sentence (and all of your other sentences) have been pointing to. You actually want the event to happen at a specific time interface, to the precision of frame advances.
This is much more dooable. As I said, you can simply use onSceneUpdate. If you want the event to happen every 500 milliseconds, simply have a counter in onSceneUpdate wait until 500 or more milliseconds have passed, fire the event, and subtract 500 from the counter. That way, you are guarenteed that the event will fire on time every time, to the extent that you're getting regular frame advance ticks.
Even if your framerate is 4fps, you'll still get an event every 2 frames. At 3fps, you'll get alternating events, where the event will fire 2 frame out of three. Since your granularity is so small, you can't expect better of the system.
Note that I don't think the physics/collision code holds up well to ultra-low framerates. A low framerate is the effective equivalent to a high velocity, and the physics/collision can't handle high velocities well. It's best to keep your framerate above 20, and if you can't, just hack into the physics system and lie to it about the time ;)
07/25/2005 (12:29 pm)
Quote:I just want it to occur at the same frequency regardless of ingame iteration rate. In other words, if the game runs 2x as slow, that event should also fire 2x as slow.
I'm pretty sure that you don't want what your second sentence says, as it is obviously the opposite of what your first sentence (and all of your other sentences) have been pointing to. You actually want the event to happen at a specific time interface, to the precision of frame advances.
This is much more dooable. As I said, you can simply use onSceneUpdate. If you want the event to happen every 500 milliseconds, simply have a counter in onSceneUpdate wait until 500 or more milliseconds have passed, fire the event, and subtract 500 from the counter. That way, you are guarenteed that the event will fire on time every time, to the extent that you're getting regular frame advance ticks.
Even if your framerate is 4fps, you'll still get an event every 2 frames. At 3fps, you'll get alternating events, where the event will fire 2 frame out of three. Since your granularity is so small, you can't expect better of the system.
Note that I don't think the physics/collision code holds up well to ultra-low framerates. A low framerate is the effective equivalent to a high velocity, and the physics/collision can't handle high velocities well. It's best to keep your framerate above 20, and if you can't, just hack into the physics system and lie to it about the time ;)
#5
@Smaug: I don't think realtime is what matters. It's all relative to graph time. Physics are all processed on graph time, and events often need to be synchronized to some future event that is defined by the physics engine, but when the scheduled event is not directly tied to graph time, as the physics engine is, it can fire at a very different time than expected.
If I set a missile moving at a given velocity and I know its target is a given distance away, I can predict when the collision will occur and set a scheduled event to generate an explosion effect at that location at that time. But there's currently a disconnect between the physics engine that drives the projectile and the schedule that triggers the explosion. There needs to be some guarantee that these kinds of scheduled events will correspond to physics-based motion regardless of system lag. Under normal load, the projectile may take 2 seconds to reach its target. When the system is lagging, the projectile may take 4 seconds. If I've scheduled an explosion in 2 seconds, I don't want it to trigger when the projectile is half-way there. Make sense?
I constantly see these kinds of issues in my game, and my framerate seldom drops below 60 fps.
I don't care if it takes twice as long as it should for something to happen, as long as everything is synchronized. I believe that's also what David is dealing with.
08/04/2005 (10:02 pm)
"I'm pretty sure that you don't want what your second sentence says, as it is obviously the opposite of what your first sentence (and all of your other sentences) have been pointing to. You actually want the event to happen at a specific time interface, to the precision of frame advances."@Smaug: I don't think realtime is what matters. It's all relative to graph time. Physics are all processed on graph time, and events often need to be synchronized to some future event that is defined by the physics engine, but when the scheduled event is not directly tied to graph time, as the physics engine is, it can fire at a very different time than expected.
If I set a missile moving at a given velocity and I know its target is a given distance away, I can predict when the collision will occur and set a scheduled event to generate an explosion effect at that location at that time. But there's currently a disconnect between the physics engine that drives the projectile and the schedule that triggers the explosion. There needs to be some guarantee that these kinds of scheduled events will correspond to physics-based motion regardless of system lag. Under normal load, the projectile may take 2 seconds to reach its target. When the system is lagging, the projectile may take 4 seconds. If I've scheduled an explosion in 2 seconds, I don't want it to trigger when the projectile is half-way there. Make sense?
I constantly see these kinds of issues in my game, and my framerate seldom drops below 60 fps.
I don't care if it takes twice as long as it should for something to happen, as long as everything is synchronized. I believe that's also what David is dealing with.
#6
Maybe this will be a solution?
Although it doesn't explicitly state that this is sceneTime based...
08/05/2005 (5:37 am)
From Melv's blog about the next update:Quote:
Another small improvement but handy enough to mention here is the ability for any T2D object to start its own periodic timer using "setTimerOn(time)". This will produce a script callback to "object::onTimer(%this)" in the specified period. When you're finished you can just turn it off. This can be very handy in many situtations.
Maybe this will be a solution?
Although it doesn't explicitly state that this is sceneTime based...
#7
That sounds like a bug, then. Produce a test-case and notify Melv. The entire game should be synchronized to the same clock, except possibly at exceedingly low framerates (and I doubt even then).
08/05/2005 (1:02 pm)
Quote:I constantly see these kinds of issues in my game, and my framerate seldom drops below 60 fps.
That sounds like a bug, then. Produce a test-case and notify Melv. The entire game should be synchronized to the same clock, except possibly at exceedingly low framerates (and I doubt even then).
#8
We're probably still talking about the same issue. I'm talking about a typical framerate of 60 or better, with occasional drops that cause these problems. So it's still low framerate that's causing it. But getting back to the original point, if everything is properly keyed off of graph time and not framerate, lags will be uniform and, in my example, I'd never see my sprite rotate beyond 90 degrees.
08/06/2005 (9:11 am)
@Smaug: I hope you're right, but from everything I've heard, schedule simply isn't meant to be bulletproof. You'll get the callback at approximately the requested delay, but there are no guarantees. It seems like schedule is the first to give whenever there's any lag. Typically when I see a problem, it's when I have very short lags caused by system load (like when my antivirus software starts churning). I mentioned this in another post, but one good example is a turning method I use where I rotate my sprite smoothly over 90 degrees rather than just doing a hard turn. I set the angular velocity to cover 90 degrees in about 250 ms, then I schedule a callback in 250 ms that returns the angular velocity to 0 and sets my angle to the target value. Normally, this works great. Now and then, my sprite does a spin.We're probably still talking about the same issue. I'm talking about a typical framerate of 60 or better, with occasional drops that cause these problems. So it's still low framerate that's causing it. But getting back to the original point, if everything is properly keyed off of graph time and not framerate, lags will be uniform and, in my example, I'd never see my sprite rotate beyond 90 degrees.
#9
It's critical in my game that I tie a scheduled event (however it's done) to the physical engine, otherwise everything will move too fast/too slow. I am not sure if the new setTimerOn is related to scene graph time, or not, though I would suspect it is, otherwise it's no different than just using a schedule.
If I set a schedule, it's as John says -- that scheduled event will fire in 50 ms or whatever, regardless of the state of the T2D physics sim.
08/06/2005 (11:13 pm)
Thanks, John, for explaining that in a much better way. :) Yes, I meant graph time -- I want some event that is syncronized to physics (and other triggers) that are used by the T2D scenegraph. That is what I was trying to explain about a fixed frequency -- in relation to the scene graph time.It's critical in my game that I tie a scheduled event (however it's done) to the physical engine, otherwise everything will move too fast/too slow. I am not sure if the new setTimerOn is related to scene graph time, or not, though I would suspect it is, otherwise it's no different than just using a schedule.
If I set a schedule, it's as John says -- that scheduled event will fire in 50 ms or whatever, regardless of the state of the T2D physics sim.
#10
08/07/2005 (2:15 pm)
So, why haven't you done as I suggested? That is, submit a bug on it to Melv, preferably with sample code that he can use to reproduce it.
#11
Of course, this is a kludgy in-script solution. I'm personally trying to avoid modifying the engine for my first game, for a number of reasons. Otherwise, it would be much better to hook a specialized scene timer into the subUpdateScene method of fxSceneGraph2D.cc, at about the same place where "pSceneObject2D->integrateObject( mSceneTime, elapsedTime, pDebugStats );" is called (where the animation gets updated, along with some other things). The primary advantage of this solution would be to do away with any unnecessary overhead associated with animation processing. For my purposes, though, the in-script solution seems adequate.
Hope this makes sense and you find it useful.
08/09/2005 (1:58 pm)
@David: I'm not sure if this is something that you could benefit from, but I just hacked together a pretty good timer substitute using fxAnimatedSprite2D. Since you can specify the time for a single iteration of an animation, and you also have the OnAnimationEnd callback, you have the makings for a fairly accurate, if somewhat bloated, timer event. In order to schedule the event, set the AnimationTime value to your time delay, AnimationCycle to false, fire up the animation, and capture the onAnimationEnd event. The onAnimationEnd callback is tightly bound to graph time because the animation update is processed within the scene update function, right along with collisions and other high priority systems. I put together a general sceneTimer(...) method that allows me to toss off one of these timers very easily, and the result has been good. My rotation problem went away. Under test cases that caused my character to rotate at least 4 times beyond the target rotation, this new timer nails it. The entire simulation will lag, and the timer will still fire at the projected time relative to the simulation.Of course, this is a kludgy in-script solution. I'm personally trying to avoid modifying the engine for my first game, for a number of reasons. Otherwise, it would be much better to hook a specialized scene timer into the subUpdateScene method of fxSceneGraph2D.cc, at about the same place where "pSceneObject2D->integrateObject( mSceneTime, elapsedTime, pDebugStats );" is called (where the animation gets updated, along with some other things). The primary advantage of this solution would be to do away with any unnecessary overhead associated with animation processing. For my purposes, though, the in-script solution seems adequate.
Hope this makes sense and you find it useful.
#12
Now that I have this working, I'll go ahead and post some code for anyone interested in taking this kind of approach.
08/09/2005 (3:47 pm)
Update: I went ahead and hooked this sceneTimer system into all of my time-critical schedule events (and there are MANY of them) to see how much of a performance hit I'd take. I'm pretty happy with the results. Obviously, an engine-level scene timer that works like the animation timer is a preferable solution, and I can't wait until that makes it into the engine, but for now, this solution has shown me how important precise timing is. Several problems I had been experiencing have gone away. For example, an important element of most games is that they be deterministic, but schedule events tend to add an element of randomness to your events (schedule is influenced by system load which is typically caused by external factors), and determinism goes out the window. This new solution may not be perfect, but I'm finding that events are much more repeatable using the sceneTimer method. For my game, at least, that's an important improvement.Now that I have this working, I'll go ahead and post some code for anyone interested in taking this kind of approach.
// Set up a dummy animation. There's no reason not to use any other existing animation. Set it to use a single frame.
datablock fxAnimationDatablock2D(timerAnimation)
{
imageMap = dummyImageMap;
animationFrames = "1";
animationCycle = false;
randomStart = false;
};
// This is the function that creates your timer. It's like schedule, only you pass the object the timer is associated with. Event will receive this
// object as well as a param if you provide one.
function sceneTimer( %obj, %delay, %event, %param )
{
// Use animation system for scenetime based timer
%timer = createTimerObject( %delay, %event, %param );
%timer.owner = %obj;
return %timer;
}
// The function that creates the timer animation object
function createTimerObject( %delay, %event, %param )
{
%obj = new fxAnimatedSprite2D() { scenegraph = sgMazeGraph; };
%obj.tag = "timer";
%obj.isTimer = true;
%obj.Event = %event;
%obj.Param = %param;
%obj.setVisible( false );
%obj.setCollisionActive( false, false );
%obj.setCollisionCallback( false );
timerAnimation.animationTime = %delay/1000;
%obj.playAnimation( timerAnimation, false );
return %obj;
}
// Use this if you want to cancel the timer. The timer object is returned from sceneTimer, and you must pass it to this function.
function cancelTimer( %timer )
{
%timer.canceled = true;
}
// This is the event that gets called from the onAnimationEnd callback.
function onSceneTimer( %obj )
{
if ( !%obj.canceled && isObject( %obj.owner ) )
call( %obj.event, %obj.owner, %obj.param );
%obj.safeDelete();
}
// This is your onAnimationEnd callback. If you already have one, just add these lines near the top to capture your timer objects
function fxSceneObject2D::onAnimationEnd( %Obj )
{
if ( %obj.isTimer )
onSceneTimer( %obj );
}
#13
Oh, and your version of onAnimationEnd ought to return a bool so that the person overriding catching onAnimationEnd can determine if it did or did not catch the animation ending as a scene timer or if it was a regular sprite.
BTW, you may want to put all of these animation scene timer objects in their own little scene graph. That way, they're not cluttering up your own scene graph and potentially being thought about during normal game processing.
08/09/2005 (5:17 pm)
None of this explains why a bug hasn't been submitted on this issue.Oh, and your version of onAnimationEnd ought to return a bool so that the person overriding catching onAnimationEnd can determine if it did or did not catch the animation ending as a scene timer or if it was a regular sprite.
BTW, you may want to put all of these animation scene timer objects in their own little scene graph. That way, they're not cluttering up your own scene graph and potentially being thought about during normal game processing.
#14
And thanks for the suggestions. If anyone else decides to implement the system, I'm sure that'll be helpful. This implemention is pretty specific to my own needs.
08/09/2005 (5:48 pm)
@Smaug: I know that Melv is very aware of this situation, and I choose not to bother him with it.And thanks for the suggestions. If anyone else decides to implement the system, I'm sure that'll be helpful. This implemention is pretty specific to my own needs.
#15
It works good for single-fire events, but there's an issue with using it for repeating events. For some reason, a playAnimation() call will not work inside of a onAnimationEnd() callback.
I've managed to get something working by using onUpdateScene().
For example:
That works pretty good, even in low frame rate situations, but requires modifications to fxSceneGraph2D::updateScene (in fxSceneGraph2D.cc).
You need to make sure that the onUpdateScene callback is executed once per physics tick. Normally, this is called at the end of the scene processing, and is only fired once regardless of any physic interations that need to occur for the world to "catch up" to the target FPS. This is fine if your physics sim frame rate equals limit FPS, but anything below that and onUpdateScene() gets called just once.
This of course means that any code inside of your script callback is now out of sync with physics sim.
A quick and dirty hack is to just move the onExecute call into the appropriate areas. For example:
And later:
And just remove the call to onUpdateScene at the very end of the method:
Still testing this, though. But it seemed to work fine for my game, even in very low FPS situations (<10fps).
With this modification, it now seems to be possible to get very accurate timing information from onUpdateScene(), which does not vary due to frame rate.
08/25/2005 (5:16 pm)
Okay, I've had a chance to play with this for a bit more, and I've tried John's method.It works good for single-fire events, but there's an issue with using it for repeating events. For some reason, a playAnimation() call will not work inside of a onAnimationEnd() callback.
I've managed to get something working by using onUpdateScene().
For example:
function fxSceneGraph2D::onUpdateScene (%this)
{
if (cbSceneGraph2D.getSceneTime() >= $nextSceneTick)
{
// do my time sensitive stuff right here
do_stuff () ;
$nextSceneTick = cbSceneGraph2D.getSceneTime() + 0.01 ; // 10 msecs
}
}That works pretty good, even in low frame rate situations, but requires modifications to fxSceneGraph2D::updateScene (in fxSceneGraph2D.cc).
You need to make sure that the onUpdateScene callback is executed once per physics tick. Normally, this is called at the end of the scene processing, and is only fired once regardless of any physic interations that need to occur for the world to "catch up" to the target FPS. This is fine if your physics sim frame rate equals limit FPS, but anything below that and onUpdateScene() gets called just once.
This of course means that any code inside of your script callback is now out of sync with physics sim.
A quick and dirty hack is to just move the onExecute call into the appropriate areas. For example:
// Yes, so are we outside the target time?
if( elapsedTime >= targetFPSTime)
{
// Yes, so we're going to calculate the target time only to subtract this from the underflow.
mElapsedUnderflow = elapsedTime - targetFPSTime;
// Clamp to zero!
if ( mIsZero(mElapsedUnderflow) )
mElapsedUnderflow = 0.0f;
// Do a full integration of the elapsed time.
subUpdateScene(targetFPSTime, &mDebugStats);
// Expose a kind of main-loop to scripts.
[b]Con::executef( this, 1, "onUpdateScene" );[/b]And later:
// Calculate Iterations for now.
// NOTE:- We'll cap iterations here to stop things getting out of hand.
U32 iterations = (U32)getMin( (S32)(elapsedTime / targetFPSTime), (S32)mScenePhysicsMaxIterations );
// Calculate Underflow.
mElapsedUnderflow = getMax(0.0f, elapsedTime - (targetFPSTime * F32(iterations)));
// Do the iterations.
for ( U32 n = 1; n < iterations; n++ ) {
subUpdateScene(targetFPSTime, &pDummyDebugStats);
// Expose a kind of main-loop to scripts.
[b]Con::executef( this, 1, "onUpdateScene" );[/b]
}And just remove the call to onUpdateScene at the very end of the method:
// Calculate Collision Hit Percentage ( if any ). if ( mDebugStats.objectPotentialCollisions != 0 ) mDebugStats.objectHitPercCollisions = mDebugStats.objectActualCollisions * ( 100.0f / mDebugStats.objectPotentialCollisions ); else mDebugStats.objectHitPercCollisions = 100.0f; // Update Debug Stats. calculateDebugStats(); // Expose a kind of main-loop to scripts. [b]//Con::executef( this, 1, "onUpdateScene" );[/b]
Still testing this, though. But it seemed to work fine for my game, even in very low FPS situations (<10fps).
With this modification, it now seems to be possible to get very accurate timing information from onUpdateScene(), which does not vary due to frame rate.
#16
why dont you write a new consoleFunction that retrieves the system time, and use that for calculating if a callback should be executed?
08/25/2005 (5:26 pm)
So, i dont get it... am I missing something here?why dont you write a new consoleFunction that retrieves the system time, and use that for calculating if a callback should be executed?
#17
If this isn't the intention of onUpdateScene(), well, one could make a new callback easily enough and use that instead. But I was assuming that onUpdateScene() would be fired at least once for every possible execution of the sim engine, otherwise trying to schedule events that must be precisely timed are impossible using onUpdateScene().
Maybe Melv could clarify the intention of onUpdateScene() and timing within Torque. For all I know, he's already fixed this, or has some other system to handle per-tick events.
08/25/2005 (5:57 pm)
If by system time, what are you referring to? Torque main loop time? T2D frame time? T2D physics sim time? (The first two appear to be the same.) That code fix above patches the onUpdateScene() callback so it will not only execute once per T2D frame time, but also for every iteration of T2D physics sim slice.If this isn't the intention of onUpdateScene(), well, one could make a new callback easily enough and use that instead. But I was assuming that onUpdateScene() would be fired at least once for every possible execution of the sim engine, otherwise trying to schedule events that must be precisely timed are impossible using onUpdateScene().
Maybe Melv could clarify the intention of onUpdateScene() and timing within Torque. For all I know, he's already fixed this, or has some other system to handle per-tick events.
#18
instead of trying to use various creative ways to extrapulate what the 'gametick' time is, why not just trigger the callbacks by system (computer) time... that seems to be what people here are trying to do anyway.
Again, am I missing something here? this seems so incredibly simple compared to the solutions being proposed that I honestly wonder if I am missing some key detail.
08/25/2005 (6:18 pm)
@David: i mean system time, as in your computer's time.instead of trying to use various creative ways to extrapulate what the 'gametick' time is, why not just trigger the callbacks by system (computer) time... that seems to be what people here are trying to do anyway.
Again, am I missing something here? this seems so incredibly simple compared to the solutions being proposed that I honestly wonder if I am missing some key detail.
#19
What I was trying to explain was that I wanted some scheduled even that would be %100 in sync with the physics of the game. So, if I needed to manipulate an object's physical properties (change angular/linear momentum, spawn a new one, etc) I could do so, without worrying about my timing being off if there was a frame rate fluctuation caused by a slower system, or something stealing cycles from Torque 2D. Currently, using onUpdateScene (the only way to get a reoccurring event that is tied to the scene graph at all) wasn't precise enough, even with using getSceneTime().
This is because if the physical sim "got behind" and needed to catch up, this wasn't reflected by additional calls to onUpdateScene.
BTW, my code changes only work if you are using FPS syncronization. (setScenePhysicsFPSActive set to true and the appropriate limits/targets set.)
08/25/2005 (6:53 pm)
Ignore my original post and the few after that, I was not explaining it properly. :) John Pritchett did a much better job!What I was trying to explain was that I wanted some scheduled even that would be %100 in sync with the physics of the game. So, if I needed to manipulate an object's physical properties (change angular/linear momentum, spawn a new one, etc) I could do so, without worrying about my timing being off if there was a frame rate fluctuation caused by a slower system, or something stealing cycles from Torque 2D. Currently, using onUpdateScene (the only way to get a reoccurring event that is tied to the scene graph at all) wasn't precise enough, even with using getSceneTime().
This is because if the physical sim "got behind" and needed to catch up, this wasn't reflected by additional calls to onUpdateScene.
BTW, my code changes only work if you are using FPS syncronization. (setScenePhysicsFPSActive set to true and the appropriate limits/targets set.)
#20
08/25/2005 (7:13 pm)
Ahh, i understand now. indeed, I was missing a key piece of the question :)
Torque Owner Smaug
Well, getting an "exact rate" for some event isn't going to happen through regular code; not on a pre-emptive multitasking OS, at any rate. Not without giving your process super-high priority.
The only way you can have a sound happen constantly at a particular time is to loop the sound with the delay built into the sound file. That is, if you want the sound to happen every half-second, make a half-second sound file that has the real data plus a lot of dead air. Then set this sound to play continuously. Only this
Other than that, you could use onSceneUpdate (it should give you a frame delta. Probably in integer milliseconds), and simply whenever you collect some number of time units, play your sound. It will never be more than a frame off of where it should be, nor will it ever start lagging behind.