# Time Fracture: Wiring Scenarios
Before this one, do [[TimeFracture-Getting-Started]]. Full path: [[00-Start-Here]].
This tutorial covers four things you can build on top of Time Channels:
1. **Interaction**: the player touches, blocks or shoots an object, and that changes the time on the object's Timeline.
2. **Visual identity**: each Timeline gets its own look, and lenses reveal or hide whole Timelines.
3. **Shared Timelines**: many objects follow one time flow and share one look.
4. **Recording and echoes**: earlier runs of the player replay alongside the current run, so the player can cooperate with themselves.
> [!tip] See it working first
> **Tools ▸ Time Fracture ▸ Build Tutorial Scene (2D)** builds `Assets/TimeFractureTutorial/TimeFractureTutorial.unity`, which has every scenario in this note already wired. Press Play, then use the sections below to see how each piece is put together.
>
> | Keyboard / mouse | Gamepad | Action |
> |---|---|---|
> | A / D or arrows | Left stick | Move |
> | Space | South (A) | Jump |
> | Left click | Right trigger (aim with right stick) | Shoot toward the pointer |
> | J | West (X) | Shoot straight ahead |
> | Q | Right shoulder | Switch charge |
> | Hold R | Hold left trigger | Rewind time |
> | Z / X | Left shoulder / D-pad up | Past Sight / Future Sight |
> | F1 | Select | Hide the help text |
## Before you start: Input System only
Time Fracture uses the **Input System package** only. Nothing calls the old Input Manager, which Unity is deprecating.
1. **Window ▸ Package Manager**: make sure **Input System** is installed. Unity 6 templates include it.
2. **Edit ▸ Project Settings ▸ Player ▸ Other Settings ▸ Active Input Handling**: set it to **Input System Package (New)**. Unity restarts.
Every control in these scripts is an **Input Action property** with default keyboard, mouse and gamepad bindings. In the Inspector you can:
- **Change the bindings in place.** Expand the action and edit or add bindings.
- **Use your own actions asset.** Tick **Use Reference** and drag in an action from your `.inputactions` asset, such as Unity 6's project-wide `InputSystem_Actions` (`Player/Move`, `Player/Jump`, `Player/Attack`). That way, rebinding and control schemes stay in one place.
## New scripts at a glance
| Folder | Script | Job |
|---|---|---|
| Interaction | `TimeInteractable` | Turns touch, shot, block and custom events into time commands for the object's Timeline. |
| Interaction | `TimeBlockSensor` | A trigger zone that reports when the object's path is blocked. |
| Interaction | `TimeProjectile` | A shot that carries a time charge. |
| Interaction | `TimeShooter` | Fires charged shots, records them, and replays them on echoes. |
| Visuals | `TimeVisualTheme` | A reusable look asset (template) for one family of Timelines. |
| Visuals | `TimeVisualizer` | Applies a theme, time-state tints, flashes and lens visibility. |
| Visuals | `TimeLensController` | Filters that show, ghost or hide Timelines by layer. |
| Members | `TimeMember` | Puts any object on a channel's or group's time. |
| Members | `TimeOscillator` | An example of scripted motion that rewinds without being recorded. |
| Recording | `TimeTake` | The recorded data: poses and actions stamped with Timeline time. |
| Recording | `TimeRecorder` | Records the player against a clock and spawns echoes. |
| Recording | `TimeEcho` | Replays a take in sync with the clock. |
| Util | `TimeInput`, `TimeTags` | Input System helpers (default actions, safe enable/disable, pointer aiming) and comma-separated tag lists. |
| Util | `TimeRewindKey` | Hold a button (R / left trigger) to rewind the world or one group. |
| Demo | `TimeDemoPlayer2D`, `TimeDemoHelp` | A minimal platformer controller and on-screen help. |
`TimeCommandTrigger` also has two upgrades. **Required Tag** now accepts lists such as `Player,Echo`. **Count Occupants** makes a trigger act like a pressure plate: Enter fires only when the first object arrives, and Exit only when the last one leaves.
---
## 1. How the player changes an object's time
Every interaction runs through a **TimeInteractable** placed on the object. It holds a list of **Reactions**. Each reaction says when it fires (`TouchStart`, `TouchEnd`, `Shot`, `BlockStart`, `BlockEnd` or `Custom`), which **Commands** to run, and optionally a second set of **Then** commands to run a few seconds later. That second set is how you make an effect temporary.
In those commands, **Self** means the object's Timeline. The interactable finds that Timeline through a `TimeMember` on the object or the nearest `TimeChannel` above it. Because of this, touching any object on a shared Timeline affects the whole Timeline (see section 3).
> [!warning] Keep Rigidbodies off objects that a Timeline animates
> Let the Timeline move them. A Rigidbody on the parent also receives trigger messages from child sensors, so a block sensor would count as a touch.
### 1a. Touching: a platform that freezes while you stand on it
1. Make the platform a child of an object that has a `PlayableDirector` and a `TimeChannel`. The Timeline animates the platform.
2. Give the platform a solid `BoxCollider2D` and add **TimeInteractable** to it:
- Touch Tags: `Player,Echo`
- Touch On Collision: ✔ (so landing on the platform counts, not only entering a trigger)
3. Add two reactions:
| Label | When | Commands | Flash |
|---|---|---|---|
| Freeze while touched | TouchStart | `Freeze` → Self | light blue |
| Resume when left | TouchEnd | `Unfreeze` → Self | none (alpha 0) |
Some variations:
- To get a fixed-length freeze instead, use one TouchStart reaction with `FreezeFor` 2.
- `Reverse` with Then after 1.5 s → `Reverse` makes a platform back up when touched.
- Echoes count as touchers, so a past self can hold a platform still for you.
### 1b. Blocking: a crusher that stops while you stand in its path
1. Give the moving object a solid collider and a **TimeInteractable**. Clear its Touch Tags and turn Touch On Collision off, because this object only uses blocking.
2. Add a child object named `Block Sensor` with a **trigger** `BoxCollider2D` that is wider than the object, then add **TimeBlockSensor** to it. Set Blocker Tags to `Player,Echo`.
3. Add the reactions:
| When | Commands |
|---|---|
| BlockStart | `Freeze` → Self |
| BlockEnd | `Unfreeze` → Self |
The sensor counts how many blockers are inside it. BlockEnd fires only when the last one leaves, so a player and an echo can share the job of blocking.
Some variations:
- BlockStart → `EaseRate` 0.2, BlockEnd → `EaseRate` 1 makes the object strain against you instead of stopping dead.
- BlockStart → `Reverse` (Group "Traffic") makes every car behind it back up.
### 1c. Shooting: charges decide the effect
1. Put **TimeShooter** on the player.
- **Charges** is a list of names and colours, by default Freeze, Rewind, Slow and Skip. A charge is just a word, and each target decides what it means.
- Aim: `Pointer` (toward the mouse, pen or touch) or `Facing`. Pushing the **Aim Stick** (gamepad right stick) always takes over.
- Input actions:
| Action | Default bindings | What it does |
|---|---|---|
| Fire | Left mouse button, right trigger | Shoots toward the aim. |
| Fire Forward | J, West button | Shoots in the facing direction. |
| Next Charge | Q, right shoulder | Switches to the next charge. |
| Aim Stick | Right stick | Aims while pushed. |
Tick **Use Reference** on Fire to use `Player/Attack` from your own actions asset instead.
- With no Projectile Prefab, it builds a glowing dot at runtime. To use your own prefab, give it a `TimeProjectile`, a trigger collider and a Rigidbody2D with Gravity Scale 0.
2. Give each target a collider and a **TimeInteractable** with one `Shot` reaction per charge. Put the charge name in **Filter**:
| Filter | Commands | Then after | Then |
|---|---|---|---|
| Freeze | `FreezeFor` 3 | — | — |
| Rewind | `Reverse` | 2 s | `Reverse` |
| Slow | `EaseRate` 0.25 (ease 4) | 3 s | `EaseRate` 1 (ease 2) |
| Skip | `Skip` 1 | — | — |
A shot with no matching reaction is simply absorbed. Leave Filter empty for a reaction that responds to every charge.
To make a hit affect more than one object:
- Target **Group** instead of Self. For example, `Freeze` → Group `Enemies` from a hit on any enemy freezes all of them.
- Add `SetFlag` commands to change the story. Time Event markers on the Timeline then branch on that flag (see the main README).
### 1d. Any other interaction (melee, hitscan, button press)
Call the interactable from your own scripts:
```csharp
// Hitscan: raycast and treat the hit as a shot
var hit = Physics2D.Raycast(muzzle.position, aimDir, 20f);
if (hit.collider != null)
hit.collider.GetComponentInParent<TimeInteractable>()?.Shoot("Rewind", gameObject);
// A "Use" button near an object, written as an Input Action field
// (edit the binding in the Inspector, or tick Use Reference for Player/Interact)
public InputActionProperty use = TimeInput.Prop(TimeInput.Button("Use", "<Keyboard>/e", "<Gamepad>/buttonNorth"));
void OnEnable() => TimeInput.Enable(use);
void OnDisable() => TimeInput.Disable(use);
void Update()
{
if (TimeInput.WasPressed(use) && nearbyInteractable != null)
nearbyInteractable.Interact("Use"); // matched by Custom reactions with Filter "Use"
}
```
`TimeInput.Disable` turns off only embedded actions. Actions from a shared asset are left alone, so one script switching off doesn't disable another script's controls.
```codefile Runtime/Interaction/TimeInteractable.cs
```
```codefile Runtime/Interaction/TimeBlockSensor.cs
```
```codefile Runtime/Interaction/TimeProjectile.cs
```
```codefile Runtime/Interaction/TimeShooter.cs
```
---
## 2. Telling Timelines apart
### 2a. Themes: one template per Timeline family
Create a theme with **Create ▸ Time Fracture ▸ Visual Theme**. Make one per family of Timelines:
| Theme | Lens layer | Tint | Tint Strength | Alpha | Ghost Alpha | Ghost Is Tangible |
|---|---|---|---|---|---|---|
| Present | Present | orange | 0.55 | 1 | 0.35 | ✔ |
| Hazard | Present | red | 0.6 | 1 | 0.35 | ✔ |
| Past | Past | blue | 0.75 | 0.9 | 0.2 | ✘ |
| Future | Future | magenta | 0.75 | 0.9 | 0.25 | ✘ |
| Echo | Echo | pale white | 0.8 | 0.45 | 0.2 | ✔ |
Then add **TimeVisualizer** to the object that has the Timeline's `TimeChannel`, and assign the theme. Everything under that object is styled:
- **Tint** blends each renderer toward the theme colour.
- **Alpha** fades the whole Timeline.
- **Override Material** swaps in, for example, an unlit additive "hologram" material for every object on that Timeline.
- **State tints** show what time is doing on that Timeline. It turns blue when frozen (with a gentle pulse), red when rewinding, violet when slow and gold when fast. Players can read time from colour.
- **Flash** is a brief colour pulse when the object is hit or touched. It's set per reaction in `TimeInteractable`.
Edit a theme asset once and every Timeline that uses it updates.
> [!note] How each kind of object is coloured
> Sprites are tinted through `SpriteRenderer.color`. 3D renderers get a `MaterialPropertyBlock` (`_BaseColor` / `_Color`), so materials are never duplicated. A 3D material only fades if its Surface Type is Transparent. If a Timeline also animates a sprite's colour, the visualizer overrides it, so animate a child instead.
### 2b. Lenses: filters that reveal or hide whole Timelines
Every theme belongs to a **lens layer**. Add one **TimeLensController** to the scene:
- **Base Rules** decide how each layer looks normally. In the tutorial scene, `Past` is Hidden and `Future` is Ghost.
- **Lenses** are filters the player switches on. Each has a **Toggle** input action, a screen tint and its own rules:
| Lens | Toggle bindings | Rules |
|---|---|---|
| Past Sight | Z, left shoulder | Past = Normal, Future = Hidden, Present = Ghost |
| Future Sight | X, D-pad up | Future = Normal, Past = Hidden, Present = Ghost |
The three visibility settings work like this:
- **Hidden**: renderers are off and, by default, so are colliders. A hidden bridge can't be stood on until the right lens reveals it.
- **Ghost**: the layer fades to the theme's Ghost Alpha. Whether it stays solid depends on the theme's **Ghost Is Tangible**.
- **Normal**: the layer looks and collides as usual.
Some extras:
- Set **World Scale While Lens On** to 0.3 to slow the world while the player looks through a lens.
- To switch a lens from gameplay, call `Toggle("Past Sight")` from a `TimeCommandTrigger`'s **On Fired** UnityEvent. You can also call `TimeLensController.Current.SetLens(...)` from code.
> [!tip] Other ways to separate Timelines visually
> - Put each Timeline's objects on its own Unity Layer and use a second camera, or toggle `Camera.cullingMask`, for full-screen effects such as a sepia past.
> - In URP, a Full Screen Pass Renderer Feature whose material is enabled per lens gives grain or chromatic effects.
> - Sorting Layers per Timeline keep echoes and ghosts drawn behind solid objects.
```codefile Runtime/Visuals/TimeVisualTheme.cs
```
```codefile Runtime/Visuals/TimeVisualizer.cs
```
```codefile Runtime/Visuals/TimeLensController.cs
```
---
## 3. Many objects on the same time flow
Pick the option that matches how the objects move.
### Option A: one Timeline animates them all (a rig)
Use this when the objects are choreographed together, like the three lamps in the tutorial scene.
1. Create an empty **rig** object with a `PlayableDirector`, a `TimeChannel` (Channel Id `Lamps`) and a `TimeVisualizer` (theme `Lamps`).
2. Put the objects under the rig as children.
3. In the Timeline, add one **Animation Track per child** and bind each track to that child's Animator.
4. Give each child its own collider and `TimeInteractable`. "Self" resolves to the rig's channel, so shooting **any** lamp rewinds or freezes **all** of them.
One channel means one time flow, one visualizer means one look, and one interaction affects the whole rig.
### Option B: objects elsewhere in the scene join a channel (TimeMember)
Use this for objects that aren't children of the rig and aren't animated by its Timeline.
1. Add **TimeMember** to the object and set Source to `ChannelId` with Channel Id `Lamps`. You can also use `ChannelReference`, or `ParentChannel` for objects inside the rig.
2. Turn on **Include Members** on the rig's TimeVisualizer. The member picks up the rig's tint, state colours and lens visibility.
3. Interactables on the member also resolve "Self" to `Lamps`.
4. To make the member's own motion follow that time, use `member.DeltaTime`, which is signed and 0 while frozen, or `member.LocalTime`, which runs backwards while rewinding. `TimeOscillator` is a worked example: its position is computed from `LocalTime`, so it freezes, slows and rewinds with the rig and doesn't need to be recorded.
5. TimeMember can also drive **Animator speed** (paused while rewinding), **ParticleSystem speed**, and optionally **AudioSource pitch** (negative pitch plays backwards).
```csharp
// Any script: write motion as a function of the member's time, and rewinding comes free.
void Update()
{
float t = (float)member.LocalTime;
transform.position = start + Vector3.right * Mathf.PingPong(t * 2f, 4f);
}
```
### Option C: separate Timelines, same group
Use this when each object needs its own Timeline but they should speed up, slow down and freeze together. Put them all in the same **Group**, for example `Clocks`, through the TimeChannel's Groups list. Then:
- `TimeWorld.SetGroupScale("Clocks", -1, 2f)` (or the command `EaseRate` → Group `Clocks`) rewinds all of them.
- A themed visualizer on each of them makes the group read as one family. Give them all the same theme asset.
| You want… | Use |
|---|---|
| One choreography across many objects | Option A (rig) |
| Loose props that follow a rig's time and look | Option B (TimeMember) |
| Independent Timelines that share speed and freeze | Option C (group) |
```codefile Runtime/Members/TimeMember.cs
```
```codefile Runtime/Members/TimeOscillator.cs
```
---
## 4. Recording the player and cooperating with past runs
### The idea
The level has a **clock**: a TimeChannel on a looping Timeline. In the tutorial scene it's 12 s long, with a sun crossing the sky.
The **TimeRecorder** on the player records their pose, sprite frame, facing and actions (such as shots) every frame. Each sample is stamped with the **clock's Timeline time**, not with real time.
When the clock loops, the finished run becomes a **TimeEcho**. Each frame, the echo looks up "where was I at clock time *t*?" Because it reads the clock, every echo stays in sync with the world. Slow the world and echoes slow. Freeze it and they freeze. Rewind it and they walk backwards.
### Setup
1. Make the clock: an object with a PlayableDirector, a looping Timeline, and a `TimeChannel` with Channel Id `Loop` and End Behaviour **Loop**.
2. Add **TimeRecorder** to the player:
- Clock: the clock channel (or Clock Id `Loop`)
- Commit On Loop ✔, Max Echoes 3
- Echo Theme: `Echo` (pale, translucent)
- Echo Tag: `Echo`. The tutorial builder adds this tag. Otherwise add it in **Project Settings ▸ Tags and Layers**.
- Solid Echoes: ✘ by default. Turn it on to let the player stand on their past self.
- Rewind With Clock ✔, with **Disable While Rewinding** set to your player controller and the TimeShooter.
3. Optionally, add **TimeRewindKey** so the player can scrub time back by holding its Rewind action (R or the left trigger). The player rewinds along their own path, and when they let go, the recording continues from that point.
With no **Echo Prefab**, the recorder builds a ghost automatically. It copies the player's sprite and collider, adds a trigger collider with a Rigidbody2D (gravity 0), copies a TimeShooter if the player has one, and applies the Echo theme. For 3D players or animated rigs, make an **Echo Prefab** with a `TimeEcho` on it, plus anything that should react to replayed actions.
### A cooperation puzzle: hold the door
| Object | Setup |
|---|---|
| **Door** | Its own Timeline (1 s, slides up). TimeChannel with Channel Id `Door`, Play On Start ✘, End Behaviour Hold. |
| **Plate Zone** | A trigger collider with **two** TimeCommandTriggers. |
| Trigger 1 | On Trigger Enter 2D, Required Tag `Player,Echo`, Count Occupants ✔. Commands: `SetRate 1` → Channel `Door`, then `Play` → Channel `Door`. |
| Trigger 2 | On Trigger Exit 2D, same tags. Commands: `SetRate -3` → Channel `Door`, then `Play` → Channel `Door`. The door closes three times faster than it opened. |
Alone, the player steps off the plate and the door slams shut before they can reach it. The solution:
1. On run 1, stand on the plate from about 8 s until the loop.
2. On run 2, your echo stands on the plate at 8 s, so the door stays open while you run through.
Because the plate counts occupants, it doesn't matter whether you, your echo, or both are standing on it.
More ideas built from the same parts:
- **Echo shooting**: a target needs Freeze and Rewind hits at the same moment. Fire one charge on run 1, and your echo repeats that shot on run 2 while you fire the other.
- **Echo blocking**: an echo standing in front of a crusher, through a TimeBlockSensor with tags `Player,Echo`, holds it still while you pass underneath.
- **Paradox rules**: if the player touches their own echo, run `SetFlag Paradox`. Timeline markers can branch on it, for example jumping the level to a "collapse" segment.
### Recording your own actions
```csharp
// When the player does something an echo should repeat:
recorder.RecordAction("Lever", leverId); // id, text argument
recorder.RecordAction("Dash", null, dashDirection); // id, vector argument
// On the echo (prefab or auto-built), react to the replay:
public class EchoLever : MonoBehaviour, ITimeEchoActionReceiver
{
public void OnEchoAction(TimeActionSample a)
{
if (a.id == "Lever") FindLever(a.arg).Pull();
}
}
```
Actions replay only while time moves forward. Rewinding past an action doesn't "un-pull" the lever, so make lever-like things Timeline-driven (rewinding the lever's channel undoes it) or flag-driven.
### Rules worth knowing
- **Rewriting history**: if the clock goes back (rewind, a jump, or a loop with Commit On Loop off) and the player carries on, everything recorded after that moment is replaced.
- **Frozen clock**: if the clock is frozen, the player can still move, but nothing new is recorded, because recordings live in clock time. To record during a freeze, put the clock in a group that your freeze effects don't touch.
- **Commit manually**: call `CommitTake()` from a checkpoint, a "split" key, or a TimeCommandTrigger's On Fired event, and call `ClearEchoes()` to reset the puzzle.
- **Save data**: echoes are `TimeTake` objects, which are plain serializable data. You could save their poses and actions with `JsonUtility` and load a "ghost" of a friend's run. Sprite references don't survive JSON, so save sprite names instead.
```codefile Runtime/Recording/TimeTake.cs
```
```codefile Runtime/Recording/TimeRecorder.cs
```
```codefile Runtime/Recording/TimeEcho.cs
```
---
## 5. Utilities and demo scripts
`TimeInput` holds the Input System helpers that every script uses:
- `Button(...)` and `Move()` build default actions with keyboard, mouse and gamepad bindings.
- `Enable` and `Disable` switch an `InputActionProperty` on and off safely; shared asset actions are never disabled.
- `WasPressed`, `IsPressed` and `ReadVector2` read actions.
- `PointerWorld2D()` turns the pointer position into a 2D aim point.
`TimeTags.Matches(collider, "Player,Echo")` checks comma-separated tag lists and never errors on undefined tags.
```codefile Runtime/Util/TimeInput.cs
```
```codefile Runtime/Util/TimeRewindKey.cs
```
```codefile Runtime/TimeCommandTrigger.cs
```
```codefile Runtime/Demo/TimeDemoPlayer2D.cs
```
```codefile Runtime/Demo/TimeDemoHelp.cs
```
```codefile Editor/TimeFractureTutorialBuilder.cs
```
---
> [!tip] Next
> To make AI enemies, bullets and physics props rewind along with the player, see [[TimeFracture-Tutorial-Enemies]].
## Troubleshooting
| Symptom | Check |
|---|---|
| Touch or block never fires | One of the two objects needs a Rigidbody2D (the player does). Make sure the tags are in the Touch Tags or Blocker Tags list, and that the tag exists in the Tag Manager. |
| A sensor fires "touch" on its parent | Remove the Rigidbody from the Timeline-animated parent. |
| Shots pass through a target | Its layer may be Hidden or a non-tangible Ghost under the current lens, which turns its colliders off. |
| A 3D object won't fade | Set its material's Surface Type to Transparent. |
| Echoes don't appear | The clock must actually loop (End Behaviour Loop) and be running forward. A run shorter than 2 samples is ignored. |
| An echo doesn't press the plate | Echo Tag must be in the plate's Required Tag list, and the Echo tag must exist. |
| An animated object jumps to the origin | On its Animation Track clip, turn off **Remove Start Offset**, and set the track to Apply Transform Offsets. The builders do this for you. |