# Time Fracture: Rewindable Enemies
A Timeline can rewind itself, because it always knows where everything was. An AI enemy or a physics object doesn't. Its past only exists if something records it. This tutorial shows how to make enemies, bullets and physics props rewindable, so the player can change the outcome of a fight and rewind to try again.
Related notes: [[00-Start-Here]] · [[TimeFracture-Getting-Started]] · [[TimeFracture-Tutorial-Wiring]] · [[TimeFracture-Scripts]] · [[TimeFracture-Design-Ideas]]
> [!tip] See it working first
> **Tools ▸ Time Fracture ▸ Build Enemy Rewind Demo (2D)** builds `Assets/TimeFractureTutorial/TimeFractureEnemyDemo.unity`. Press Play.
>
> | Keyboard / mouse | Gamepad | Action |
> |---|---|---|
> | A / D | Left stick | Move |
> | Space | South (A) | Jump |
> | Left click | Right trigger | Shoot toward the pointer (the right stick aims) |
> | J | West (X) | Shoot straight ahead |
> | Q | Right shoulder | Switch charge: **Strike**, Freeze, Rewind, Slow |
> | Hold R | Hold left trigger | Rewind the world |
>
> Things to try:
> - **Kill a guard, then hold R.** It comes back to life, and its bullets fly backwards into its gun.
> - **Get hit.** Time automatically rewinds about 2 seconds.
> - **Hit the sentry with Rewind.** Only the sentry and its bullets go backwards; the world keeps going.
> - **Walk up to the patrol guard.** It leaves its Timeline route to chase you. Rewind past that moment and it snaps back onto its patrol.
> - **Wait for the 20-second loop.** Everything resets, and your echo can lure a guard away while you get past it.
---
## 1. The idea: every live object keeps its own history
**TimeRewindable** gives an object its own clock, called *local time*. That clock moves at the object's **time rate**, which comes from the first of these that exists:
1. **The object's own TimeChannel** (found through a TimeMember or a parent). This is what lets Freeze, Rewind and Slow shots affect a single enemy. The channel doesn't need a Timeline: a TimeChannel with an empty PlayableDirector works purely as a "time state."
2. **Another TimeRewindable**, set as Time Source. Bullets use this to follow the enemy that fired them.
3. **The level clock**. Any World rewind or freeze also reaches the object through the World group.
What happens depends on which way that rate points:
| Rate | What TimeRewindable does |
|---|---|
| **Forward** | Records a sample about 30 times a second: position, rotation, scale, velocity, alive or dead, and the state of every `ITimeStateful` script on the object. |
| **Zero** (frozen or paused) | Holds still. Rigidbodies switch to kinematic and keep their velocity for later. |
| **Backward** | Plays the samples back, pauses the object's scripts, and stops physics. |
| **Forward again** | Throws away the recorded "future," restores the exact state at that moment, and lets the scripts and physics carry on live. |
| **Level clock loops** | Restores the state from when the scene started, and removes anything spawned at runtime. |
This is the same "rewrite history" rule the player's TimeRecorder uses, so the player and enemies always agree about what happened.
---
## 2. Three kinds of rewindable objects
### 2a. A physics prop (no AI)
The demo's **Crate** works this way.
1. Give the object a Rigidbody2D and a collider as usual.
2. Add **TimeRewindable**:
- Mode: `Live`
- Clock: the level clock (or Clock Id `Loop`)
That's all it needs. Push the crate off a ledge, hold R, and it flies back up. Freeze the world and it hangs in mid-air, then falls with the speed it had once time resumes.
> [!note] Physics in slow motion
> Unity's physics can't run at a different speed for each object. While time is stopped, TimeRewindable holds bodies still, and during rewind it plays them back from the recording. At slow rates such as 0.3×, a physics prop still falls at normal speed. For slow motion, move objects in your own scripts using `DeltaTime` (section 3).
### 2b. A live AI enemy
The demo's **Sentry** works this way.
1. **Body**: a sprite and a solid collider. A Rigidbody is optional.
2. **TimeChannel** with no Timeline, Channel Id `Sentry`, Groups `World`. This is the sentry's personal time. Adding TimeChannel also adds an empty PlayableDirector, and that's fine.
3. **TimeRewindable**: Mode `Live`, Clock = the level clock.
4. **Your AI script**, written the rewind-friendly way (section 3). The demo uses `TimeDemoGuard2D` with Chase Speed 0.
5. **TimeInteractable** with Shot reactions such as Freeze, Rewind and Slow on **Self**. Self means the sentry's own channel, so only the sentry is affected.
6. Optionally, a **TimeVisualizer** on the same object. It shows the sentry's time state as colour and hides it cleanly when it dies.
### 2c. A scripted enemy that can be disturbed
The demo's **Patrol Guard** works this way, and it's the mixed approach from the design notes. The guard follows a Timeline route that repeats exactly every loop until something disturbs it. After that, it acts on live AI for the rest of the loop.
1. **Root object**: a PlayableDirector, a Timeline with the patrol animation, and a **TimeChannel** with End Behaviour Loop and Groups `World`. Add a TimeVisualizer here if you like.
2. **Child "Guard"** (bound to the Animation Track):
- **TimeRewindable**: Mode `Scripted Until Disturbed`, Clock = the level clock
- Your AI script
- **TimeInteractable**
3. In the AI script, call `rewindable.Disturb()` when the enemy notices the player. The demo also disturbs the guard when it's hit.
What `Disturb()` does:
- The guard's channel becomes **Detached**. Its playhead keeps moving, so loops and timing stay in step, but the Timeline stops posing the guard and its markers stop firing.
- Every sample records whether the channel was detached at that moment. When you rewind past the moment of disturbance, the channel re-attaches and the guard snaps back onto its route, exactly where the script says it should be.
- The **onDisturbed** UnityEvent fires, which you can use for an alert sound or a "!" sprite.
> [!tip] Why this mix works well for puzzles
> Undisturbed enemies repeat exactly every loop, so echoes and plans stay reliable. Enemies only become unpredictable when the player causes it, and the player can always rewind that away.
---
## 3. Writing rewind-friendly AI scripts
The rules below are what `TimeDemoGuard2D` follows:
| Rule | Why | How |
|---|---|---|
| **Save every changing variable** | A rewound enemy with the wrong health or cooldown has broken state. | Implement `ITimeStateful`. Return a **new** snapshot from `CaptureState()` and copy the values back in `RestoreState()`. |
| **Use the object's own delta time** | Freeze, Slow and Rewind only affect what reads the object's time. | Use `rewindable.DeltaTime` instead of `Time.deltaTime`. It's 0 while frozen or rewinding. |
| **Never Destroy, call Kill()** | A destroyed object can't come back when time is rewound. | Call `rewindable.Kill()`. It hides the object, makes it intangible and pauses its scripts. Rewinding revives it. |
| **Spawn with Link** | Bullets should rewind with the enemy that fired them. | Call `TimeRewindable.Link(bullet, rewindable)` or `TimeRewindable.Spawn(prefab, pos, rot, rewindable)`. |
| **Ignore physics messages during replay** | `OnTrigger…` and `OnCollision…` still fire on paused scripts while history replays. | Start each handler with `if (rewindable.IsPlayback || !rewindable.IsAlive) return;` |
| **Keep sensing cheap and stateless** | Targets are found again each time, so they don't need saving. | Re-scan every 0.2 s or so. Don't store targets in the snapshot. |
```csharp
public class Turret : MonoBehaviour, ITimeStateful
{
public GameObject bulletPrefab;
public Transform muzzle;
TimeRewindable rewind;
int hp = 3;
int ammo = 6;
float cooldown;
void Awake() => rewind = GetComponent<TimeRewindable>();
// 1. Save what changes
class Snap { public int hp, ammo; public float cooldown; }
public object CaptureState() => new Snap { hp = hp, ammo = ammo, cooldown = cooldown };
public void RestoreState(object s) { var x = (Snap)s; hp = x.hp; ammo = x.ammo; cooldown = x.cooldown; }
void Update()
{
float dt = rewind.DeltaTime; // 2. Use the object's own time
if (dt <= 0f) return; // 0 while frozen or rewinding
cooldown -= dt;
if (cooldown <= 0f && ammo > 0) { cooldown = 1f; ammo--; Fire(); }
}
void Fire()
{
var b = Instantiate(bulletPrefab, muzzle.position, muzzle.rotation);
TimeRewindable.Link(b, rewind); // 4. The bullet rewinds with the turret
}
public void TakeHit() { if (--hp <= 0) rewind.Kill(); } // 3. Kill, don't Destroy
}
```
> [!note] Bullets and other spawned objects
> Linked spawns remember their **birth time**:
> - Rewinding to before the bullet existed hides it.
> - Letting time run forward from before its birth deletes it. The AI fires again on its own if it still decides to.
> - A spawn that has been dead for longer than **Forget Dead After** seconds is deleted for good, which keeps the scene tidy.
> - When the owner resets at a loop, its spawns are removed.
---
## 4. Outcomes the player can now change
| Player does… | Result | Comes from |
|---|---|---|
| Defeats a guard, then rewinds | The guard is alive again, with its health back to what it was. | `Kill()` plus the ITimeStateful snapshot |
| Gets shot | The world rewinds about 2 s and the bullet goes back toward the gun. | `TimeAutoRewind.Trigger()` plus linked bullets |
| Shoots the sentry with Freeze | The sentry and its bullets in flight stop. The player and world don't. | The sentry's own channel and DeltaTime |
| Shoots the sentry with Rewind | Its last 2 seconds of actions replay backwards, then it continues live. | Per-object playback, then rewrite on resume |
| Lures the patrol guard with an echo | The guard chases the echo instead. | `targetTags = "Player,Echo"` |
| Rewinds before the guard noticed them | The guard returns to its exact patrol. | Re-attaching the Timeline when replay passes the disturbance |
| Waits out the loop | Everything resets, and the echoes of previous runs replay. | `Reset On Loop`, TimeRecorder |
> [!warning] Divergence with echoes
> Echoes replay fixed recordings, but live enemies react to what's happening *now*. An echo's recorded shot can miss because the guard moved differently this loop. That's deliberate in this design. If a puzzle needs certainty, keep those enemies **scripted** (undisturbed) or put them on a Timeline.
---
## 5. Settings reference
**TimeRewindable**
| Field | Meaning |
|---|---|
| Mode | `Live`, or `Scripted Until Disturbed` |
| Pause During Playback | Scripts to switch off while replaying or dead. ITimeStateful scripts are added automatically. |
| Channel | The rate source. Empty means a TimeChannel found through TimeMember or a parent. |
| Clock / Clock Id | The level clock whose loop resets this object. |
| Time Source | Follow another rewindable's clock. Set automatically by `Link` and `Spawn`. |
| Sample Rate | Samples per second of local time. 30 is plenty, and 15 is fine for slow enemies. |
| Reset On Loop | Restore the starting state when the clock loops. |
| Freeze Physics When Stopped | Make Rigidbodies kinematic while frozen or paused. |
| Spawned At Runtime / Forget Dead After | Set automatically for spawns; controls clean-up. |
| Events | `onDisturbed`, `onKilled`, `onRevived`, and the C# event `WasReset` |
Useful properties for scripts: `LocalTime`, `DeltaTime`, `Rate`, `IsPlayback`, `IsAlive`, `IsDisturbed`. Useful methods: `Disturb()`, `Kill()`, `Revive()`, `ResetToStart()`, `Link()`, `Spawn()`.
**TimeChannel.Detached**: when true, time keeps running but the Timeline stops posing its objects and firing markers. TimeRewindable sets it for you; you can also set it from your own scripts.
> [!note] Memory
> History grows only until the next loop or reset. At 30 samples a second, a 60-second loop is about 1,800 small samples per object, which is fine for dozens of enemies. For long levels without loops, lower Sample Rate.
---
## 6. Troubleshooting
| Symptom | Check |
|---|---|
| The enemy doesn't freeze or slow when shot | Its AI must use `rewindable.DeltaTime`, and it needs its own TimeChannel. A channel with no Timeline is fine. |
| The enemy comes back with the wrong health or state | That variable isn't in `CaptureState()`, or the snapshot shares a list or reference instead of copying it. |
| Bullets hit the player again while being rewound | Add the `IsPlayback` / `IsAlive` guard at the top of the collision handler. |
| A dead enemy reappears when you switch lenses | Use `Kill()`, not `SetActive(false)`. TimeVisualizer knows to keep dead rewindables hidden. |
| A scripted guard teleports after a rewind | That's correct behaviour: it re-attaches to its Timeline route at the current time. To make the snap less abrupt, end live behaviour where the route resumes, or shorten the patrol. |
| A physics prop drifts after rewinding | Make sure it has its own TimeRewindable rather than relying on a parent's. |
---
## 7. Scripts
```codefile Runtime/Rewind/TimeRewindable.cs
```
```codefile Runtime/Rewind/ITimeStateful.cs
```
```codefile Runtime/Demo/TimeDemoGuard2D.cs
```
```codefile Runtime/Demo/TimeDemoBullet2D.cs
```
```codefile Runtime/Demo/TimeAutoRewind.cs
```
```codefile Editor/TimeFractureEnemyDemoBuilder.cs
```
`TimeChannel` gained the **Detached** property, and a channel with no Timeline now still runs its freeze timers and rate easing:
```codefile Runtime/TimeChannel.cs
```