This page is a work in progress and could be pending updates.

TickTimer

Introduction

It is often useful to wait for some period of time before triggering certain logic. TickTimers are a safe, convenient, and bandwidth efficient way to do this in Fusion. Despite its name, TickTimer is not really a timer, but rather a struct which stores a target tick value and run checks using the current simulation tick.

Using the current tick (from the NetworkRunner) and the target tick (saved when initializing the TickTimer), the TickTimer can provide information much like any other timer does and answer questions such as:

  • has the timer expired?
  • is the timer running?
  • how much time / many ticks are left?

Since the TickTimer uses the target tick instead of incrementing or decrementing a float or int, it uses less bandwidth when it is included as part of the [Networked] game state since it does not change and the timer logic is local.

Note: TickTimer uses a target tick, even when initializing it with TickTimer.CreateFromSeconds(). With this and the tick rate in mind, it is possible for the calculated target tick to lose a bit of accuracy depending on the amount of seconds passed, e.g TickTimer.CreateFromSeconds(1.37f).

Back To Top

API

  • CreateFromSeconds: Returns a new TickTimer with the target tick calculated using the amount of Seconds provided and the current simulation tick.
  • CreateFromTicks: Returns a new TickTimer with the target tick calculated using the amount of Ticks provided and the current simulation tick.
  • RemainingTime: Returns the remaining time in Seconds until the TickTimer expires.
  • RemainingTicks: Returns the remaining time in Ticks until the TickTimer expires.
  • IsRunning: Returns true if the TickTimer target tick is greater than the current simulation tick.
  • Expired: Returns true if the TickTimer is running and the target tick is less or equals than the current simulation tick.
  • ExpiredOrNotRunning: Returns true if the TickTimer is default, TickTimer.None, or running and the target tick is less or equals than the current simulation tick.

Back To Top

Reset The TickTimer

A TickTimer is expired returns true every tick after the simulation tick has become greater than the target tick stored by it. To get this only once, is useful to reset the TickTimer after the first tick on which this condition is evaluated as true. This can be done by setting the TickTimer to TickTimer.None or default.

[Networked] TickTimer timer { get; set; }

void FixedUpdateNetwork()
{
  if (timer.Expired(Runner))
  {
    // Execute Logic

    // Reset timer
    timer = TickTimer.None;
    // alternatively: timer = default.

    Debug.Log("Timer Expired");
  }
}

Back To Top

Creating Custom Functionalities

Given the simple nature of TickTimer, it is easy to create a custom version of it and implement more or different functionalities. The two following examples - Counting up and Normalized time - are possible simply by adding the initial tick on the moment the timer was created.

Note: The following code is a custom and simplified version of the original TickTimer to showcase functionalities which can be implemented in custom versions.

public struct CustomTickTimer : INetworkStruct
{
  private int _target;
  private int _initialTick;

  public bool Expired(NetworkRunner runner) => runner.IsRunning && _target > 0
    && (Tick) _target <= runner.Simulation.Tick;

  public bool IsRunning => _target > 0;

  public static CustomTickTimer CreateFromTicks(NetworkRunner runner, int ticks)
  {
    if (runner == false || runner.IsRunning == false)
      return new CustomTickTimer();

    CustomTickTimer fromTicks = new CustomTickTimer();
    fromTicks._target = (int) runner.Simulation.Tick + ticks;
    fromTicks._initialTick = runner.Simulation.Tick;
    return fromTicks;
  }

  public float NormalizedValue(NetworkRunner runner)
  {
    if (runner == null || runner.IsRunning == false || IsRunning == false)
      return 0;

    if (Expired(runner))
      return 1;

    return ElapsedTicks(runner) / (_target - (float)_initialTick);
  }

  public int ElapsedTicks(NetworkRunner runner)
  {
    if (runner == false || runner.IsRunning == false)
      return 0;

    if (IsRunning == false || Expired(runner))
      return 0;

    return runner.Simulation.Tick - _initialTick;
  }
}

Back To Top

Counting Up

To get the elapsed number of ticks since the timer was created, subtract the initial tick value from the NetworkRunner's current tick.

public int ElapsedTicks(NetworkRunner runner)
{
  if (runner == false || runner.IsRunning == false)
      return 0;

  if (IsRunning == false || Expired(runner))
    return 0;

  return runner.Simulation.Tick - _initialTick;
}

Back To Top

Normalized Value Between 0 And 1

To return a normalized float between 0 and 1 for the timer's current progress, divide the number of elapsed ticks by the total number ticks that the timer is counting.

public float NormalizedValue(NetworkRunner runner)
{
  if (runner == null || runner.IsRunning == false || IsRunning == false)
    return 0;

  if (Expired(runner))
    return 1;

  return ElapsedTicks(runner) / (_target - (float)_initialTick);
}

Now, the CustomTickTimer can be used to show those values.

[Networked]
public CustomTickTimer MyTickTimer { get; set; }

public void StartTimer()
{
  if (Runner.IsServer)
  {
    MyTickTimer = CustomTickTimer.CreateFromTicks(Runner, 120);
  }
}

public override void FixedUpdateNetwork()
{
  Debug.Log($"Elapsed {MyTickTimer.ElapsedTicks(Runner)} ticks.");
  Debug.Log($"Normalized Value {MyTickTimer.NormalizedValue(Runner)}.");

  if (MyTickTimer.Expired(Runner))
  {
    // Execute Logic

    // Reset timer
    MyTickTimer = default;

    Debug.Log("Timer Finished on tick: " + Runner.Simulation.Tick);
  }
}


To Document Top