One move. Two consequences.
A dual-character puzzle platformer where movement inputs are shared between a green ninja and a pink ninja clone across 16 handcrafted levels, requiring simultaneous arrival at dual exit doors.

When a single player input command broadcasts simultaneously to multiple physics-driven bodies, level geometry complexity multiplies exponentially. A jump that clears a spike pit for Character A can catapult Character B directly into a crushing hazard. The core challenge was making this dual-character relationship understandable, perfectly deterministic, and satisfying rather than arbitrary or punitive.
Engineered an Observer-driven input broadcaster and dual-entity state coordination engine in Unity C# with Raycast-based ground detection.
Created an InputBroadcastHub that decouples raw keyboard/touch reads from entity logic. Both primary player and clone subscribe to identical event channels, applying directional velocity and jump impulses in LateUpdate/FixedUpdate.
Implemented modular CharacterController2D scripts with box raycasting for precise ground detection, coyote time, and jump buffering to ensure responsive platforming feel regardless of entity spacing.
Door sensors operate as state observers. A centralized LevelCompletionValidator monitors both door trigger volumes; only when both Character A and Character B are within their respective matched doors does the level unlock.
using System;
using UnityEngine;
public class DualCharacterCoordinator : MonoBehaviour
{
[SerializeField] private CharacterMovement primaryNinja;
[SerializeField] private CharacterMovement cloneNinja;
[SerializeField] private DoorSensor greenDoor;
[SerializeField] private DoorSensor pinkDoor;
public event Action OnLevelCompleted;
private void Update()
{
// Sample inputs once per frame
float horizontal = Input.GetAxisRaw("Horizontal");
bool jump = Input.GetButtonDown("Jump");
bool interact = Input.GetKeyDown(KeyCode.W) || Input.GetKeyDown(KeyCode.UpArrow);
// Broadcast identical inputs to both active entities
primaryNinja.ProcessMovement(horizontal, jump);
if (cloneNinja != null && cloneNinja.gameObject.activeInHierarchy)
{
cloneNinja.ProcessMovement(horizontal, jump);
}
// Validate simultaneous completion trigger
if (interact && CanCompleteLevel())
{
ExecuteLevelCompletion();
}
}
public bool CanCompleteLevel()
{
bool primaryReady = greenDoor.IsOccupiedBy(primaryNinja);
bool cloneReady = (cloneNinja != null) && pinkDoor.IsOccupiedBy(cloneNinja);
return primaryReady && cloneReady;
}
private void ExecuteLevelCompletion()
{
greenDoor.LockDoor();
pinkDoor.LockDoor();
OnLevelCompleted?.Invoke();
}
}ℹ This centralized coordinator ensures single-frame input parity and strictly enforces that both doors must be occupied before victory state triggers.
Shipped 16 full handcrafted puzzle levels in browser WebGL, receiving enthusiastic community feedback for tight controls and creative level progression.
Handcrafted puzzles with smooth difficulty curve from tutorial to master.
Zero movement deviation between linked entities on identical terrain.
Brotli-compressed build loads in under 4 seconds on desktop and mobile.
Playable right now in your browser with full WebGL performance.