Agamotto

Last updated: Nov 12, 2020 - Agamotto v1.2.0

What is it?

Agamotto is a Time Manipulating Tool for Unity.
It allows you to store your objects state over time, would it be recording their past or simulating their future. You can then move them on the timeline with ease
Agamotto lets you do simulations based on physics, code or both.
It is delivered with many examples, designed to be extendable and offers easy way to visualize your time data.

Warning : Agamotto is not a tool to slow, freeze or accelerate time in real time. It contains utility tools to freeze objects and you can playback or rewind your objects state over time at the desired speed, but you'll need to first record or simulate their state.
Agamotto is made to store and control objects state over time.
For example, if you're searching a tool to activate a bullet time... this is not the asset you're search for.



Presentation

Demos Walkthrough

Quick Start

  1. An Agamotto scene will have a TimeStone to record or simulate, TimeAgents to be handled by the TimeStone and eventually visualizers on TimeAgents to display the TimeStone's data.
  2. Add a TimeStone component in your scene
  3. Activate "Record on start" option on the TimeStone
  4. Add TimeAgent component to the objects you want control in time (create spheres in the air and a plane ground for simple physics for example)
  5. Don't forget to add TimeAgent on the plane ground if you want do simulations ;)

  6. Configure your TimeStone to manage this agents, for example by listing them manually
  7. Press Play, and use TimeStone's Editor controller to stop recording and move in time!
  8. screenshot

Unity Versions compatibility

Agamotto is deployed from Unity 2019.4 (lower versions not supported)
    2020.1 - Tested
    2019.4 - Tested

Time Stone

The TimeStone component is the heart (maybe the eye...) of Agamotto.

A TimeStone manages a TimeLine with a list of TimeAgents.
This is the component responsible for :

  • Keeping the TimeLine data, with TimeAgents data
  • Orchestrating the TimeAgents data persistence
  • Orchestrating the physics simulation on TimeAgents
  • Orchestrating the TimeAgents life cycle callbacks to simulate code
  • Applying timeLine data on TimeAgents to move in time

You can have multiple TimeStone running, recording or simulating at the same time

About destroying objects

Obviously, Agamotto can not handle objects destruction over time (if an object is destroyed, Agamotto can't magically re-instantiate it).
If you need objects to be destroyed while recorded or simulated, you must use a pool.
See the TowerDefenseDemo project for an example.

TimeLine

The TimeStone persists TimeAgents data in a TimeLine where every tick corresponds to a frame execution.
The TimeLine is continuous and you can start recording or simulating from any point in it (setting TimeStone.addTickCursor).

Agents Initialization

screenshot
  • Initialization mode
    • Manually
    • Manually define the list of Time Agents. This list only accepts GameObject with a TimeAgent component

      To use programmatically : set TimeStone.timeAgentsManualInitList

    • By Tag
    • Initializes with the TimeAgents that have this tag

      To use programmatically : set TimeStone.timeAgentsListInitTag

    • By Layer
    • Initializes with the TimeAgents that are on this layer name

      To use programmatically : set TimeStone.timeAgentsListInitLayer

    To initialize the stone, call TimeStone.InitTimeAgentsList(), this method is automatically called on Start and when calling TimeStone.StartSimulation(true)

  • Parse children
  • This option let you search for TimeAgents in the children

Recording

screenshot

When recording is active, the TimeStone will persist its TimeAgents data every tick

  • Start and Stop Recording
  • Call TimeStone.StartRecording() and TimeStone.StopRecording()
    For testing purpose, you have Editor's real time controller to do this actions.

  • Record on start
  • TimeStone will activate recording on Start

  • Duration
  • The maximum duration (in seconds) of the recording.
    Past this duration, oldest data are removed to be replaced by the new data.

    Set to 0 to deactivate

    This option does not differentiate simulation and record data when operating the removal.
    So for example if you have 30 seconds of simulation at the start of your timeline and you start recording with a maximum duration of 5 sec, 25 seconds of simulation data will be removed

  • Record step
  • The delta time between each recording tick.
    Set to 0 to record every frame (could impact performances).

  • Synchronize playback tick
  • With this option, the playback will be synchronized with the recording. Programmatically, this updates the TimeStone.playbackTickCursor with TimeStone.addTickCursor

Simulating

The TimeStone can apply simulation on Physics and on code.

screenshot

Code simulation and deltaTime

In code executed by Simulation callbacks (SimulationUpdate, SimulationFixedUpdate, etc.), be sure to never rely on Time.deltaTime or you'll get inconsistent results. You must use the deltaTime given in parameter of the callback, see demo projects for examples.

TimeAgents cloned for simulation

To execute the simulation, the TimeStone clones all its TimeAgents in a new scene and run manually the physics simulation while executing callbacks on TimeAgents to simulate the code.
It is critical to understand that when simulating code, to understand if your code is executed on the original TimeAgent, or the clone instantiated for the simulation.

Physics Simulation and determinism

It is possible to get exactly the same result when running your scene than what you get making a physics simulation with a TimeStone.

You can see the ComplexPhysicsDemo or the PoolDemo for example that works

But as Unity's Physics system is very (and I mean Very) fragile, you must be perfectly sure that what you simulated is exactly the same than what your run.

For example, using the SorcererDemo you will sometime see differences between what's simulated for the cubes path and the reality when time is released. This is because the scene is different when simulated and when ran. The most simple difference being the player, which is not in the same place or oriented the same way. But even smaller details could impact the result violently.

TimeAgent Initialization for simulation

To initialize your TimeAgents well for a simulation (for example to reference another TimeAgent), be sure to :

  • Use Start method.
  • Be sure to pick clones in simulation and originals in normal case, this is made easy using TimeStoneUtils methods

Explanations

  • You can't initialize in Awake method, because it can be called by Unity before the TimeAgent.IsClone field is set
  • Sometimes you can't use TimeAgent.onSimulationStart UnityEvent. This is the case with pools, because UnityEvents aren't called on inactive objects.

See TowerDefenceDemo initialization of Tower, Projectile or Minion Components for an example.

  • Start and Stop Simulating
  • Call TimeStone.StartSimulation() and TimeStone.StopSimulation()
    For testing purpose, you have Editor's real time controller to do this actions.

  • Do simulation on start
  • TimeStone will execute a simulation on Start

  • Limit duration
  • The simulation will automatically stops when reaching the duration

    If the simulation has no limit, you should stop it by calling TimeStone.StopSimulation()

  • Simulation speed
  • The simulation will be executed at this speed.
    If the maximum milliseconds by frame is reached while simulating, the real execution speed could be lowered to preserve the performances

  • Use Time.deltaTime
  • The simulation uses the current frame Time.deltaTime.
    If speed is higher than 1, then the current frame deltaTime is used for all ticks simulated in the same frame.

    This option is useful to get results closer to the real execution, but as a result, you will also get the eventual real lags and spikes in your simulation. For example, on scene start Unity will most of the time have a deltaTime spike, you can avoid it in your simulation by unchecking this option.

    About Debugging : While debugging your code, you should uncheck this option, because the debug will strongly alter the deltaTime you get in Unity and you could get different simulation results from what you get without debugging

  • Simulate Code
  • With this option activated, the TimeStone will call the TimeAgents life-cycle callbacks to execute code during the simulation

  • Simulate Physics
  • With this option activated, the TimeStone will run the Physics simulation on the TimeAgents.

  • Max ms by frame
  • The limit in milliseconds that a simulation execution won't be able to go over every frame. This is a security to preserve your framerate during a simulation.

  • Group frame ticks
  • When speed is higher than 1, this option will group all simulated ticks from a same frame (with an adapted deltaTime).
    This results in faster simulation but with less precision.

  • Keep simulation scene
  • The scene where TimeAgents and clone and simulated won't be destroyed when simulation ends. Useful for debug for example.

  • Reset add cursor on completion
  • The TimeStone.addTickCursor will be replaced at the Simulation start tick index on completion.
    Useful to run multiple simulations in a row without having to replace the cursor manually (without this option the simulations will be chained)

Playback

The playback is the ability of the TimeStone to replay its data on the TimeAgents.
It allows you to easily move your TimeAgents in time at the speed you want.

  • Start and Stop Playback
  • Call TimeStone.StartPlayback() and TimeStone.StopPlayback()
    For testing purpose, you have Editor's real time controller to do this actions.

  • TimeStone.playbackSpeed
  • The speed of the playback

  • TimeStone.playbackUpdateAddTickCursor
  • If true, the TimeStone.addTickCursor will be synchronized with the playback.
    So when you stop the playback, you can start to record or simulate and chain the data gracefully.

Editor Real Time controller

screenshot

The TimeStone's Editor contains real time controller to manipulate the timeLine, start/stop recording or simulating, freeze the agents and clear the data.

UnityEvents

The TimeStone triggers many UnityEvents that you can use to control it. All of the events are exposed in the Editor to be used directly from the UI.

UnityEvent Description
OnInitTimeAgentsList

Called when the TimeStone has initialized its TimeAgents (which means resolving the list from tag or layer and searching for children if needed)
OnTimeLineClear

Called when TimeStone.Clear() is executed
OnTimeLineChange<TimeTickOrigin>

Called when TimeLine is modified.
The TimeTickOrigin parameter let you know if it's called after a tick is recorded or when all simulated tick of frame are finished.
OnSimulationStart

Called when the simulation starts, which is after a TimeStone.StartSimulation call, because it means that the simulation scene is created, the TimeAgents cloned and the simulation is really starting to be executed.
Furthermore, a simulation start to execute at least one frame after TimeStone.StartSimulation is called to let cloned TimeAgents initialize.
OnSimulationComplete

Called when the simulation is complete, either because its max duration is reached or because you called TimeStone.StopSimulation.

Time Agent

The TimeAgent component must be added to all objects handled by a TimeStone.
This components will know how to :

  • Persist its data in the TimeStone
  • Apply its persisted data from the TimeStone
  • Clone itself for a simulation
  • Execute simulation callbacks to simulate code

TimeAgent can natively handle the data for :

  • GameObject's active state
  • Transform position/rotation/scale
  • Rigibody velocity for Physics simulation
  • Animator component (state and animations)
  • Particle Systems

You can then use TimeAgent's UnityEvents or extend the component to persist just any data in the TimeStone

Configuration

screenshot
  • Parse Children
  • TimeAgents will be recursively searched in children during the TimeStone initialization of its TimeAgents list.

  • Active State
  • GameObject.activeSelf property will be persisted though time.
    This is a mandatory option if you use a pool of object

  • Persist Transform
  • Persists Transform.position / Transform.rotation / Transform.localScale

    You can choose to use the local versions of position and rotation. in all cases, both the world and local values are persisted in the TimeStone, but if you check "use localPosition" or "use localRotation", the SetDataTick method will use the local value.

  • Persist Physics
  • Persists Rigidbody.velocy / Rigidbody.angularVelocity

  • Persist Animator
  • Persists Animator's State.
    You can specify the Animator component to persist or leave empty to automatically use GameObject's Animator component

  • Persist Particles
  • Persists Particle Systems states.
    You can specify the Particle Systems to persist or leave empty to automatically use GameObject's Particle Systems

    • Search children
    • Will also persist Particle Systems found in children

  • Clone Rendering
  • Options applied to the TimeAgent cloned for simulations.

    If you need more options when cloning your TimeAgent, you can extend TimeAgent and override ClonedForSimulation

    • Hide policy
    • The way the TimeAgent will be hidden in the simulation scene

    • Apply Material
    • A specific material will be applied

    • Apply Color
    • A color will be applied

    • Apply on children
    • Hide policy, color and material will be applied to TimeAgent's children

  • Simulation config
  • You can choose the way simulation callbacks (SimulationStart, SimulationComplete, etc.) are received :

    • All : The callbacks will be executed on the Original and the Clone instances of the TimeAgent.
      Use TimeAgent.IsClone to know which instance is executing the code.
    • CloneOnly (default) : The callbacks will be executed on the Clone instance only.
      This is The default behaviour because in most cases the Original instance won't need the callbacks and thus you can avoid to check on TimeAgent.IsClone when coding your callback
    • OriginalOnly : The callbacks will be executed on the Original instance only.

Simulation callbacks

This callbacks are called by the TimeStone when running a simulation.
They are made to give you control over the simulation and give an equivalent of MonoBehaviour's Callbacks (Update, FixedUpdate, etc.). So you should be able to execute similar code in it when needed.

There is a corresponding UnityEvent for most of the callback to use them.

Alternatively, you can extend TimeAgent and override the callbacks, don't forget to call the base method in that case.

Callback Description UnityEvent
void InitTimeAgentsList(TimeStone stone)

[Not specific to simulations] Called when the TimeStone has initialized its TimeAgents. NO
GameObject GetClone()

Called once by simulation, to get the clone GameObject of the TimeAgent.
Override if you need to do specific action on the cloned version.
NO
void SimulationSceneReady(TimeStone stone)

Called once by simulation, when the simulation scene used to simulate is ready and all TimeAgents have been cloned. NO
void ClonedForSimulation(TimeStone stone)

Called once by simulation, when the TimeAgent has been cloned. Used to apply hidePolicy, material and color to the clone. NO
void TimeLineChange(TimeStone stone, TimeStone.TimeTickOrigin origin)

[Not specific to simulations] Called when TimeStone's TimeLine is modifed, either by new recorded or simulated ticks. YES
void SimulationStart(TimeStone stone)

Called when TimeStone starts to simulate. YES
void SimulationUpdate(float step, TimeStone stone)

Called when TimeStone simulates a frame.
Equivalent to MonoBehaviour.Update but you must use step parameter instead of Time.deltaTime in your code
YES
void SimulationFixedUpdate(float fixedStep, TimeStone stone)

Called when TimeStone simulates physics
Equivalent to MonoBehaviour.FixedUpdate but you must use fixedStep parameter instead of Time.fixedDeltaTime in your code
YES
void SimulationLateUpdate(float step, TimeStone stone)

Called when TimeStone simulates a frame but after SimulationUpdate.
Equivalent to MonoBehaviour.LateUpdate but you must use step parameter instead of Time.deltaTime in your code
YES
void SimulationComplete(TimeStone stone)

Called when TimeStone's Simulation is complete YES
void PersistTick(TimeStone stone, TimeStone.TimeTickOrigin origin, float deltaTime)

Called when TimeStone persist this TimeAgent's data for the current tick.
This is where you add your own data to persist
YES
void SetDataTick(TimeStone stone, int tick)

Called when TimeStone set the time tick of the TimeAgent, for example when using playback to move in time
This is where you must apply your own persisted data
YES

Persist Custom Data

To use custom data from your TimeAgent in the TimeStone (like character's life, ammos, etc.), you need to persist and apply them.

To persist your data you need to call TimeStone.PersistData when the TimeAgent.PersistTick callback is called.
ForExample :

stone.PersistData("lifeDataId", life);

To apply your data you need to get your data using TimeStone.GetDataValue when the TimeAgent.SetDataTick callback is called.
ForExample :

var lifeData = stone.GetDataValue<int>("lifeDataId", out var dataExists);
if (dataExists)
    life = lifeData;

You can use multiple methods to make this calls

  • UnityEvent
  • You can add a listener to the TimeAgent.onPersistTick and TimeAgent.onSetDataTick UnityEvent, from code or from the editor.

  • Extend TimeAgent
  • If you extend TimeAgent, you can override PersistTick and SetDataTick and add your PersistData calls here

  • Use GenericTimeAgent
  • See next section for details

GenericTimeAgent Component

screenshot

GenericTimeAgent is a component extending TimeAgent to add a generic handling of custom properties

You can add properties from your TimeAgent's other components by their name and they will be automatically persisted and updated.

About Performances

To offer this generic functionality, this component makes use of Reflection.

Because of that, its performances will always be way worse than accessing your properties directly.
So if you have critical performances concerns, you should not use this Component but rather persist and set your data yourself like explained in previous section

  • Component / Member table
  • This table displays the automatically persisted data, first column is the component name, second is the component member

  • New Data panel
  • In this panel, you can drag and drop a component from the GameObject and then select the member you want persist automatically

    Alternatively, you can click on "Raw mode" to enter the component type and member name manually. Be careful there is no security check in raw mode on the component or member validity

UnityEvents

The TimeStone triggers many UnityEvents that you can use to control it. All of the events are exposed in the Editor to be used directly from the UI.

UnityEvent Description
OnInitTimeAgentsList

Called when the TimeStone has initialized its TimeAgents (which means resolving the list from tag or layer and searching for children if needed)
OnTimeLineClear

Called when TimeStone.Clear() is executed
OnTimeLineChange<TimeTickOrigin>

Called when TimeLine is modified.
The TimeTickOrigin parameter let you know if it's called after a tick is recorded or when all simulated tick of frame are finished.
OnSimulationStart

Called when the simulation starts, which is after a TimeStone.StartSimulation call, because it means that the simulation scene is created, the TimeAgents cloned and the simulation is really starting to be executed.
Furthermore, a simulation start to execute at least one frame after TimeStone.StartSimulation is called to let cloned TimeAgents initialize.
OnSimulationComplete

Called when the simulation is complete, either because its max duration is reached or because you called TimeStone.StopSimulation.

Visualizers

Visualizers are helper components made to visualize TimeAgent data over time.

To create your own visualizer, you can extend AbstractTimeVisualizer component which provides a good base, already extending necessary UnityEvents.

More Visualizers will be added over time.

PathVisualizer

screenshot

Visualizer that displays the TimeAgent's path over time using Unity's LineRenderer

  • Stone
  • The TimeStone the visualizer will get its data from

  • Auto Assign Stone
  • If stone is not defined and this option enabled, the stone will be automatically defined with the first TimeStone interacting with the TimeAgent

  • Auto Update on simulation complete
  • Listens to onSimulationComplete event to update the visualization

  • Auto Update on time line change
  • Listens to onTimeLineChange event to update the visualiztion

  • Simplify Tolerance
  • The LineRenderer path will be simplified (with LineRenderer.Simplify) using this value on SimulationComplete or RecordStop

  • Dynamic Simplify Tolerance
  • The LineRenderer path will be simplified (with LineRenderer.Simplify) using this value on TimeLineChange
    This value should be lower than Simplify Tolerance, it's needed because when the path is in creation, its last points can be over simplified wrongly and would generate glitches

Demo projects

Agamotto comes with multiple demo projects demonstrating how to use its features

Kenney Assets

Thanks to Kenney assets, some of this demo can have descent look :)

See amazing free assets provided by Kenney : Kenney.nl

Simple Physics Demo

screenshot

Very simple demo with 3 falling spheres.

You can simulate their path using a physics, freeze and unfreeze them.

They use PathVisualizer to display their path

Complex Physics Demo

screenshot

Same demo than Simple Physics but with much more objects.

Demonstrating that simulation can be exact even with complex physics.

Sorcerer Demo

screenshot

Scene where you can walk around the level with a FPS controller, grab and throw cubes.

You can stop time and simulate the cubes paths (displayed with a PathRenderer

When time is stopped, you can also move the cubes in time.

Pool Demo

screenshot

Remake of a Unity demo for multiple Physics scene feature

Select the angle and power of the shoot then visualize the result before shooting

Custom Time Agent Demo

screenshot

This project shows how to simulate code

One character is going though waypoints, another character is following the first one, you can simulate their path

Tower Defense Demo

screenshot

A tower defense like level

Minions are generated and try to reach the end of the level while you can generate towers that throw projectiles

This project show how to mix physics and code simulation

It also demonstrates how to use objects pools to destroy and spawn objects during a simulation

Particles Demo

screenshot

Demonstrates the use Agamotto to record or simulate particles

Multiple Stones Demo

Very Simple scene to demonstrates that 2 stones can be used simultaneously on the same TimeAgents

FAQ and Use cases

Simulation with private field persisted

When the TimeStone clones an object, the instantiation does not copy the privates fields.

So if the fields you persist in TimeStone are private, you need to initialize them from your Original TimeAgent in SimulationStart for example

See TimeAgent's class code for an example in CacheInitDataFromOriginal and

Simulating complex Particle Systems

Most of the time, the simplest way to simulate complex Particle Systems is to manually reference all its Particle Systems components from the TimeAgent's particle systems list.

In addition, if your Particle System has its own GameObject and its coordinates are in "World" scope, it is better to reference the ParticleSystem from the parent TimeAgent, than adding a TimeAgent component directly to it

Searching for GameObjects in Simulation code

You can use utility class TimeStoneUtils to have equivalent of Unity's Find methods but limited to simulation agents

But remember that you can't use GameObject.Find(name) to find a GameObject in a simulation scene, because as a result of the cloning, it's name is different with "(clone)" added to it.

I get error Exception running SetDataTick on <GameLogic> : System.InvalidCastException: Specified cast is not valid.

You can get this error if you use the wrong generic type calling TimeStone.GetDataValue

For example this code will generate this error because life is a float and not an int:

var lifeData = stone.GetDataValue<int>(lifeDataId, out dataExists);
if (dataExists)
	life = lifeData;
	

So it should be corrected to :

var lifeData = stone.GetDataValue<float>(lifeDataId, out dataExists);
if (dataExists)
	life = lifeData;
	

Contact

Found a bug? Any question? Contact us 7/7, day and night! (but don't expect immediate answer at night... or on sunday... and saturday... And maybe not too early in the morning... at least not before first coffee)