3D Platformer
Weeks 23-24 | Camera control, spatial design, and the art of the jump in three dimensions.
"In a 2D platformer, the camera is solved. In a 3D platformer, the camera IS the problem. Everything else is downstream of whether the player can see where they're going."
Prerequisites
| Module | What You Used From It |
|---|---|
| Module 02 - Platformer | Gravity, jump arcs, grounded-state detection, tile collision, game feel |
| Module 11 - First-Person Game | 3D coordinate systems, transforms, 3D collision, basic lighting, working in an engine |
Week 1: History & Design Theory
The Origin
Shigeru Miyamoto's team at Nintendo spent months on Super Mario 64 (Nintendo, 1996) doing something that sounds absurd: just making Mario feel good to move around. Before a single level was designed, before a single enemy was placed, they built a small room and tuned Mario's run, jump, triple-jump, long-jump, wall-jump, backflip, and ground-pound until the act of controlling him was intrinsically fun.
The camera system was revolutionary and openly acknowledged the problem it was solving. Lakitu, a character who had been a cloud-riding enemy in previous Mario games, was recast as a cameraman literally flying behind Mario with a camera on a fishing pole. This was Miyamoto telling the player: "Yes, the camera is a separate thing you manage. Here's a friendly face to make that feel natural."
How the Genre Evolved
Crash Bandicoot (Naughty Dog, 1996) solved the camera problem through a completely different philosophy: constrain it. Rather than giving players a free camera, Crash's camera was mostly fixed — behind the player on rails. By limiting the camera, Naughty Dog ensured the player always had a readable view.
Banjo-Kazooie (Rare, 1998) expanded the collect-a-thon design that Mario 64 had introduced. Multiple move types, dozens of collectible types, and ability unlocks that opened new paths in previously visited worlds demonstrated that 3D platformers could be about thorough exploration of dense spaces.
A Hat in Time (Gears for Breakfast, 2017) and Astro Bot (Team Asobi, 2024) prove the genre is alive and still evolving, pushing the boundaries of what a 3D platformer camera can do.
What Makes 3D Platformers Great
The 3D platformer is a negotiation between two systems in constant tension: character control and camera control. The player needs to execute precise jumps, but they also need to see where they are jumping. These goals frequently conflict. Great 3D platformers resolve this through generous player mechanics (double-jumps, air control, coyote time), smart camera behavior, and level design that is readable from multiple angles.
The Essential Mechanic
Jumping and landing on platforms while managing a camera that must make 3D space readable — the marriage of character control and camera control.
Week 2: Build the MVP
What You're Building
A small 3D level with platforms at varying heights and positions. The focus is on making the jump feel good, the camera feel helpful, and the space feel readable. A tight, well-designed space with 10-15 platforms is more valuable than a sprawling empty world.
Core Concepts (Must Implement)
1. Third-Person Camera System
An orbit camera that follows the player at a fixed distance and can be rotated by the player. The camera must handle collision — if it would clip through a wall, it moves closer to the player.
// Orbit camera each frame:
yaw += inputX * rotateSpeed
pitch += inputY * rotateSpeed
pitch = clamp(pitch, -30, 60)
// Desired camera position: offset behind and above player
offset.x = -sin(yaw) * cos(pitch) * distance
offset.y = sin(pitch) * distance
offset.z = -cos(yaw) * cos(pitch) * distance
desiredPosition = player.position + offset
// Camera collision: raycast from player to desired position
hit = raycast(player.position, direction_to(desiredPosition), distance)
if hit:
camera.position = hit.point + hit.normal * skinWidth
else:
camera.position = desiredPosition
camera.lookAt(player.position + (0, 1, 0)) Why it matters: The third-person camera is arguably the hardest unsolved problem in game design. No automatic system works perfectly in all situations.
Drag on the canvas to orbit the camera around the player cube on the platform. The camera position coordinates update in real-time. The camera always looks at the player.
2. 3D Character Controller with Jump
Extend the Module 11 character controller with a jump system. Gravity pulls the player down each frame. When grounded and the jump button is pressed, apply an upward velocity impulse.
// Gravity:
velocity.y -= gravity * dt
// Ground detection:
groundHit = spherecast(player.position, DOWN, radius=0.3, distance=0.1)
isGrounded = groundHit != null
// Jump:
if isGrounded and jumpPressed:
velocity.y = jumpForce
player.position += velocity * dt Why it matters: The 3D jump is the direct evolution of Module 2's gravity and grounded-state detection. The core physics are identical but ground normals and slope handling add complexity.
3. Camera-Relative Movement
When the player pushes the stick forward, the character must move toward where the camera is facing, not toward a fixed world direction.
// Get camera's ground-plane directions:
camForward = camera.forward
camForward.y = 0
camForward = normalize(camForward)
camRight = camera.right
camRight.y = 0
camRight = normalize(camRight)
moveDir = camForward * stickY + camRight * stickX
if length(moveDir) > 0:
moveDir = normalize(moveDir)
player.rotation = look_rotation(moveDir)
player.position += moveDir * moveSpeed * dt This is THE key UX insight of 3D platformers. Without camera-relative movement, the player would need to mentally translate between "push stick up" and "character moves world-north."
Why it matters: Camera-relative movement is what separates a playable 3D game from a frustrating one. This same pattern applies to every third-person game.
Use arrow keys to move the player. Drag the canvas to orbit the camera. Notice how "Up" always moves the character toward where the camera faces, not world-north. A top-down minimap shows actual world directions vs. camera directions.
4. 3D Spatial Design and Level Layout
Design platforms and spaces that are readable from a dynamic camera angle. Key design principles:
- Silhouette readability — platforms should have distinct shapes visible from multiple angles
- Consistent scale — the player should internalize how far they can jump
- Visual layering — foreground (interactive) geometry should be visually distinct from background
- Sightlines — the player should usually be able to see their next goal
platforms = [
{ position: (0, 0, 0), size: (5, 0.5, 5), color: "green" }, // start
{ position: (4, 1, 3), size: (2, 0.5, 2), color: "green" }, // step up
{ position: (8, 2.5, 1), size: (2, 0.5, 2), color: "green" }, // gap jump
{ position: (8, 4, 6), size: (3, 0.5, 3), color: "gold" }, // goal
] Why it matters: Level design in 3D is harder than in 2D because the player's viewpoint is variable. You must design spaces that communicate effectively from many possible camera angles.
5. Shadow / Blob Under Player
Project a dark circle directly below the player onto whatever surface is underneath. This tells the player exactly where they will land.
// Each frame, cast a ray down from the player:
shadowHit = raycast(player.position, DOWN, maxDistance=50)
if shadowHit:
blobShadow.position = shadowHit.point + shadowHit.normal * 0.01
blobShadow.rotation = align_to_normal(shadowHit.normal)
blobShadow.visible = true
height = player.position.y - shadowHit.point.y
blobShadow.scale = max(0.5, 1.0 - height * 0.05)
else:
blobShadow.visible = false Why it matters: Without a shadow, players cannot tell if they are directly above a platform or five meters to the left. This is a "generous lie" — real shadows are complex and expensive, but a simple blob projected downward solves the gameplay problem. Nearly every 3D platformer uses this technique.
A character jumps back and forth across a gap between two platforms. Toggle the shadow blob on/off with the button to see how it helps judge landing position. Without it, gauging where you will land becomes much harder.
6. Moving Platforms in 3D
Create platforms that move along a path. When the player stands on a moving platform, they must move with it. This requires parent-child transform relationships.
// Moving platform update:
platform.t += speed * dt
platform.position = lerp(pointA, pointB, ping_pong(platform.t))
// When player lands on a moving platform:
if player.groundHit.object == platform:
platformDelta = platform.position - platform.previousPosition
player.position += platformDelta Why it matters: Moving platforms introduce the critical distinction between local space and world space. This parent-child transform relationship is the same pattern used for any hierarchical attachment in 3D.
7. Collectibles in 3D Space
Place items throughout the level to guide the player. Collectibles serve as breadcrumbs — a trail of items leading toward a platform implicitly tells the player "jump here."
// Collectible behavior each frame:
collectible.rotation.y += spinSpeed * dt
dist = distance(player.position, collectible.position)
if dist < pickupRadius:
player.score += collectible.value
play_sound("collect")
spawn_particles(collectible.position)
collectible.active = false Why it matters: Collectibles solve a navigation problem unique to 3D: the player can get lost. They provide implicit wayfinding and secondary objectives.
8. Double-Jump and Air Control
Give the player a second jump in mid-air and allow directional influence while airborne. Neither is physically realistic, but both are essential for making 3D platforming feel responsive and forgiving.
jumpsRemaining = maxJumps // 2 for double-jump
if jumpPressed and jumpsRemaining > 0:
velocity.y = jumpForce
jumpsRemaining -= 1
if isGrounded:
jumpsRemaining = maxJumps
// Air control:
if not isGrounded:
airMoveDir = camForward * stickY + camRight * stickX
velocity.x += airMoveDir.x * airControlStrength * dt
velocity.z += airMoveDir.z * airControlStrength * dt Why it matters: These are the same "generous lies" from Module 2, evolved for 3D. The added dimension makes precise jumping harder, so the game compensates by giving the player more tools.
Stretch Goals
- Wall-jump — Detect when the player is sliding against a wall and allow a jump off of it.
- Checkpoints and respawn — Respawn at the last checkpoint when the player falls into a void.
- Camera auto-adjustment — Gently rotate the camera to show upcoming challenges.
- Character animation states — Idle, run, jump, fall, land animations driven by a state machine.
MVP Spec
| Feature | Required |
|---|---|
| Third-person orbit camera with collision | Yes |
| 3D character with gravity and jump | Yes |
| Camera-relative movement | Yes |
| At least 10 platforms at varying heights | Yes |
| Blob shadow under the player | Yes |
| At least one moving platform | Yes |
| Collectibles that guide the player | Yes |
| Double-jump or air control | Yes |
| Wall-jump | Stretch |
| Checkpoints / respawn on fall | Stretch |
| Camera auto-adjustment | Stretch |
| Character animation state machine | Stretch |
Deliverable
A playable 3D platformer level with camera control, jumping, and collectibles. Write-up: What did you learn? How does designing for 3D space differ from 2D? What was your biggest camera challenge?
Analogies by Background
These analogies map 3D platformer concepts to patterns you already know. Find your background below.
For Backend Developers
| Concept | Analogy |
|---|---|
| Third-Person Camera System | Like a load balancer health check that tracks the player but must avoid collisions, adjusting distance dynamically |
| 3D Character Controller with Jump | Like a state machine managing a connection lifecycle (IDLE, ACTIVE, CLOSING) |
| Camera-Relative Movement | Like resolving relative paths — ./forward is interpreted relative to the current working directory (camera orientation) |
| 3D Spatial Design / Level Layout | Like API design — the level must communicate its structure to the player through intuitive patterns |
| Shadow / Blob Under Player | Like a lightweight status probe — a constant downward raycast giving real-time position feedback |
| Moving Platforms in 3D | Like container orchestration parent-child — the player inherits the platform's position |
| Collectibles in 3D Space | Like breadcrumb logging or distributed tracing — guiding through a complex path |
| Double-Jump / Air Control | Like retry policies — a second attempt and course correction rather than failing hard |
For Frontend Developers
| Concept | Analogy |
|---|---|
| Third-Person Camera System | Like a scroll-follow with intersection observers that detects overlaps and adjusts position |
| 3D Character Controller with Jump | Like a CSS transition with cubic-bezier easing — the jump arc is a curve |
| Camera-Relative Movement | Like resolving CSS transform-origin — movement is relative to the camera's coordinate system |
| 3D Spatial Design / Level Layout | Like responsive design — the level must be readable at different camera angles |
| Shadow / Blob Under Player | Like a tooltip or cursor follower projected onto a surface |
| Moving Platforms in 3D | Like a child element inside a CSS-transformed parent — position: relative and absolute |
| Collectibles in 3D Space | Like visual affordances in UI — spinning, glowing items say "interact with me" |
| Double-Jump / Air Control | Like undo/redo in a text editor — a second chance after committing to an action |
For Data / ML Engineers
| Concept | Analogy |
|---|---|
| Third-Person Camera System | Like a constrained optimization problem — minimizing distance to a target subject to constraints |
| 3D Character Controller with Jump | Like numerical integration of a differential equation — Euler method with impulse |
| Camera-Relative Movement | Like a change of basis — input in camera basis vectors transformed to world coordinates |
| 3D Spatial Design / Level Layout | Like feature engineering for interpretability — reducing the dimensionality of navigation |
| Shadow / Blob Under Player | Like a projection onto a lower-dimensional subspace |
| Moving Platforms in 3D | Like reference frame transformations in physics simulation |
| Collectibles in 3D Space | Like reward shaping in reinforcement learning — guiding toward the goal |
| Double-Jump / Air Control | Like regularization — relaxing constraints to get better generalization (playability) |
Discussion Questions
- Super Mario 64 and Crash Bandicoot launched the same year with opposite approaches to the camera: free control vs. constrained rails. What are the tradeoffs?
- The blob shadow is a "fake." Why is this acceptable? What other "lies" does your game tell the player?
- Compare your 2D platformer from Module 2 to this 3D platformer. Which concepts transferred directly, and which required fundamentally different solutions?
- Camera-relative movement means "forward" changes meaning whenever the camera rotates. What happens if the camera rotates while the player is holding forward?