Guide 15 — Apep's Trial
Lazaro Garcia
What you're building
Ra's barque is delayed for construction and the world ends if nobody makes his nightly journey. You're a magic construct standing in for Ra, travelling to stop Apep and bring light to the new day.
The core mechanic: the camera switches from first-person 3D to a 2D side-scroller or top-down view, and the controls change with it. You have dash, air dash, double jump, glide, sprint.
Yours is the only hybrid build in the class, and the most conceptually ambitious. It's also the one with the clearest single piece of advice:
Prototype the switch on grey boxes before you make any art or level.
The mechanic either feels good or it doesn't, and you want to know that in week two rather than week ten. Everything else in your game depends on it.
Time: two hours for a working prototype. Do that first.
1. Before you start
- Finish Guide 0.
- Read Guide B §B1 (2D player) and §B3 (camera).
- One new input action:
Dash. - Do not skip section 3.
2. Scripts you'll use
Mode switching (Tier 8)
CameraModeSwitcher · ModeSwitchTrigger
Controllers
PlayerController2D (2D side) · TopDownMover (top-down) · FirstPersonController (3D)
Movement (Tier 3)
Dash · DoubleJump · Glide · Sprint
Core
Health · DamageOnContact · Checkpoint · RespawnManager · SceneLoader · GameManager · AudioManager · HUDController · MovingPlatform
Puzzles (Tier 6)
PushBlock · PuzzleSwitch (Tier 4) · Door
3. Build the prototype first
Before art, before level design, before the barque. One scene, grey boxes, twenty minutes of testing.
The player object — this will look alarming
Your player carries both a CharacterController and a Rigidbody2D, plus all three movement scripts. Only one is ever enabled at a time, so they never fight — but the Inspector looks wrong. It isn't.
- GameObject → Create Empty, name it
Player. Tag →Player. - Add a Capsule as a child (GameObject → 3D Object → Capsule) for the 3D body, and a Sprite Renderer child for the 2D body. You'll show and hide these later; for the prototype, just the capsule is fine.
- Add to the root:
| Component | Settings |
|---|---|
| CharacterController | Height 1.8 · Radius 0.3 · Center Y 0.9 |
| Rigidbody2D | Gravity Scale 3 · Freeze Rotation Z ✓ |
| Capsule Collider 2D | fit the sprite |
| Player Input | Behavior: Send Messages |
| FirstPersonController | Walk Speed 3.5 · Sprint 6 |
| PlayerController2D | Move Speed 8 · Jump Force 14 |
| TopDownMover | Move Speed 5 |
| Health | Max Health 3 |
- Child
CameraHolderat Y 1.6 — drag it intoFirstPersonController's Camera Pivot. - Child
GroundCheckat the feet — drag intoPlayerController2D. - Disable
PlayerController2DandTopDownMoverin the Inspector (untick their checkboxes).CameraModeSwitchermanages them from here.
The three cameras
- GameObject → Cinemachine → Cinemachine Camera ×3. Name them
Cam3D,CamSide,CamTopDown. Cam3D— put it insideCameraHolder, position (0,0,0), rotation zero. Set its Follow and Look At empty.CamSide— add a Position Composer, set Follow toPlayer, and position it looking down −Z with the settings from Guide B §B3.CamTopDown— add a Position Composer, Follow =Player, rotate it to look straight down (rotation X = 90).- Make sure Main Camera has a
CinemachineBrain. Set Default Blend → Ease In Out, 0.6s.
The switcher
On Player, add CameraModeSwitcher:
| Field | Drag in |
|---|---|
| Camera 3D / Camera Side / Camera Top Down | the three cameras |
| Active Priority | 20 |
| Inactive Priority | 5 |
| Controller 3D | the FirstPersonController |
| Controller Side | the PlayerController2D |
| Controller Top Down | the TopDownMover |
| Character Controller | the CharacterController |
| Body 2D | the Rigidbody2D |
| Starting Mode | FirstPerson3D |
| Blend Time | 0.6 — match your Cinemachine default blend |
| Lock Input During Blend | ✓ |
| Manage Bodies | ✓ |
| Switch Sfx | mode_switch |
Test it with a button
Add a temporary UI Button wired to CameraModeSwitcher.SetModeByIndex with the parameter 1 (0 = 3D, 1 = side, 2 = top-down). Add two more for 0 and 2.
Press Play and click between them. You should see the camera blend, and the controls change with it. If that works and feels good, you have a game. If it feels bad, now is the cheapest possible moment to find out.
4. Doorway triggers
Once the button test works, replace it with real transitions.
- Empty GameObject
Doorwaywith a 3D BoxCollider, Is Trigger ✓, at the threshold. - Add ModeSwitchTrigger:
| Field | Value |
|---|---|
| Switcher | leave empty — it finds it |
| Mode On Enter | SideScroll2D |
| Restore On Exit | ✓ for a two-way doorway |
| Mode On Exit | FirstPerson3D |
| Reposition Player To | see below |
| Snap Z Only | ✓ |
The reposition point — do not skip this
Create an empty GameObject sitting exactly on the plane your 2D section runs along, and drag it into Reposition Player To. With Snap Z Only ticked, entering the doorway snaps the player onto that Z plane while leaving X and Y alone.
This is the single most common bug in a 3D/2D hybrid. Without it, the player enters 2D mode standing slightly off the plane, walks "into the background," and appears to phase through the level. It looks like a physics bug and it isn't.
5. Build the 2D sections
Once switching works, the 2D parts are ordinary platforming.
- Ground on Layer →
Groundwith 2D colliders, all on the same Z plane KillZoneunder the sectionCheckpoints- Camera bounds via
CinemachineConfiner2DonCamSide
Add the movement abilities
On Player, add these — they only apply while PlayerController2D is the active controller:
| Component | Settings |
|---|---|
| Dash | Speed 22 · Duration 0.15 · Cooldown 0.4 · Air Dashes 1 · Invulnerable ✓ |
| DoubleJump | Extra Jumps 1 · Air Jump Force 12 |
| Glide | Glide Fall Speed 2 · Min Fall Speed To Start 1 · Glide Move Multiplier 1.1 |
| Sprint | Sprint Multiplier 1.6 · Toggle Mode ✗ |
All four are in your pitch. They coexist without configuration — the Jump button is shared and they negotiate who acts.
Add the Dash input action to your input asset (Guide 0 §6).
6. Build the top-down sections
Top-down works the same way — same object, different active controller.
- Ground and walls with 2D colliders
TopDownMoversettings: Move Speed 5, Facing → FourDirection or FlipSprite- A
ModeSwitchTriggerset toTopDown2Dat the entrance
CameraModeSwitcher sets Rigidbody2D.gravityScale to 0 in top-down mode and 3 in side-scroll mode automatically — you don't manage that.
Puzzles suit top-down
Your pitch mentions the barque journey. Top-down sections are a natural fit for PushBlock and PuzzleSwitch puzzles — moving weights onto pressure plates reads much better from above than from the side.
PushBlock: sprite + Rigidbody2D + solid collider + the component (Mode Physics). Wire RespawnManager.OnRespawned → PushBlock.ResetPosition so a death can't make a puzzle unsolvable.
7. Show the right body
Your player has a 3D capsule and a 2D sprite. Show one at a time.
Wire CameraModeSwitcher.OnModeChanged… except it sends a Mode enum, which doesn't connect directly to SetActive.
The no-script version: use OnSwitchStarted and OnSwitchFinished plus separate ModeSwitchTrigger.OnEntered events on each doorway to toggle the two body objects. Slightly manual but it works.
The better version: ask your instructor for a three-line script that listens to OnModeChanged and enables the right body. It's genuinely three lines, and you'll want it.
For the prototype, just leave the capsule visible in all modes. It looks odd and it doesn't matter yet.
8. Managers and HUD
Guide B §B2 — GameManager, RespawnManager, AudioManager, SceneLoader.
HUD: a Canvas with hearts or a health bar, plus an empty HUD with HUDController.
Cursor handling: FirstPersonController locks the mouse in 3D mode and CameraModeSwitcher unlocks it when you leave. Escape releases it — useful in the editor. If you add a pause menu, wire it to FirstPersonController.UnlockCursor.
9. Wire it up
| On this object | Event | Drag in | Choose |
|---|---|---|---|
Player → Health | On Died | RespawnManager | RespawnManager.OnPlayerDied |
Player → Health | On Health Changed | HUD | HUDController.UpdateHealth (Dynamic) |
RespawnManager | On Respawned | PushBlock | PushBlock.ResetPosition |
CameraModeSwitcher | On Switch Started | AudioManager | PlaySFX (string) → mode_switch |
PuzzleSwitch | On Activated | Door | Door.Open |
Player → Dash | On Dash Started | AudioManager | PlaySFX (string) → dash |
10. Prototype playtest
Do this before building any level.
- All three modes are reachable via your test buttons.
- The camera blends smoothly rather than snapping.
- Controls change with the mode — WASD+mouse in 3D, left/right+jump in side, 8-way in top-down.
- You can't move mid-blend (Lock Input During Blend ✓).
- A doorway trigger switches mode and repositions you onto the plane.
- You don't drift off the 2D plane — walk back and forth in side-scroll mode and confirm you stay put.
- Gravity is right in each mode — you fall in side-scroll, you don't in top-down.
- Dash, double jump and glide all work in side-scroll and do nothing in 3D.
Step 6 is the one that kills hybrid games. Test it deliberately: switch to 2D, run back and forth for ten seconds, switch to 3D, look at where you are. If you've drifted, your reposition point isn't set.
11. Then add
- One complete area — a 3D room, a doorway, a 2D section, a doorway back.
- Art. Only now. Your Egyptian setting is strong and the 3D sections will carry most of it.
- Animation for the 2D body — Guide B §B6, plus
Dash,DoubleJump,IsGliding. - The barque as a hub — a 3D space you return to between trials.
- Apep as a boss (Guide 7 §9), probably in a 2D section where the camera can frame him.
- More areas, each mixing modes differently.
12. Design notes for your game specifically
Prototype first. I've said it three times because it's the whole guide. Everything else here is standard platforming and standard first-person walking. The only genuinely novel thing in your project is whether the switch feels good, and that's answerable in two hours with grey boxes.
The switch should mean something. A perspective change that's purely decorative gets old fast. The interesting version is when the same space reads differently in two views — a gap that looks impassable in 3D turns out to be a simple jump in 2D, or a path hidden behind a pillar in first-person is obvious from above. That's what Fez and Shady Part of Me do, and it's why you listed them.
Fewer, better transitions. Two well-designed switches per area beats ten. Each one costs the player a moment of disorientation, and that budget is finite.
Reposition points are not optional. Every single ModeSwitchTrigger that enters a 2D mode needs one. Make it a habit: place the trigger, place the point, drag it in. Skipping one gives you a bug that appears twenty minutes into playtesting and takes an hour to diagnose.
The 3D sections don't need to be complicated. First-person walking through an Egyptian barque, looking at things, finding a doorway — that's enough. Your budget should go into the 2D sections where the movement abilities live.
Your theme is doing a lot of work. Ra's nightly journey through the underworld is already a sequence of distinct realms, which is a perfect excuse for perspective changes. Lean on that: each mode switch can be a passage into a different part of the night.
13. Common problems
| What you see | What's wrong |
|---|---|
| Camera snaps instead of blending | No CinemachineBrain on Main Camera, or Default Blend is 0 |
| Nothing happens on switch | Controllers not assigned in CameraModeSwitcher |
| Two controllers fight | Both enabled — the switcher should manage this; check Manage Bodies ✓ |
| Player floats in side-scroll | Rigidbody2D.simulated off, or Manage Bodies unticked |
| Player falls in top-down | Gravity not zeroed — Manage Bodies ✓ handles it |
| Player drifts off the 2D plane | Reposition Player To not assigned on the trigger |
| Player phases through the level in 2D | Same cause — wrong Z |
| Can move during the blend | Lock Input During Blend unticked |
| Mouse stays locked in 2D | Controller 3D not assigned, so the switcher can't unlock it |
| Falls through the floor in 3D | CharacterController disabled, or the floor has no 3D collider |
| Falls through the floor in 2D | Ground has no 2D collider, or it's on the wrong layer |
| Dash works in 3D | Expected — it does nothing there because PlayerController2D is disabled. If it moves you, check the module isn't reading a stale controller |