How To Cog - Chapter 3.9 - Timers and Pulses

Timers and Pulses are very useful in creating effects at a later time than the present. With normal programming, this idea would never be considered - when an application needs to do something, it will perform the action as soon as it can. But with a game engine, you'll need delays in your code to make effects seem more natural or continuous.

For creating a delayed effect, Cog provides timers. These are special events which are triggered when a countdown timer expires. For creating a continuous effect, Cog has pulses. Pulses are much like timers, but pulses keep going. After the pulse countdown timer expires and the pulse event occurs, the timer is automatically reset.

Using Timers

The verbs used to set timers are SetTimer(), SetTimerEx(), and SetThingTimer(). SetTimer() is the simple version - it has one parameter for the delay. You would use SetTimer() if you're only going to have one timer at a time. SetTimerEx() allows you to give your timers an ID and two parameters along with the delay. SetThingTimer() works much like SetTimer() except that you're setting a timer for a thing instead of setting a timer for a cog.

As soon as a timer is set, JK will start a countdown for the timer's delay. When the countdown ends, the timer is said to expire and a timer event message is sent. If this is a simple or extended timer, then only the cog you set the timer in will receive the message. If this is a thing timer, then all of the thing's associated cogs will receive the message. For extended timers, you can retreive the ID and parameter values with GetSenderID() and GetParam(). If this is a thing timer, you can use GetSenderRef() to find the thing that sent it.

If you need to stop an extended timer before it expires, you can use KillTimerEx(). The one parameter it needs is the timer's ID. Timers set by SetTimer() and SetThingTimer() need to be stopped with SetTimer(0) and SetThingTimer(thing, 0). As an example for using timers, here's LEC's xtank1.cog:

damaged:
   barrel = GetSenderRef();
   damage = GetParam(0);
   barrelhealth = GetThingUserData(barrel);

   if(GetParam(1) == 1) Return;              // barrel won't be damaged by damage type impact

   if(barrelhealth <= damage)                // is this enough damage to kill the barrel ?
   {
      SetTimerEx(0.5, barrel, 0, 0);         // prepare to kill the barrel in 0.6 second
      Return;
   }

   SetThingUserData(barrel, barrelhealth - damage);
   Return;

# ............................................................................................

timer:
   KillTimerEx(GetSenderId());
   CreateThing(barrel_exp, GetSenderId());   // create an explosion
   DestroyThing(GetSenderId());              // Destroy the barrel
   Return;

This is the same cog mentioned in the threads tutorial when talking about sleeps. If you explode the barrel immediately after it's damaged, then players won't have time to get away and you'd lose that element of suspense. Using a sleep here would be bad because you can easily have more than five barrels blow up at the same time - the thread limit would be exceeded and not all of the barrels would blow up.

The solution is to use timers. You need to use extended timers because more than one barrel can be waiting to explode. To get something unique for a timer's ID, you can use the barrel's thing number. And in the timer handler, you can retrieve the ID and use it to identify the barrel.

KillTimerEx() is used to stop a timer that has the same ID as the one that has expired. This is done as a safeguard in case two timers were set for the same barrel. You can't destroy something twice, but there could be other consequences. Thing numbers are not unique to one object in a game. Once an object is removed from gameplay, a thing number is free to be reused. So you may need to check a thing's signature to make sure your thing hasn't been destroyed and replaced by something.

Using Pulses

A pulse works just like a timer set with SetTimer(). The difference is that a pulse automatically resets itself after its timer expires. If you need to create a continuous effect for an extended period of time, then you'll probably want to run the same code every so many seconds. Pulses are more convenient because you don't have to reset them and using a pulse frees up timer messages for other things.

A pulse is set with SetPulse() or SetThingPulse(). They are stopped with SetPulse(0) and SetThingPulse(0). SetPulse() works with just one cog like SetTimer(). SetThingPulse() works with a thing like SetThingTimer().

Say you want to create a rain effect in part of your level. You need to create rain objects and make them fall towards the floor. When they touch a surface, they can be removed. This effect needs the same code to run in short intervals. For example:

#--------------------------------------------------------
startup:
	SetPulse(burstInterval);

Return;
#--------------------------------------------------------
pulse:
	for(i=0; i < numBurstElements; i=i+1) SetTimerEx(Rand(), Rand() * 6, 0, 0);

Return;
#--------------------------------------------------------
timer:
	if(GetSenderID() > 5) Return;	// just in case Rand() actually returns 1
	parent = ghost0[GetSenderID()];
	precip=FireProjectile(parent, precipTemp, -1, -1, RandVec(), '0 0 0', elementSpeedMult, 0x1, 0, 0);
	SetThingVel(precip, VectorSet(xMove, yMove, VectorZ(GetThingVel(precip))));

Return;
#--------------------------------------------------------

Both pulses and timers are used to create the rain effect. Timer doesn't have to be used, the pulse could be a little bit faster, but this rain implementation is using bursts with different intervals than the raindrops in the burst. This example shows that by using a pulse, you can use timers for other things.


Previous: Parsing Up: How To Cog Next: Review
  • Create:
This page was last modified 16:28, 14 April 2006.   This page has been accessed 1,509 times.