mission: electrician
Mission: Electrician was my project for GMTK Game Jam 2025. The basic idea was simple: make a puzzle-platformer where the player physically routes power through a level, but every connection leaves behind cable, so movement decisions also become circuit decisions. I wanted the game to feel like you were solving an electrical problem spatially rather than just toggling switches in the right order.
After going back through the repo, I think the most interesting part is that I did not build a complicated “electricity simulation.” Most of the game is just a few focused Unity systems wired together carefully enough that the mechanic feels coherent.
The Core Mechanic
The whole game loop comes from a very small rule set:
- move through the level
- connect a new checkpoint with
E - leave that cable segment behind permanently
- avoid crossing the active cable over older segments
- power every required target to finish the level
What I like about this in retrospect is that the player’s path and the puzzle state are the same thing. I did not need a separate abstract graph puzzle. The line the player leaves behind is the graph.
Player Controller
Most of the movement lives in PlayerController.cs. It is a pretty standard 2D Rigidbody controller, but a few decisions there matter for the rest of the game:
- I detect grounded state manually with
Physics2D.OverlapCircleAll - jump input is queued in
Update()and applied inFixedUpdate() - horizontal motion is smoothed with
Vector2.SmoothDamp - moving platforms feed velocity back into the player controller
- electrocution locks the rigidbody, triggers animation, and immediately hands off to the level-loss flow
One detail that ended up mattering more than I expected is the cable anchor offset. Since the cable is visually attached to the player, I had to flip that offset when the character turns around. Otherwise the line starts from the wrong side of the sprite and the whole effect looks cheap.
Building The Cable Path
The main mechanic lives in CableManager.cs.
Instead of trying to maintain one big procedural rope or spline, I represent the route as a chain of LineRenderer segments:
- the game starts with one line from the origin outlet to the player
- when I connect a checkpoint, that current line is tweened into place at the checkpoint
- then I spawn a new live line from that checkpoint back to the player
- the process repeats until the level is done
That structure gave me exactly what I needed for a jam game. Old decisions become fixed geometry, while the current segment stays “hot” and follows the player every frame.
I also lerp the cable color based on distance between the player and the segment start, which helped the live cable feel more dynamic than a flat debug line.
How Crossing Works
The most important failure condition in the game is cable crossing.
I handled that geometrically rather than through tile logic or any kind of physics rope simulation. In CableManager.cs, every frame I check whether the active live segment intersects any previous segment:
- I trim the segment slightly at the ends so touching endpoints does not count as a collision
- I ignore segments that are still too short
- then I run a line intersection test against the earlier stored lines
If the active line crosses an earlier one, I flip the cable red and electrocute the player immediately.
This was one of the better scope decisions in the project. It was simple enough to finish during a jam, but still gave the game its identity. The player is not just platforming between buttons. They are building a path history that constrains every future move.
Checkpoints
I kept the checkpoint architecture intentionally small.
Checkpoint.cs is an abstract base class that:
- registers itself with the level manager
- tracks whether the player is in range
- listens for
E - asks the player’s cable manager what the previous checkpoint was
- hands off to a
NewConnection(prev)method implemented by subclasses
That let me keep the input and connection rule centralized while still making different powered objects behave differently.
In the current repo there are at least two concrete checkpoint types:
LightCheckpoint.cs, which turns on a light, changes color, plays audio, and checks for a winMovingPlatformCheckpoint.cs, which powers a platform and then checks for a win
That pattern let me make levels feel more varied without inventing a whole new system for every interactable.
Powered Objects And Feedback
I wanted powered objects to feel like they were actually “waking up,” even though the underlying logic stayed very simple.
For example, Light.cs just tweens the alpha of a light aura with DOTween. The moving platform script does something similar conceptually: once powered, it starts a DOTween-driven motion from its current position to a target position.
The important thing was not physical realism. It was that powering something produced an immediate visible consequence:
- checkpoint turns green
- audio plays
- light fades in
- platform starts moving
That feedback mattered because the puzzle itself is already fairly strict. The player needs clear confirmation that a risky cable route actually accomplished something.
Level State And Win/Loss
LevelManager.cs is the project’s central coordinator.
It handles:
- checkpoint registration
- level unlock state
- win/loss sounds
- reset flow after death
- advancing to the next scene after a win
The win condition is intentionally blunt: if every registered checkpoint is powered, the level is complete.
For a jam project, that was the right level of abstraction. I did not need save files, puzzle graphs, or a generalized event system. I just needed a stable object that knew when the room was finished and what scene should come next.
One part of that script I still like is the win presentation. When a level is solved, I switch the Cinemachine camera away from the player and push it toward a wider view of the full scene before wiping out. That gave the level a sense of closure without needing a separate cutscene pipeline.
Hazards And Failure
Hazards are simple by design. LaserWall.cs just checks for a PlayerController on trigger enter and calls Electrocute().
I think that simplicity was important. The interesting failure in the game is already the cable crossing itself. Environmental hazards only needed to reinforce the tension, not compete with it using more complicated behavior.
Scene Flow And Wipes
Scene transitions are handled by WipeToNextScene.cs together with CircleController.cs.
The wipe flow is:
- start loading the next scene asynchronously
- delay scene activation
- animate a circular mask closed by tweening a shader radius
- allow the new scene to activate once the wipe is done
This gave me a nice bit of polish for relatively little code. It also made the project feel more cohesive because the same transition language gets reused across level changes, deaths, and menu flow.
Level Select And Progression
The build settings show a simple scene progression:
MainMenuMailSceneLevel1throughLevel7
MenuManager.cs uses the level manager’s unlock state to decide which carrot buttons are interactable. Again, it is not overbuilt, but it does exactly what the project needed.
For a jam game, that was the recurring theme: avoid building systems that are more general than the game itself.
Technical Stack
Looking back through the manifest, the stack is pretty straightforward:
- Unity 2D
- URP
- Cinemachine
- DOTween
- Aseprite import support
One funny detail is that the project includes the newer Input System package, but the actual player input still reads from the legacy Input API directly. That is a very normal game-jam outcome. I clearly set up more than I ended up needing, then shipped with the path that got the mechanic working fastest.
What I’d Keep And What I’d Change
If I were extending the game now, I would probably keep the overall architecture.
The cable system is the right kind of bespoke. It does not try to be generic. It is just enough code to support one mechanic clearly.
The main thing I would revisit is edge-case robustness:
- line intersection logic could be cleaned up more carefully
- checkpoint state could be made more explicit
- level unlock persistence is currently very lightweight
- some of the scene/game-state code would benefit from better separation
But for a jam project, I still think the core decision was right: spend the complexity budget on the mechanic the player can feel, not on infrastructure they never notice.
Credits
- Code / Design: ClawBoomGames
- Art: Abigail G.
- SFX: Zapsplat