Download

Do timers have an advantage over handling it manually in the Tick() method?

I have been using Timers a lot in UE4, like seen a lot in examples.

In my old code, based on the ShooterGame i used a timer for re-firing the weapon, which works perfectly.
But as im extending my code, implementing more weapons i need to make some changes to handle “continuous” damage weapons (pepperspray or for example firethrower)

For firing weapons, instead of using a timer i implemented this: (yes, the FiredWeapon class name is based on the UE2 equipment model, FiredWeapon, ThrownWeapon, UsedOnOthers, … :D)



void ASwatFiredWeapon::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	if (RefireDelay > 0.0f)
	{
		ElapsedRefireDelay += DeltaTime;
		if (ElapsedRefireDelay >= RefireDelay)
		{
			OnFireWeapon();
			ElapsedRefireDelay = 0.0f;
		}
	}
	else
	{
		OnFireWeapon();
	}
}


So, i was wondering, are there any pro’s/con’s on using timers or doing it manual in the Tick method?

NOTE: The code sample is missing if (bIsFiring) … the code sample is a concept

I guess timers are “unreliable”. The tick is there every frame and can not be missed, whereas timer events may or may not be skipped in a frame (or in multiple frames).

Timers are just as reliable and they won’t not-fire, they’re critical to a lot of things the engine does so you needn’t worry about that. I use them all the time personally.

One huge advantage of timers is that they are ticked by a single object, the FTimerManager which is a static non-copyable object. The beauty is that you don’t need to ‘Tick’ your weapon if you use a timer, but you can still perform time-based operations. Ticking is something you want to use as little as you can get away with. It’s more efficient to tick one object with hundreds of operations than it is to tick multiple objects with fewer operations (generally speaking).

In terms of creating continous-fire weapons, the best way to achieve this is to fake it. Don’t fire a projectile every frame because your weapon and the damage it deals then becomes tied to the framerate. My approach would be to spawn a single ‘Projectile’ like a cone of death, and a particle system that matches it as best as possible. The cone-of-death can deal ‘DamagePerSecond * DeltaSeconds’ - so youre weapon does consistent damage regardless of framerate. In fact, you probably don’t even need to spawn a projectile at all, make the damage area part of the weapon itself.

Thanks for the answers,

It sounds logical that calling a lot of Tick methods is less efficient, so using timers is probably better.
The damage dealing is a bit a special case in my project. I don’t have a flamethrower like i put as example in the initial post, but the game does have pepperspray.
This is not a “weapon” that cause damage to the health, it has a damage type that causes a stunning effect with effect duration. It’s also a pretty close range weapon.
It does not spawn a projectile but uses tracing instead. This also makes the tick less efficient cause it does the trace every tick :o

As the pepperspray damage causes the effect for a certain amount of time, using a timer with 0.1 rate would be more efficient?

Yes. Every bit of logic that you can move out of the Tick method is worth the effort.
On one hand, slowing down a Tick slows down your game. If you do a trace on Tick, you will do it i.e. 60 times per second. This alone is not a problem yet, but if you continue this pattern and suddenly got thousands of traces through various Tick methods, you will start getting troubles.

On the other hand, imagine if you got a performance spike on some pc’s (maybe you are streaming in a big level why something else happens) and there’s no tick within the RefireDelay (i.e. if it is set to 0.1s). What will happen? If one Tick suddenly takes 0.3s, will you shoot just 1 bullet or 3 at once? In any case, when doing stuff inside the Tick, it will become framerate dependent one or the other way, and you dont want that to happen :slight_smile:

Cheers,
Indy

Tracing isn’t that expensive, you can get away with doing a fair few of them even when ticking. It’ll definitely be cheaper than creating a projectile every frame because actor spawning IS expensive. I doubt you’ll hit a perf limit with Line traces but the only way to find out is to profile it using the STAT_CYCLE_COUNTER macros and the profiling tool.

AFAIK the delegates from Timers don’t ‘Stack’ depending on Framerate, so it will only fire once. However, if you multiply the damage or the intensity of the effect by DeltaSeconds it won’t matter anyway, since the deltatime between those slower frames will make up for the lost firing of line traces and multiply the intensity of the effect to make up for it.

In this case, if you actor is already ticking, I would do the traces on tick and multiply the damage by delta time. If it’s not already ticking, try using a timer and multiply the intensity by DeltaSeconds * (1 / TimerDelay) - that should keep the damage-per-second the same regardless of framerate and missed ticks, but do check my logic on that!

Also, you probably want to ensure it’s not ticking by doing this in the constructor just to be certain:



PrimaryActorTick.bCanEverTick = false


Ok, i chose a bad example :slight_smile:
Though, doing stuff in the Tick shouldnt be encouraged and more of an exception than a pattern.

I mean, its quite funny how people react to having cast-nodes in blueprints like “Omg, you got 10 casts in your Tick method, your game will be so slow!!!”, but hundreds or thousands of traces within a Tick are ok :wink:

This was actually a rethorical question :wink:
With the logic from the first post it would just fire once when called from the Tick, missing out some firing (admitting that he said that its just sample code). And for the effect with the pepper spray you are right, but for projectiles its a whole different story (as you said). In that case, simply firing all missed bullets inside one Tick would be bad, as this would hardly be the same as firing at a fixed time rate (imagine a moving target, with firing bullets framerate dependent, you would never be really accurate).

What i dont really get is why he should multiply by DeltaSeconds * (1 / TimerDelay)? I mean, if he would want to have i.e. 10 dmg/s he would just do DmgPerSec / TimerDelay on each iteration of the timer. But maybe i am missing something here :slight_smile:

Cheers,
Indy

It’s because the timers themselves are Ticked, and they won’t fire ‘Dead On’ every 0.1 second, they too are mildly affected by the frames. If for example you have a timer that fires every 0.1 seconds, but the game stalls for 0.5 seconds, it’ll only fire once at the end of that stall.

At least, that’s in my experience anyway from what I remember - but definitely test it, I could be mistaken. It may actually cumulatively add the remaining frame times and stack them up, but I doubt it.

What would be a good way to “stall” a frame? just using plain c++ sleep method?

As for the 0.1 timer firing only 1 time if delayed for 0.5 sec, that will be the same with the Tick implementation is made.
When the time elapsed it fire once, reset the elapsed time and goes to the next frame

The main reasson i want to keep tracing something like the pepperspray is because the player might be holding the fire button while rotating to hit more as 1 enemy

Definitely double check that bit on the timers, because I could be wrong. As for stalling, you could just load in a streaming level with loads of unbuilt lights or something as a quick hack to make the game lag a bit.

Providing you take delta time into account, the damage would remain the same no matter how low or varied the framerate. However, if you rotated ‘through’ an enemy during a single frame, you wouldn’t apply damage to them unless you also did a sweep from the last trace point to the new one. At that point though, your framerate is probably so low that that’s the least of your concerns…

EDIT: Okay just check Source code (thanks Kamrann) - Timers do indeed stack up the time between frames, so you don’t in fact need to factor in DeltaSeconds when using a timer. Very interesting this…

I checked the TimerManager code and it does indeed stack up the calls.
So if i set a timer to 0.1 sec and the frame takes 0.5 sec, the function will be called 5x in the same frame.
this is not really what i want cause it would trace 5 times using the same position … which will give the same result.
It would be possible to store the last frame position and time and calculate the rotation for each call, but i don’t think its worth doing so.

This is from the Tick function of the timer manager:




			// Determine how many times the timer may have elapsed (e.g. for large DeltaTime on a short looping timer)
			int32 const CallCount = CurrentlyExecutingTimer.bLoop ? 
				FMath::TruncToInt( (InternalTime - CurrentlyExecutingTimer.ExpireTime) / CurrentlyExecutingTimer.Rate ) + 1
				: 1;

			// Now call the function
			for (int32 CallIdx=0; CallIdx<CallCount; ++CallIdx)
			{ 
				CurrentlyExecutingTimer.TimerDelegate.Execute();

				// If timer was cleared in the delegate execution, don't execute further 
				if( CurrentlyExecutingTimer.Status != ETimerStatus::Executing )
				{
					break;
				}
			}


So using the Weapon Tick method seems to be the best solution for continuous damage?



void ASwatFiredWeapon::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	if (bIsFiring && CanFire())
	{
		if (RefireDelay > 0.0f)
		{
			ElapsedRefireDelay += DeltaTime;
			if (ElapsedRefireDelay >= RefireDelay)
			{
				OnFireWeapon();
				ElapsedRefireDelay = 0.0f;
			}
		}
		else
		{
			OnFireWeapon();
		}
	}
}