Afterimage

A 2D puzzle platformer where your past self is your only tool

Role
Solo Developer - Design & Engineering
Engine
Unity 2D (C#)
Status
Completed - May 2026

Play it: itch.io | Source Code



Design Intent

The echo replays exactly what you did, 1.2 seconds later. That rigidity is the entire puzzle space. You have to think ahead, because what you do now becomes a tool (or a problem) in a moment.

Two inputs. One system. Nothing exists outside it. Eight levels, each asking a different question of that same system.


Key Design Decisions

The echo is a transform replayer, not a physics object

The echo has no Rigidbody. It reads positions from a circular buffer and sets its transform directly. If the buffer recorded the player mid-air, the echo is mid-air. Not because of physics, just because that's what the buffer says.

This means the echo can help the player reach positions that are physically impossible to reach. A platform floating over a void. A spot past a sweeping laser. A ledge across a gap with no surface. Teleporting to any of those puts you somewhere no normal path could get you. That's the whole game.

The buffer is a fixed-size circular array

512 entries at 50Hz FixedUpdate. Write head advances every frame, wraps on overflow. The 1.2-second delay is just reading 60 frames behind the write head. Changing the delay means changing an index offset.

Each frame, the write head advances and wraps:

buffer[writeHead] = frame;
writeHead = (writeHead + 1) % bufferSize;

Reading at a delay is a single index calculation — no iteration, no search:

int index = (writeHead - delayFrames + bufferSize) % bufferSize;

The + bufferSize before the mod prevents negative indices when delayFrames > writeHead (early in the session, before the buffer fills). Changing the echo delay at runtime is a single integer:

delayFrames = Mathf.RoundToInt(echoDelaySeconds / Time.fixedDeltaTime);

At 50Hz with a 1.2s delay that's 60 frames. Adjust echoDelaySeconds and the read head just jumps to the new offset instantly — no restructuring, no copying. That's what makes dynamic delay trivial if the design ever calls for it.

Each entry is a value-type struct (position, velocity, facing, animation state), so the array stores data inline rather than heap pointers.

All visuals are procedural

Every visual in the game is generated at runtime from a single 1x1 white pixel sprite, scaled and tinted. The static charge hazard draws crackling arcs between emitter nodes using LineRenderers, jittering every 60ms. The trace laser raycasts each frame and stops at the first solid collider, drawing a three-layer line (white core, mid glow, soft bloom). Zone boundaries auto-position from BoxCollider2D bounds at Awake.


Zone System

The base game has two verbs: freeze and teleport. Zones modify what those verbs do.

Momentum Zone (cyan): Teleporting preserves your current speed but redirects it to match the echo's stored velocity direction. You bring the force, the echo aims it. This creates a two-step ritual: scout jump to plant a direction in the buffer, freeze at the angle you want, build speed, then teleport. The aim and the launch are separate actions.

Kinetic Freeze Zone (amber): Freezing the echo doesn't lock it in place. Instead it inherits the velocity it had at that replay frame and slides until it hits geometry. You deploy the echo like a projectile, then teleport to where it lands, or jump onto it for a ride!

Echo Split Zone (green): Freezing plants two echoes at different buffer offsets simultaneously. Two platforms from one input.

Each zone announces itself with a screen flash and a riddle that follows the player. Direct enough to point at the mechanic, vague enough not to spoil the solve.

↑↓ navigate ↵ open esc close ⌘K toggle