This cached version may be outdated.

For the latest version, please visit the live website.

Documentation

Icon Timekeeper /

A global singleton tasked with keeping track of global clocks in the scene. One and only one Timekeeper is required per scene.

The Timekeeper class follows the singleton design pattern. That's a complicated way of saying it's an object that can and must exist only once per scene. This object, called the instance, can be accessed from anywhere via Timekeeper.instance.

Properties

bool debug { get; set; }

Determines whether Chronos should display debug messages and gizmos in the editor.

int maxParticleLoops { get; set; }

The maximum loops during which particle systems should be allowed to run before resetting.

This property is a temporary performance hotfix until a better solution to particle simulation is found. You can set it to 0 or less for infinite loops, but expect some performance problems.

Methods

IEnumerable<GlobalClock> clocks { get; }

An enumeration of all the global clocks on the timekeeper.

bool HasClock(string key)

Indicates whether the timekeeper has a global clock with the specified key.

GlobalClock Clock(string key)

Returns the global clock with the specified key.

GlobalClock AddClock(string key)

Adds a global clock with the specified key and returns it.

void RemoveClock(string key)

Removes the global clock with the specified key.

Examples

Speed up the "Monsters" global clock if it exists:


if (Timekeeper.instance.HasClock("Monsters"))
{
	Timekeeper.instance.Clock("Monsters").localTimeScale = 2;
}

Create a new clock for enemies and apply it to all Enemy GameObjects:


Clock enemiesClock = Timekeeper.instance.AddClock("Enemies");
Enemy[] enemies = GameObject.FindObjectsOfType<Enemy>();

foreach (Enemy enemy in enemies)
{
	// This assumes every enemy already has a Timeline component
	Timeline timeline = enemy.GetComponent<Timeline>();
	timeline.mode = TimelineMode.Global;
	timeline.globalClockKey = enemiesClock.key;
}

Icon Clock /

An abstract base component that provides common timing functionality to all types of clocks. This means all the properties and methods of Clock are available on GlobalClock, LocalClock and AreaClock.

You will almost never use the measurements (timedeltaTime, etc.) provided by clocks directly. You should instead use the same measurements available on the Timeline class, since these combine those of all relevant clocks for each GameObject.

Properties

float localTimeScale { get; set; } = 1

The scale at which the time is passing for the clock. This can be used for slow motion, acceleration, pause or even rewind effects.

float timeScale { get; }

The computed time scale of the clock. This value takes into consideration all of the clock's parameters (parent, paused, etc.) and multiplies their effect accordingly.

Chronos is never affected by Time.timeScale. You should refrain from using that property, as it may even cause unexpected behaviour with physics. To control time globally, create a root GlobalClock instead and make all of your other clocks its descendants.

float time { get; }

The time in seconds since the creation of the clock, affected by the time scale. Returns the same value if called multiple times in a single frame.

Unlike Time.time, this value will not return Time.fixedTime when called inside MonoBehaviour's FixedUpdate.

float unscaledTime { get; }

The time in seconds since the creation of the clock regardless of the time scale. Returns the same value if called multiple times in a single frame.

float deltaTime { get; }

The time in seconds it took to complete the last frame, multiplied by the time scale. Returns the same value if called multiple times in a single frame.

Unlike Time.deltaTime, this value will not return Time.fixedDeltaTime when called inside MonoBehaviour's FixedUpdate.

float fixedDeltaTime { get; }

The interval in seconds at which physics and other fixed frame rate updates, multiplied by the time scale.

float startTime { get; }

The unscaled time in seconds between the start of the game and the creation of the clock.

bool paused { get; set; }

Determines whether the clock is paused. This toggle is especially useful if you want to pause a clock without having to worry about storing its previous time scale to restore it afterwards.

GlobalClock parent { get; set; }

The parent global clock. The parent clock will multiply its time scale with all of its children, allowing for cascading time effects.

ClockBlend parentBlend { get; set; } = Multiplicative

Determines how the clock combines its time scale with that of its parent.

ValueDescription
MultiplicativeThe clock's time scale is multiplied with that of its parent.
AdditiveThe clock's time scale is added to that of its parent.

In most cases, multiplicative blend will yield the expected results. However, additive blend becomes extremely useful when you have a parent clock with a time scale of 0.

TimeState state { get; }

Indicates the state of the clock.

ValueDescription
AcceleratedTime is accelerated (time scale > 1).
NormalTime is in real-time (time scale = 1).
SlowedTime is slowed (0 < time scale < 1).
PausedTime is paused (time scale = 0).
ReversedTime is reversed (time scale < 0).

Methods

void LerpTimeScale(float timeScale, float duration, bool steady = false)

Changes the local time scale smoothly over the given duration in seconds.

This method is not affected by any time scale.

If you enable the steady parameter, duration will apply to a time scale variation of 1. For example, if you call LerpTimeScale(3, 2, true), and the current time scale is 1, the duration of the lerp will be (3 - 1) * 2 = 4seconds instead of 2.

void ComputeTimeScale()

The time scale is only computed once per update to improve performance. If you need to ensure it is correct in the same frame, call this method to compute it manually.

Examples

Toggle a pause on the "World" clock when pressing P:


using UnityEngine;
using Chronos;

class MyBehaviour : MonoBehaviour
{
	void Update()
	{
		if (Input.GetKeyDown(KeyCode.P))
		{
			GlobalClock worldClock = Timekeeper.instance.Clock("World");

			worldClock.paused = !worldClock.paused;
		}
	}
}

Same example, this time with smoothing:


using UnityEngine;
using Chronos;

class MyBehaviour : MonoBehaviour
{
	bool paused = false;

	void Update()
	{
		if (Input.GetKeyDown(KeyCode.P))
		{
			GlobalClock worldClock = Timekeeper.instance.Clock("World");

			if (!paused)
			{
				worldClock.LerpTimeScale(0, 1); // Change time scale to 0 over 1 second
				paused = true;
			}
			else
			{
				worldClock.LerpTimeScale(1, 1); // Change time scale to 1 over 1 second
				paused = false;
			}
		}
	}
}

Icon GlobalClock /

A Clock that affects all Timeline and other global clocks configured as its children.

Properties

string key { get; }

The unique key of the global clock.

Icon LocalClock /

Clock that only affects a Timeline attached to the same GameObject.

Methods

void CacheComponents()

The components used by the local clock are cached for performance optimization. If you add or remove the Timeline on the GameObject, you need to call this method to update the local clock accordingly.

Icon AreaClock /

Clock that affects every Timeline within its collider by multiplying its time scale with that of their observed clock.

Area clocks can be moved, scaled and rotated at runtime. They can even stack and combine their effects!

Properties

ClockBlend innerBlend { get; set; } = Multiplicative 

Determines how the clock combines its time scale with that of the timelines within.

ValueDescription
MultiplicativeThe area clock's time scale is multiplied with that of the timelines.
AdditiveThe area clock's time scale is added to that of the timelines.

In most cases, multiplicative blend will yield the expected results. However, additive blend becomes extremely useful to affect your timelines when they have a time scale of 0.

AreaClockMode mode { get; set; }

Determines how objects should behave when progressing within the area clock.

ValueDescription
InstantObjects that enter the clock are instantly affected by its full time scale, without any smoothing.
PointToEdge

Objects that enter the clock are progressively affected by its time scale, depending on a A / B ratio where:

  • A is the distance between center and the object
  • B is the distance between center and the collider's edge in the object's direction
DistanceFromEntry

Objects that enter the clock are progressively affected by its time scale, depending on a A / B where:

  • A is the distance between the object's entry point and its current position
  • B is the value of padding

See the Notes section below for diagrams and more explanations for these modes.

AnimationCurve curve { get; set; }

The curve of the area clock. This value is only used for the PointToEdge and DistanceFromEntry modes.

Mode
Axis PointToEdge DistanceFromEntry
Y

Indicates a multiplier of the clock's time scale, from 1 to -1.

X

0 is at the center;
1 is at the collider's edge.

1 is at entry;
0 is at a distance of padding or more from entry.

A good use for this curve is to dampen an area clock's effect to make it seem more natural. Think of the curve's left being the centermost part of the clock, and right being the edge. Common damping curves for both modes are illustrated below.

Curve Center

A common damping curve for the PointToEdge mode.

Curve Entry

A common damping curve for the DistanceFromEntry mode.

Vector center { get; set; }

The center of the area clock. This value is only used for the PointToEdge mode.

float padding { get; set; }

The padding of the area clock. This value is only used for the DistanceFromEntry mode.

Methods

void Release(Timeline timeline)

Releases the specified timeline from the clock's effects.

void ReleaseAll()

Releases all timelines within the clock.

If the timeline enters the clock after having been released, it will be captured again. To configure which area clocks capture which timelines, use Unity's collision matrix.

void CacheComponents()

The components used by the area clock are cached for performance optimization. If you add or remove the Collider on the GameObject, you need to call this method to update the area clock accordingly.

Notes

This section contains diagrams that help understand how the PointToEdge and DistanceFromEntry modes function. For simplicity, 2D area clocks are represented, however the same concepts and calculations apply to 3D area clocks.

Point to edge

Point To Edge Diagram

Diagram for objects within a PointToEdge area clock.

  • Area clock icon: Clock's center
  • Green edge: Clock's collider
  • Yellow dot: Object's position
  • Magenta line: Distance between center and object
  • Cyan line: Distance between object and edge

In the point to edge mode, the X value of the curve property is calculated by the distance of the object from center to the edge of the area clock.

In the above diagram, this represents the following ratio:

x = Magenta line / ( Cyan line + Magenta line )

For example, A and E would have an x value of about 0.8, while B and D would have an x value of about 0.6. In the unlikely event that an object was placed at the exact center of the clock, its x value would be 0.

Distance form entry

Distance From Entry Diagram

Diagram for objects within a DistanceFromEntry area clock.

  • Area clock icon: Clock's local position
  • Green edge: Clock's collider
  • Green dot: Object's entry point
  • Yellow dot: Object's position
  • Orange line: Length of padding
  • Cyan edge: Collider inset by padding (for reference only)
  • Magenta line: Distance between entry and object
  • Cyan line: Distance remaining before end of padding

In the distance from entry mode, the X value of the curve property is calculated by the distance of the object from its entry point relative to the value of padding.

In the above diagram, this represents the following ratio:

x = Cyan line / Orange line

For example, A and C would have an x value of about 0.25, while B, D and E would have an x value of 0, because they went over the maximum distance.

The inset cyan collider has no real impact on the calculation. It is merely a helper that lets you visualize how big the value of padding looks while in the editor. Indeed, unless objects enter perfectly ortogonally to the collider's edge, their max distance will not be at the edge of the inset cyan collider.

You can display the cyan and magenta lines for both modes in scene view by enabling Timekeeper.debug.

Icon Timeline /

A component that combines timing measurements from an observed LocalClock or GlobalClock and any AreaClock within which it is.

This component should be attached to any GameObject that should be affected by Chronos.

Properties

TimelineMode mode { get; set; }

Determines what type of clock the timeline observes.

ValueDescription
LocalThe timeline observes a LocalClock attached to the same GameObject.
GlobalThe timeline observes a GlobalClock referenced by globalClock.
string globalClockKey { get; set; }

The key of the GlobalClock that is observed by the timeline. This value is only used for the Global mode.

bool rewindable { get; set; }

Determines whether the timeline should support rewind.

float recordingDuration { get; }

The maximum duration in seconds during which snapshots will be recorded. Higher values offer more rewind time but require more memory.

float recordingInterval { get; }

The interval in seconds at which snapshots will be recorded. Lower values offer more rewind precision but require more memory.

float availableRewindDuration { get; }

Returns the available rewind duration in seconds. 

Clock clock { get; }

The clock observed by the timeline.

float timeScale { get; }

The time scale of the timeline, computed from all observed clocks. For more information, see Clock.timeScale.

float deltaTime { get; }

The delta time of the timeline, computed from all observed clocks. For more information, see Clock.deltaTime.

float smoothDeltaTime { get; }

A smoothed out delta time. Use this value if you need to avoid spikes and fluctuations in delta times. The amount of frames over which this value is smoothed can be adjusted via smoothingDeltas.

static int smoothingDeltas { get; set; } = 5

The fixed delta time of the timeline, computed from all observed clocks. For more information, see Clock.fixedDeltaTime.

float time { get; }

The time in seconds since the creation of this timeline, computed from all observed clocks. For more information, see Clock.time.

float unscaledTime { get; }

The unscaled time in seconds since the creation of this timeline. For more information, see Clock.unscaledTime.

TimeState state { get; }

Indicates the state of the timeline.

Value Description
Accelerated

Time is accelerated (time scale > 1).

Normal

Time is in real-time (time scale = 1).

Slowed

Time is slowed (0 < time scale < 1).

Paused

Time is paused (time scale = 0).

Reversed

Time is reversed (time scale < 0).

Methods

void SetRecording(float duration, float inverval)

Sets the recording duration and interval in seconds.

This will reset the saved snapshots.

void ResetRecording()

Resets the saved snapshots.

int EstimateMemoryUsage()

Estimate the memory usage in bytes from the storage of snapshots for the current recording duration and interval.

void ReleaseFrom(AreaClock areaClock)

Releases the timeline from the specified area clock's effects.

void ReleaseFromAll()

Releases the timeline from the effects of all the area clocks within which it is.

Coroutine WaitForSeconds(float seconds)

Suspends the coroutine execution for the given amount of seconds. This method should only be used with a yield statement in coroutines.

There is currently no built-in support for rewindable coroutines due to limitations in the way .NET enumerators work. This feature is being considered for a future release if a workaround can be found. For now, if time is going backward, WaitForSeconds will simply never finish.

void CacheComponents()

The components used by the timeline are cached for performance optimization. If you add or remove built-in Unity components on the GameObject, you need to call this method to update the timeline accordingly.

void Reset()

Attempts a full manual reset of the timeline. When using game object pooling, you should call this method after despawning or before spawning.

Occurrences

To keep this documentation organized, the timeline methods to trigger occurrences can be found in the occurrence triggers section below.

Events

void OnStartPause()

Sent to every behaviour on the GameObject when the timeline starts a pause.

void OnStopPause()

Sent to every behaviour on the GameObject when the timeline stops a pause.

void OnStartRewind()

Sent to every behaviour on the GameObject when the timeline starts a rewind.

void OnStopRewind()

Sent to every behaviour on the GameObject when the timeline stops a rewind.

void OnExhaustRewind()

Sent to every behaviour on the GameObject when the timeline exhausts its rewind capacity.

void OnStartSlowDown()

Sent to every behaviour on the GameObject when the timeline starts a slow-down.

void OnStopSlowDown()

Sent to every behaviour on the GameObject when the timeline stops a slow-down.

void OnStartFastForward()

Sent to every behaviour on the GameObject when the timeline starts a fast-forward.

void OnStopFastForward()

Sent to every behaviour on the GameObject when the timeline stops a fast-forward.

Examples

Make the GameObject rotate in a framerate-independant manner by the scale of all affected clocks:


using UnityEngine;
using Chronos;

class MyBehaviour : MonoBehaviour
{
	void Update()
	{
		Timeline time = GetComponent<Timeline>();

		transform.Rotate(time.deltaTime * Vector3.one * 20);
	}
}

Make the GameObject cyan when paused:


using UnityEngine;
using Chronos;

class MyBehaviour : MonoBehaviour
{
	Color oldColor;

	void OnStartPause()
	{
		Renderer renderer = GetComponent<Renderer>();
		oldColor = renderer.material.color;
		renderer.material.color = Color.cyan;
	}

	void OnStopPause()
	{
		Renderer renderer = GetComponent<Renderer>();
		renderer.material.color = oldColor;
	}
}

Create a timeline component procedurally if it doesn't exist:


using UnityEngine;
using Chronos;

class MyBehaviour : MonoBehaviour
{
	void Awake()
	{
		Timeline time = GetComponent<Timeline>();

		if (time == null)
		{
			time = gameObject.AddComponent<Timeline>();
			time.mode = TimelineMode.Global;
			time.globalClock = Timekeeper.instance.Clock("Monsters");
		}
	}
}

It is usually much simpler to add a timeline component directly from the editor — this is purely in case you need to create your GameObjects procedurally.

Changing the GameObject's color randomly every 5 seconds. This delay will take in consideration pauses, fast-forwards and slow-downs.


using UnityEngine;
using Chronos;

class MyBehaviour : MonoBehaviour
{
	Timeline time;

	void Awake()
	{
		time = GetComponent<Timeline>();

		StartCoroutine(ChangeColor());
	}

	IEnumerator ChangeColor()
	{
		while (true)
		{
			// Use Timeline.waitForSeconds()
			// instead of new WaitForSeconds()
			yield return time.WaitForSeconds(5);

			float r = Random.Range(0, 256);
			float g = Random.Range(0, 256);
			float b = Random.Range(0, 256);

			Renderer renderer = GetComponent<Renderer>();
			renderer.material.color = new Color(r, g, b);
		}
	}
}

The previous example is not rewindable.

Create a base behaviour that will let you access the timeline component easily:


using UnityEngine;
using Chronos;

class BaseBehaviour : MonoBehaviour
{
	public Timeline time
	{
		get
		{
			return GetComponent<Timeline>();
		}
	}
}

// ... In other scripts, simply inherit from
// BaseBehaviour instead of MonoBehaviour

class MyBehaviour : BaseBehaviour
{
	void Update()
	{
		// We can now access the timeline extremely easily!
		transform.Rotate(time.deltaTime * Vector3.one * 20);
	}
}

Using a base behaviour is a good Unity design pattern, and it is certainly not limited to Chronos! If you have any methods or properties that you use very often, feel free to add them to BaseBehaviour. They'll be accessible from any script that extends it.

Components

Timelines manipulate the built-in Unity components at runtime to adjust their speeds. This allows an effortless setup in almost all cases. However, it also means that if you edit their speeds directly, Chronos will overwrite them or behave unexpectedly. To remedy this situation, you should use the properties below instead.

For more information about the script changes needed to migrate to Chronos, see the Migration page.

Animator

float animator.speed { get; set; }

The speed that is applied to the animator before time effects. Use this property instead of Animator.speed, which will be overwritten by the timeline at runtime.

Unfortunately, if you rewind an animator then let time flow normally, all of its previous recording will be reset. This is due to how Unity's animator recording methods are built. There seems to be no alternative at the moment.

Animation

float animation.speed { get; set; }

The speed that is applied to animations before time effects. Use this property instead of AnimationState.speed, which will be overwritten by the timeline at runtime.

ParticleSystem

float particleSystem.playbackSpeed { get; set; }

The playback speed that is applied to the particle system before time effects. Use this property instead of ParticleSystem.playbackSpeed, which will be overwritten by the timeline at runtime.

At extremely low speeds or time scales (< 0.25), particle systems will appear to stutter. This is due to a bug in Unity's particle simulation method. A bug report has been submitted here: ParticleSystem.Simulate truncates first parameter to 2 decimals.

float particleSystem.time { get; set; }

The playback time of the particle system. Use this property instead of ParticleSystem.time, which will be overwritten by the timeline at runtime.

bool particleSystem.isPlaying { get; }

Indicates whether the particle system is playing. Use this property instead of ParticleSystem.isPlaying, which will be overwritten by the timeline at runtime.

bool particleSystem.isPaused { get; }

Indicates whether the particle system is paused. Use this property instead of ParticleSystem.isPaused, which will be overwritten by the timeline at runtime.

bool particleSystem.isStopped { get; }

Indicates whether the particle system is stopped. Use this property instead of ParticleSystem.isStopped, which will be overwritten by the timeline at runtime.

void particleSystem.Play(bool withChildren = true)

Plays the particle system. Use this property instead of ParticleSystem.Play, which will be overwritten by the timeline at runtime.

void particleSystem.Pause(bool withChildren = true)

Pauses the particle system. Use this property instead of ParticleSystem.Pause, which will be overwritten by the timeline at runtime.

void particleSystem.Stop(bool withChildren = true)

Determines whether the particle system is alive. Use this method instead of ParticleSystem.IsAlive, which will be overwritten by the timeline at runtime.

void particleSystem.IsAlive(bool withChildren = true)

Stops the particle system. Use this property instead of ParticleSystem.Stop, which will be overwritten by the timeline at runtime.

Rigidbody (2D / 3D)

bool rigidbody.isKinematic { get; set; }

Determines whether the rigidbody is kinematic before time effects. Use this property instead of Rigidbody.isKinematic, which will be overwritten by the physics timer at runtime.

bool rigidbody.useGravity { get; set; } // 3D only

Determines whether the rigidbody uses gravity. Use this property instead of Rigidbody.useGravity, which will be overwritten by the physics timer at runtime.

float rigidbody2D.gravityScale { get; set; } // 2D only

The gravity scale of the rigidbody. Use this property instead of Rigidbody2D.gravityScale, which will be overwritten by the physics timer at runtime.

float rigidbody.mass { get; set; }

The mass of the rigidbody before time effects. Use this property instead of Rigidbody.mass, which will be overwritten by the physics timer at runtime.

Vector3 rigidbody.velocity { get; set; } 
Vector2 rigidbody2D.velocity { get; set; }

The velocity of the rigidbody before time effects. Use this property instead of Rigidbody.velocity, which will be overwritten by the physics timer at runtime.

Vector3 rigidbody.angularVelocity { get; set; }
float rigidbody2D.angularVelocity { get; set; }

The angular velocity of the rigidbody before time effects. Use this property instead of Rigidbody.angularVelocity, which will be overwritten by the physics timer at runtime.

float rigidbody.drag { get; set; }

The drag of the rigidbody before time effects. Use this property instead of Rigidbody.drag, which will be overwritten by the physics timer at runtime.

float rigidbody.angularDrag { get; set; }

The angular drag of the rigidbody before time effects. Use this property instead of Rigidbody.angularDrag, which will be overwritten by the physics timer at runtime.

void rigidbody.AddForce(Vector3 force, ForceMode mode = ForceMode.Force)
void rigidbody2D.AddForce(Vector2 force, ForceMode2D mode = ForceMode2D.Force)

The equivalent of Rigidbody.AddForce adjusted for time effects.

void rigidbody.AddRelativeForce(Vector3 force, ForceMode mode = ForceMode.Force)
void rigidbody2D.AddRelativeForce(Vector2 force, ForceMode2D mode = ForceMode2D.Force)

The equivalent of Rigidbody.AddRelativeForce adjusted for time effects.

void rigidbody.AddForceAtPosition(Vector3 force, Vector3 position, ForceMode mode = ForceMode.Force)
void rigidbody2D.AddForceAtPosition(Vector2 force, Vector2 position, ForceMode2D mode = ForceMode2D.Force)

The equivalent of Rigidbody.AddForceAtPosition adjusted for time effects.

void rigidbody.AddExplosionForce(float explosionForce, Vector3 explosionPosition, float explosionRadius, float upwardsModifier = 0, ForceMode mode = ForceMode.Force)

The equivalent of Rigidbody.AddExplosionForce adjusted for time effects.

void rigidbody.AddTorque(Vector3 force, ForceMode mode = ForceMode.Force)
void rigidbody2D.AddTorque(Vector2 force, ForceMode2D mode = ForceMode2D.Force)

The equivalent of Rigidbody.AddTorque adjusted for time effects.

void rigidbody.AddRelativeTorque(Vector3 force, ForceMode mode = ForceMode.Force)
void rigidbody2D.AddRelativeTorque(Vector2 force, ForceMode2D mode = ForceMode2D.Force)

The equivalent of Rigidbody.AddRelativeTorque adjusted for time effects.

AudioSource

float audioSource.pitch { get; set; }

The pitch that is applied to the audio source before time effects. Use this property instead of AudioSource.pitch, which will be overwritten by the timeline at runtime.

If you have multiple audio sources on your GameObject, you can all their timeline wrappers via the audioSources property.

NavMeshAgent

float navMeshAgent.speed { get; set; }

The speed that is applied to the agent before time effects. Use this property instead of NavMeshAgent.speed, which will be overwritten by the timeline at runtime.

float navMeshAgent.angularSpeed { get; set; }

The angular speed that is applied to the agent before time effects. Use this property instead of NavMeshAgent.angularSpeed, which will be overwritten by the timeline at runtime.

WindZone

float windZone.windMain { get; set; }

The wind that is applied to the wind zone before time effects. Use this property instead of WindZone.windMain, which will be overwritten by the timeline at runtime.

float windZone.windTurbulence { get; set; }

The turbulence that is applied to the wind zone before time effects. Use this property instead of WindZone.windTurbulence, which will be overwritten by the timeline at runtime.

float windZone.windPulseMagnitude { get; set; }

The pulse magnitude that is applied to the wind zone before time effects. Use this property instead of WindZone.windPulseMagnitude, which will be overwritten by the timeline at runtime.

float windZone.windPulseFrequency { get; set; }

The pulse frequency that is applied to the wind zone before time effects. Use this property instead of WindZone.windPulseFrequency, which will be overwritten by the timeline at runtime.

Icon TimelineChild /

A component that transfers the effects of any ancestor Timeline in the hierarchy to the current GameObject.

Timeline parent { get; }

The parent Timeline from which time effects are transfered.

CacheParent()

The timeline hierarchy is cached for performance. If you change the transform hierarchy, you must call this method to update it.

The following members, along with all component timelines, are shared with the Timeline class:

Icon Occurrence /

An event anchored at a specified moment in time composed of two actions: one when time goes forward, and another when time goes backward. The latter is most often used to revert the former.

Properties

float time { get; }

The time in seconds on the parent timeline at which the occurrence will happen.

bool repeatable { get; }

Indicates whether this occurrence can happen more than once; that is, whether it will stay on the timeline once it has been rewound.

Methods

abstract void Forward()

The action that is executed when time goes forward.

abstract void Backward()

The action that is executed when time goes backward.

Triggers

The following methods are all called from a Timeline instance. They are placed in this section for the sake of organization only.

Occurrence Schedule(float time, bool repeatable, Occurrence occurrence)
Occurrence Schedule(float time, bool repeatable, ForwardFunc<T> forward, BackwardFunc<T> backward)
Occurrence Schedule(float time, bool repeatable, ForwardAction forward, BackwardAction backward)
Occurrence Schedule(float time, ForwardAction forward)

Schedules an occurrence at a specified absolute time in seconds on the timeline.

Occurrence Do(bool repeatable, Occurrence occurrence)
Occurrence Do(bool repeatable, ForwardFunc<T> forward, BackwardFunc<T> backward)
Occurrence Do(bool repeatable, ForwardAction forward, BackwardAction backward)

Executes an occurrence now and places it on the schedule for rewinding.

Occurrence Plan(float delay, bool repeatable, Occurrence occurrence)
Occurrence Plan(float delay, bool repeatable, ForwardFunc<T> forward, BackwardFunc<T> backward)
Occurrence Plan(float delay, bool repeatable, ForwardAction forward, BackwardAction backward)
Occurrence Plan(float delay, ForwardAction forward)

Plans an occurrence to be executed in the specified delay in seconds.

Occurrence Memory(float delay, bool repeatable, Occurrence occurrence)
Occurrence Memory(float delay, bool repeatable, ForwardFunc<T> forward, BackwardFunc<T> backward)
Occurrence Memory(float delay, bool repeatable, ForwardAction forward, BackwardAction backward)
Occurrence Memory(float delay, ForwardAction forward)

Creates a "memory" of an occurrence at a specified "past-delay" in seconds. This means that the occurrence will only be executed if time is rewound, and that it will be executed backward first.

If repeatable is set to false, the occurrence will be cancelled after it has been rewound. This is useful for code that occurs as a result of object interaction and would therefore reoccur by itself after a rewind.

All the trigger methods return the created occurrence as a result, in case you need to cancel or reschedule it via the methods below.

void Cancel(Occurrence occurrence)

Removes the specified occurrence from the timeline.

bool TryCancel(Occurrence occurrence)

Removes the specified occurrence from the timeline and returns true if it is found. Otherwise, returns false.

void Reschedule(Occurrence occurrence, float time)

Changes the absolute time in seconds of the specified occurrence on the timeline.

void Postpone(Occurrence occurrence, float delay)

Moves the specified occurrence forward on the timeline by the specified delay in seconds.

void Prepone(Occurrence occurrence, float delay)

Moves the specified occurrence backward on the timeline by the specified delay in seconds.

Examples

Changing the color of the GameObject to blue 5 seconds after Space is pressed. This delay will take into consideration pauses, fast-forwards and slow-downs. However, this occurrence is not (yet) rewindable: the object will not go back to its original color on rewind.


using UnityEngine;
using Chronos;

class MyBehaviour : MonoBehaviour
{
	void Update()
	{
		if (Input.GetKeyDown(KeyCode.Space))
		{
			GetComponent<Timeline>().Plan(5, ChangeColor);

			// Notice the absence of () after the method name.
			// This is because we are refering to the method itself,
			// not calling it right now.
		}
	}

	void ChangeColor()
	{
		GetComponent<Renderer>.material.color = Color.blue;
	}
}

Same example as before, but this time, we pass a color parameter to our method:


using UnityEngine;
using Chronos;

class MyBehaviour : MonoBehaviour
{
	void Update()
	{
		if (Input.GetKeyDown(KeyCode.Space))
		{
			GetComponent<Timeline>().Plan(5, delegate { ChangeColor(Color.red); });

			// Here, we create a delegate (an inline method) to
			// be called in 5 seconds. In turn, our delegate calls
			// the ChangeColor with the color red as a parameter.
		}
	}

	void ChangeColor(Color newColor)
	{
		GetComponent<Renderer>.material.color = newColor;
	}
}

If you are unfamiliar with delegates, it is recommended that you take a look at the MSDN tutorial on delegates. Note, however, that you won't have to create your own delegate types to use Chronos — you will only need a basic understanding of what they are and how they can be created.

Now, let's make our color change a rewindable occurrence. When time is rewound up to the moment the GameObject became blue, it should automatically revert back to its original color.


using UnityEngine;
using Chronos;

class MyBehaviour : MonoBehaviour
{
	void Start()
	{
		GetComponent<Timeline>().Plan
		(
			5, // In 5 seconds...

			true, // ... create a repeatable event...

			delegate // ... that sets the color to blue and saves the previous one...
			{
				Renderer renderer = GetComponent<Renderer>();
				Color previousColor = renderer.material.color;
				renderer.material.color = Color.blue;
				return previousColor; // This will be passed as the first parameter below
			},

			delegate (Color previousColor) // ... and reverts it on rewind.
			{
				Renderer renderer = GetComponent<Renderer>();
				renderer.material.color = previousColor;
			}
		);

		// You can pass any object, be it a simple integer or
		// a complex class, from the first delegate
		// to the second. This allows to indicate a 'state'
		// to which you can easily revert.
	}
}

Try experimenting with the repeatableparameter from that example to get an understanding of what it does. For example, try setting it to false, then rewinding time after the object turned blue until it goes back to its original color, then letting time go forward normally. You'll realize it doesn't turn blue again! That's because the occurrence was removed from the timeline after rewinding.

That previous example works, but it's a bit tedious to set up. Imagine that we often wanted to have rewindable color-change occurrences like this one — it would be quite annoying to type that code every time! Fortunately, we don't have to.

In the following example, we'll create our own Occurrence class and transform our previously lengthy code into a reusable one-liner.


using UnityEngine;
using Chronos;

// Inherit Occurrence and implement Forward() and Backward()
public class ChangeColorOccurrence : Occurrence
{
	Material material;
	Color newColor;
	Color previousColor;

	public ChangeColorOccurrence(Material material, Color newColor)
	{
		this.material = material;
		this.newColor = newColor;
	}

	public override void Forward()
	{
		previousColor = material.color;
		material.color = newColor;
	}

	public override void Backward()
	{
		material.color = previousColor;
	}

	// You can create occurrences of any complexity!
	// This simple one is only for the sake of a short example.
}

class MyBehaviour : MonoBehaviour
{
	void Start()
	{
		Timeline time = GetComponent<Timeline>();
		Material material = GetComponent<Renderer>().material;

		// Change the color to blue in 5 seconds
		time.Plan(5, true, new ChangeColorOccurrence(material, Color.blue));

		// Change the color to red in 10 seconds
		time.Plan(10, true, new ChangeColorOccurrence(material, Color.red));
	}
}

Finally, if you don't need to pass data between the forward and backward actions, you can simply return nothing in the first delegate and have no parameter in the second:


using UnityEngine;
using Chronos;

class MyBehaviour : MonoBehaviour
{
	void Start()
	{
		GetComponent<Timeline>().Plan
		(
			5, // In 5 seconds...

			true, // ... create a repeatable event...

			delegate
			{
				Debug.Log("Going forward!");
			},

			delegate
			{
				Debug.Log("Going backward!");
			}
		);
	}
}

You now have the tools to make any kind of custom code work with Chronos; whether time flows normally, slower, faster or even backwards!

When making rewindable occurrences, it is crucial to remember that if your code occurs from the interaction between game objects (e.g. collisions), it should — in almost all cases — not be set to repeatable

For example, say you change the color of two objects to red when they collide (in OnCollisionEnter). If that occurrence is repeatable and you rewind, then let time run normally, not only will the objects change to red from the existing occurrence, but they'll collide again, creating a new occurrence. This can quickly lead to unexpected results, so be careful!

Icon Recorder /

An abstract base component that saves snapshots of any kind at regular intervals to enable rewinding.

The recorder class is just a shell to properly record snapshots and rewind by interpolating between them. By itself, it doesn't do anything; you can't add it to a GameObject. However, it can be extended (subclassed) to record any type of information in snapshots.

Methods

These methods are also available on the following timeline components when Timeline.rewindable is enabled:

  • Timeline.transform (for objects without a rigidbody)
  • Timeline.rigidbody (for objects with a 3D rigidbody)
  • Timeline.rigidbody2D (for objects with a 2D rigidbody)
void ModifySnapshots(SnapshotModifier modifier)

Modifies all snapshots via the specified modifier delegate.

void Record()

Forces the recorder to record a snapshot at the current frame.

Examples

Move all recorded snapshots up by 1 unit when the up arrow is pressed:


using UnityEngine;
using Chronos;

class MyScript : MonoBehaviour
{
	public void Update()
	{
		if (Input.GetKeyDown(KeyCode.UpArrow))
		{
			Timeline timeline = GetComponent<Timeline>();
			
			timeline.transform.ModifySnapshots((snapshot, snapshotTime) =>
			{
				snapshot.position += new Vector3(0, 1, 0);
				
				return snapshot;
			});
		}
	}
}

Custom Recorders

Creating a custom recorder from the editor

Let's say we want to record the health and color of our player (presuming its color changes over time). Our player script would look similar to this:


using UnityEngine;

public class Player : MonoBehaviour
{
	public float health = 100;
	public Color color = Color.blue;
	
	void Update()
	{
		// ...
	}
}

All we have to do is add a Custom Recorder component to our game object, and specify which variables to record:

Custom Recorder

Configuring the custom recorder component.

That's all there is to it! Rewinding will now properly handle the player's health and color.

Custom recorders support all fields and properties that are value-typed and not read-only. They work on built-in Unity components and even custom scripts.

Creating a custom recorder from script

Creating a recorder from scripting is a tiny bit more complex, but it's more efficient in terms of speed and memory. If you are targetting mobiles, you might want to consider making your recorders from scripting before releasing, while still using editor-based custom recorders for fast prototyping.

Again, let's record the health and color of our player. We will create a PlayerRecorder script that inherits Recorder, and implement the 3 mandatory abstract methods:


using UnityEngine;
using Chronos;

// Make your class inherit Recorder.
// The generic parameter points to the type of snapshot.
public class PlayerRecorder : Recorder<PlayerRecorder.Snapshot>
{
	// The struct that contains each of our snapshot's data.
	// In our case, a health value and a color.
	public struct Snapshot
	{
		public float health;
		public Color color;
	}

	// Record the current health and color in a snapshot
	protected override Snapshot CopySnapshot()
	{
		return new Snapshot()
		{
			health = GetComponent<Player>().health,
			color = GetComponent<Player>().color,
		};
	}

	// Apply a snapshot to the current GameObject
	protected override void ApplySnapshot(Snapshot snapshot)
	{
		GetComponent<Player>().health = snapshot.health;
		GetComponent<Player>().color = snapshot.color;
	}

	// Interpolate between two snapshots for smoothing
	protected override Snapshot LerpSnapshots(Snapshot from, Snapshot to, float t)
	{
		return new Snapshot()
		{
			health = Mathf.Lerp(from.health, to.health, t),
			color = Color.Lerp(from.color, to.color, t)
		};
	}
}

You can then attach your PlayerRecorder component, along with a timeline, to your player GameObject. Its health and color should now be rewindable.


Did you spot any error in the documentation?
If so, please report it!