Pogo Carrot
Physics Traversal | Pogo Controller | UE5 C++ | Solo Design + Programming
Play The Game →Summary
Pogo Carrot is a physics traversal prototype built solo in Unreal Engine 5 over two weeks.
The core mechanic is a pogo stick controller where bounce and movement are physically coupled —
charge intensity directly determines launch trajectory and speed, making every hop a deliberate
movement decision rather than a fixed-height jump.
The project served as a focused engine migration challenge: porting a Unity-style event bus framework
and a physics-based character controller architecture into Unreal C++ from scratch, adapting familiar
patterns to a new engine's physics model and API.
Project Details
Mechanics
Charge-driven pogo controller — hold to charge, release to launch; charge level scales both height and horizontal velocity
Automated camera system decoupled from movement input, solving the two-input constraint without sacrificing feel
Pre- and post-bounce coyote time for both standard and charged bounces, preserving player intent at the edge of a jump
Technical
Fully C++-based — no Blueprint logic in the movement system
Event bus architecture ported from Unity C# to Unreal C++, organized into domain sub-buses (Race, Character) on the Game Instance
Custom movement mode (MOVE_Custom) extending UCharacterMovementComponent — manual velocity, gravity, and collision resolution via CMC's own sweep API
Team and Time
Team Size: 1 (Solo project)
Time: 2 weeks (Prototype Production)
Engine: Unreal Engine 5 (C++)
Goals
The goal of Pogo Carrot was to prototype a physics-driven traversal mechanic that felt
expressive and skill-rewarding within a tight two-week window — while simultaneously treating
the project as an engine migration exercise.
The core design question was: what does it feel like to move when every variable of your
jump is under your control? By coupling charge intensity directly to both height and
horizontal velocity, the pogo controller turns traversal into a continuous input problem —
not a binary "press jump" decision.
On the technical side, the constraint was to bring proven Unity patterns — specifically an event
bus architecture and a custom character controller approach — into Unreal Engine 5 without falling
back on Blueprint. The goal was to understand how UE5's delegate system and movement pipeline differ
and adapt the architecture accordingly, not just replicate surface-level behavior.
Systems
Charge-Coupled Pogo Controller
Movement runs as a custom mode (MOVE_Custom) inside UCharacterMovementComponent,
managing its own velocity variable each tick. Gravity is applied manually when airborne;
collision is resolved via CMC's SafeMoveUpdatedComponent and SlideAlongSurface,
stripping velocity into a surface on contact. Ground detection is a short line trace from
the capsule bottom.
Horizontal movement applies force in camera-relative directions each tick, with a braking
multiplier when input opposes the current velocity and an air control multiplier when airborne.
Drag decelerates and snaps to zero when no input is held.
On bounce, ApplyBounce decomposes the launch vector into two components relative to
the impact surface: along-normal (carrying the vertical force) and along-surface (carrying
the horizontal force). This means slopes naturally redirect the player — an angled wall
bounces you diagonally without any special-case logic.
Charge produces three distinct outcomes based on timing: Power (released in a coyote
window while charged → scales toward MaxBounceForce), Penalty (charging but missed
the window → scales toward PenaltyForce), or Default (no charge attempt).
Automated Camera System
A pogo controller creates a two-input problem: charging consumes the same input axis that would normally handle camera rotation, leaving the player unable to aim and charge simultaneously. The camera system resolves this by automating rotation during the charge phase — tracking movement direction and blending smoothly back to player control on release, so feel is preserved without requiring a second analog input.
Coyote Time (Pre- and Post-Bounce)
The coyote system has two distinct windows managed by BounceForceManager, each
covering a different player intention:
Pre-coyote (charge then land): the player charges and releases before the pogo
tip contacts the ground. On release, a coyote timer starts and the charged state is saved.
If the bounce detector fires before the timer expires, the landing is treated as a
Power bounce — the player's early commitment is honored.
Post-coyote (land then charge): the player lands on a natural (uncharged) bounce
first. On contact, a coyote timer starts and the impact normal is saved in the bounce
detector. If the player releases a charge before the timer expires, an
OnPostCoyoteRelease event fires and the bounce detector replays the bounce using
the saved normal — the player can react to the landing and still get a Power bounce.
The two windows are mutually exclusive: bouncing while in pre-coyote consumes it and
blocks post-coyote for that contact, preventing double-rewarding.
Technical Challenge: Porting an Event Bus from Unity to Unreal C++
The pogo controller relies on decoupled communication between systems — the controller, camera,
animation layer, and coyote time logic all need to react to bounce events without holding direct
references to each other. In Unity (C#), this was handled by a typed event bus built with
generics and delegates: systems subscribe to strongly-typed event channels and fire them
without knowing who is listening.
Unreal Engine 5 provides its own event and delegate infrastructure, but it is macro-heavy,
Blueprint-oriented by default, and does not map cleanly to a generic typed channel pattern.
Adapting the Unity framework meant working within UE5's macro system, Unreal's object model
(UObject lifecycle, garbage collection), and a templated C++ environment with stricter
constraints than C# generics.
The challenge was to reproduce the same decoupled pub-sub pattern in Unreal C++ without
losing type safety or requiring Blueprint glue code.
Solution
I implemented the bus as a UObject held directly on UPogoGameInstance and accessed
via Cast<UPogoGameInstance>(GetWorld()->GetGameInstance())->EventBus from any
component that needs it. This keeps the bus at game-instance scope without a subsystem
registration step and gives every UObject a single, consistent access path.
Rather than a flat channel registry, the bus is organized into domain sub-buses —
EventBus->Race (OnFinishReached, OnKillboxEntered, OnRespawn) and
EventBus->Character (OnBounce, OnBounceSound, OnChargeChanged,
OnPostCoyoteRelease, OnPostCoyoteBounce). Each event is declared with
DECLARE_DYNAMIC_MULTICAST_DELEGATE, so subscriptions use AddDynamic and
RemoveAll — compatible with UE5's garbage collector and Blueprint-bindable if needed.
The result is the same decoupled pattern from Unity: BounceDetector fires
OnBounce; BounceForceManager, the camera, and the movement component each
subscribe independently and react without any direct cross-references.
Outcome
All major systems — bounce detection, coyote time, charge state, camera, sound — communicate
exclusively through the event bus with no direct inter-system references.
The domain sub-bus structure also kept subscriptions readable: each component subscribes
only to the sub-bus relevant to it (Race events for respawn/finish handling, Character events
for gameplay feedback), matching how the Unity version organized channels by context.
Technical Challenge: Custom Character Controller in UE5
UE5's Character Movement Component provides robust swept-capsule collision and
built-in movement modes (Walking, Falling, Flying), but those modes run their own velocity
integration and gravity — you cannot cleanly inject custom force logic without fighting the
component's internal state. CMC's jump implementation is an impulse applied to a managed
velocity with built-in apex detection, which produces consistent hops but not a pogo
mechanic where bounce height and angle depend on how hard you charged and what surface you hit.
The challenge was to implement movement that felt physically grounded — with manual gravity,
drag, air control, and surface-normal-aware bounce impulses — while still using CMC's
swept collision infrastructure rather than reimplementing capsule sweeps from scratch.
Solution
I extended CMC using MOVE_Custom mode, which hands full tick control to the subclass
while keeping CMC's collision primitives available. The component manages its own
Velocity vector: gravity accumulates when airborne, drag decelerates when no input
is held, and input force is applied in camera-relative directions each tick.
Collision is resolved using CMC's own SafeMoveUpdatedComponent and
SlideAlongSurface — so swept capsule accuracy is preserved — with a manual step
to strip velocity going into a blocking surface via dot product against the hit normal.
Ground detection is a short downward line trace from the capsule bottom, giving direct
access to the surface normal without CMC's floor data.
Bounce impulses are decomposed into two components relative to the impact normal:
along-normal (scaled by VerticalBounceForce) and along-surface
(scaled by HorizontalBounceForce). The bounce direction blends the impact normal with
the actor's up vector via tunable multipliers, so angled surfaces redirect the player
naturally without any special-case geometry handling.
Outcome
The controller responds to charge input with physically accurate launch arcs —
a partial charge produces a low flat trajectory, a full charge produces a steep high arc —
and slopes redirect the bounce direction naturally.
Using MOVE_Custom instead of bypassing CMC entirely meant collision resolution stayed
robust on complex geometry, and the coyote and charge systems integrated cleanly
against the same manual velocity state rather than fighting CMC's internal floor logic.