In this tutorial, you’ll learn how to create a simple click-to-move game like Monument Valley, an exploration game where you navigate a series of seemingly impossible platforms. Like an M.C. Escher-inspired maze, optical illusions form each level. Decipher its physics-defying architecture and create something like it yourself. Level up with the following achievements along the way:
- Penrose Triangle creation in Unity.
- Scene Camera settings to work in an isometric view with Penrose Triangles.
- How to piece together a level architecture using Penrose Triangles and other parts.
By following through this tutorial, you’ll learn how the famous Monument Valley game mechanics and camera views work. On top of that, you will have added a few really cool techniques to your Unity toolbelt :]
Click the Download Materials button at the top or bottom to download the project files. Unzip and open MonumentValleyStart using Unity 2020.1 or later.
The RW folder contains everything you need to get started:
There are also additional subfolders for extra project organization, such as for Level and Interface items within Prefabs. Both the Project window and the Hierarchy have a search feature, in case anything’s hard to find.
This project uses the Universal Render Pipeline. In Package Manager, confirm you have the latest Universal RP package installed.
Select Edit ▸ Project Settings ▸ Graphics. Make sure that UniversalRP-HighQuality is the current Pipeline Asset.
Load TutorialLevel from Scenes. This includes a Main Camera, a Backdrop for the environment and several lights. PostProcessing contains a Volume with basic image effects.
As you add objects to the Hierarchy, position them between these spacers to stay organized.
Rather than coding from scratch, you’ll construct your app from prefabs. Experienced users can customize and extend the provided scripts.
Setting up the Game View
In keeping with the original game’s mobile platform origins, you’ll set the Game view’s aspect ratio to 9:16.
To do this, ensure you are in the Game window view. Click the + icon in the Free Aspect drop down. Select Aspect Ratio for the Type and label it HDPortrait. Enter 9 and 16 for the Width and Height. Finally, click OK.
Select your newly-created HDPortrait for the Game view aspect ratio.
Now that you have that out of the way, it’s time to start constructing your impossible platforms.
Discovering Penrose Triangles
The impossible shape that you’ll build for your platform is based on a Penrose Triangle, a triangular object that relies on perspective and can’t exist in real space. You’ll use a similar idea to make a platform that your player will try to navigate — but that won’t be as easy as it seems.
The scene currently shows a simple set of cubes grouped under PenroseTriangle.
The Main Camera has the following Transform values:
- Position: (X:22, Y:24, Z:-18)
- Rotation: (X:35.264, Y:-45, Z:0)
You can adjust the translation as needed, but be sure to use the exact values for the rotations! Monument Valley’s perspective puzzles only work with specific camera angles.
Spin the PenroseTriangle group on the Y-axis. When you reach 0 or 360 degrees, the blocks almost appear to connect with each other in the form of a triangle.
Next, you’re going to enhance the illusion so that the beams form a triangle.
Adjusting Camera and Lighting
An isometric camera makes the illusion look better.
Change the Projection of the Main Camera to Orthographic.
Removing any unnecessary lighting cues improves the illusion. In the Light components of KeyLight and FillLight, set the Shadow Type to No Shadows.
You have one more step to take to complete your optical illusion.
The Vertical beam appears to connect with the HorizontalX blocks. However, a small amount of shading near the top reveals an imperfection. You’ll use some helper geometry to correct this.
Drag the LJoint prefab into the Hierarchy. This is simply two polygons representing a “half cube”.
Position the LJoint at (X:8, Y:9, Z:1), next to the vertical blocks. Rotate it to (X:90, Y:-90, Z:0). With this view, you can no longer see the gap, giving you the illusion of a smooth but twisty triangle.
Voila! You’ve created a Penrose triangle, a classic impossible object that forms the basis for Monument Valley’s optical illusions.
Swedish artist Oscar Reutersvärd first discovered the Penrose triangle in 1934. Father and son, Lionel and Roger Penrose, later popularized the design.
Your next goal is to enable movement along these beams so that a character seems to move in an impossible fashion.
Shifting Screen Z
Orthogonal cameras don’t show perspective. Moving any object along the camera’s local Z-axis does not affect the object’s screen space position.
Confirm this for yourself by translating the LJoint to (X:10.5, Y:11.5, Z:-1.5).
Through the Game view, nothing appears to have happened. However, the mesh has clearly shifted positions in the Scene view.
You’ll use this phenomenon to make movement challenging for your player.
Adding Level Geometry
Your goal is to connect smaller platforms to imitate the Penrose triangle effect. Eventually, you’ll guide a character to reach a goal platform at the top.
Now that you’ve seen how the Penrose triangle works, you don’t need your model of it anymore. So select the LJoint and the PenroseTriangle, then right-click and select Delete to remove them from the Hierarchy.
Next, you’ll build a course for your character to follow.
You’ll start with two more prefabs. Drag the Bridge and SpinnerControl into the Hierarchy. Bridge should have a Position of (X: 6, Y: 7, Z:0) and SpinnerControl should have a position of (X: 7.85, Y: 7, Z:0).
The SpinnerControl contains custom Drag Spinner and Highlighter components.
You want to use the Drag Spinner to change the position of the bridge, so set its Target To Spin to the Bridge.
Next, set the Highlighter’s Mesh Renderers Size property to 1.
Locate the SpinnerMesh under the Bridge. Assign this to the empty field in Element 0.
Finally, you need a few more platforms for the character to move along. So import the HorizontalPlatform, VerticalPlatform and StairsPlatform prefabs. Set their rotations and translations to zero, if necessary.
For your next step, you’ll give the character a way to move toward its goal.
Bridging the Gap
Open the Scene view. Note the large physical gap between the HorizontalPlatform and the Bridge. That’s what you’ll be working with in this section.
Enter Play mode in the Editor, then hover your mouse over the SpinnerControl. The Highlighter component causes the SpinnerMesh to glow red. Clicking and dragging snaps the Bridge to 90-degree intervals.
Watch when the Bridge‘s X rotation reaches 90 degrees. Notice how Box10 appears to connect with Box15 in the Game view. In the Scene view, however, the gap remains.
You want the two platforms to appear seamless. But right now, when you look through the Main Camera you can see a shading error where the two platforms join.
A small piece of geometry can conceal this region to repair the glitch, similar to the way you fixed the Penrose triangle in an earlier section.
Drag a new LJoint prefab into the Hierarchy. Set it’s Position to (X:7, Y:8, Z:-5) with a Rotation of (X:90, Y:-90, Z:0). To stay organized, rename it to LJoint10 to match Box10.
This conceals the break between Box10 and Box15.
Adjust the SpinnerControl in Play mode. The connection now appears seamless.
Now, it’s time to introduce the character.
Using Layers With the Camera Stack
Adding a playable character on top of the level adds an unfortunate complication — the character doesn’t display properly because the draw order is incorrect.
Check this out by dragging the Player from Prefabs/Player into the Hierarchy. Position it at (X:-1, Y:0.5, Z:3).
Now, you can see that LJoint10 incorrectly obscures the Player‘s MeshRenderer.
The holdout geometry needs to appear in front of the level. However, it also needs to render behind the Player. Tricky!
To solve this problem, you’ll use multiple cameras to adjust the draw order of meshes onscreen. Exit Play mode to get started.
The project already has several custom layers, which you’ll find in Layers ▸ Edit Layers…Tags and Layers. Select Layers ▸ Edit Layers… to confirm.
Next, check that User Layers 8 to 10 are labeled Background, Level and Player.
Unity can use a camera stack to render multiple camera passes. This allows you to control the order of everything drawn onscreen.
To use this feature, start by cloning the MainCamera twice using Edit ▸ Duplicate.
Rename the clones to LevelCamera and PlayerCamera. Remove their AudioListener components.
Set the LevelCamera and the PlayerCamera to use RenderType: Overlay. The MainCamera should continue to use RenderType: Base.
Select the MainCamera and modify its CullingMask in the Rendering options. First, choose Everything for Culling Mask. Then, uncheck Level and Player. This camera will not draw those layers.
In LevelCamera’s CullingMask, select Nothing and then select the Level layer. This camera will only render the Level layer.
Finally, in the PlayerCamera, select Nothing in the CullingMask and select the Player layer. This camera will only draw the game’s foreground elements.
Assign the Player to use Layer 10: Player. Select Yes, Change Children when prompted.
The PlayerCamera view will now look like this:
The LevelCamera view will now look like this:
Bridge, HorizontalPlatform, VerticalPlatform, StairsPlatform, and LJoint10 should use Layer 9: Level. Assign those and, again, choose Yes, Change Children for each.
Confirm that you’ve already assigned the Backdrop to Layer 8: Background.
With those actions complete, it’s time to move on to creating the camera stack itself.
Creating the Camera Stack
Camera stacking is a common technique to composite rendered elements at runtime. You’ll use it here to add challenge to your game.
Select the LevelCamera or PlayerCamera. A thumbnail preview of their respective layers appears in the Scene view. However, only the Backdrop remains visible in the Game view. This is because you have not yet composited the cameras to form a stack yet.
Next, you’ll create a camera stack so all the cameras render together. Select MainCamera and, at the bottom of the Inspector, locate the Stack.
Click the + button to add the LevelCamera and PlayerCamera, in that order. Cameras at the bottom of the stack draw last.
Everything now appears in the Game view in the correct rendering order.
Enter Play mode to confirm the SpinnerControl still works. The Bridge forms a partial Penrose triangle with the other platforms.
Now, you need to connect the various platforms so the Player can cross the level.
Adding a Graph, Nodes and Edges
The level consists of several blocks and ramps to reach the final Goal at the top of StairsPlatform.
Unity’s built-in navigation system does not adapt easily to this game’s tile-based design. Instead, the project includes a custom pathfinding solution. This allows your character to move in discrete steps along the polygon faces.
Before you start building, take a moment to familiarize yourself with some terminology from graph theory:
- A waypoint, or node, forms one unit of the path. It describes a three-dimensional location.
- A node connects to its neighboring nodes with structures called edges.
- The network of nodes forms a graph that describes the possible pathways through the maze.
Later in this tutorial, you’ll build a click-to-move controller to navigate this graph.
Exploring Nodes and Edges
If you’re already familiar with these concepts, open Node.cs and Edge.cs and familiarize yourself with those classes. Otherwise, here’s some background.
Nodes are simply three-dimensional points tracked by their transform.position. This implementation includes some options for visualization, like Gizmo size and color.
Edge class allows a node to connect to its neighboring nodes. You can toggle each edge on and off, which will be useful later.
You won’t use some of the fields and methods, like
EnableEdge, until you dive deeper into the project. Set those aside for now, but don’t forget about them!
In the Hierarchy, each box that comprises HorizontalPlatform has a child transform with a Node component.
Under the Box6 transform, for example, select the Node6 child GameObject. Note that the numerical suffixes match the Node with the corresponding Box tile.
Adding the Graph
Nodes need a graph to make them aware of each other. You’ll add one now.
Create an empty GameObject in the Hierarchy called Graph and add the Graph component.
Make sure you activate Gizmos for the Scene view.
You can set up Edges manually for each Node in the Inspector. To save time, horizontally-adjacent Nodes also detect each other automatically at runtime.
Select Node6. The Edges field currently shows a Size of 0.
Enter Play mode. Notice that it now has two active Edges that connect to Node5 and Node7.
Select other Nodes under HorizontalPlatform. Each Node is represented by a Gizmo sphere with lines drawn to its neighbors.
Connecting Edges Manually
You must set up vertical and diagonal nodes manually. To do this, exit Play mode and select NodeStairRamp1 in the StairsPlatform.
Set the Edges’ Size to 2. Add Node25 as the Neighbor of the first Edge and check isActive.
Add NodeStairRamp2 as the Neighbor to the second Edge and, again, check isActive.
Enter Play mode. Select several Nodes from the StairsPlatform, Bridge and HorizontalPlatform. Note how the Gizmos represent their Edges to neighboring Nodes.
To save time, the other vertical or diagonal Edges for the level platforms are already set up. Check the Gizmos to verify that.
Setting the Goal Node
Graph class manages the network of Nodes. Open Graph.cs to examine its functionality.
Graph references a list of all the Nodes, as well as a public field for the NodeGoal. It also contains several useful methods:
FindNodeAt: Returns a single Node located exactly at a specific 3-D point in space.
FindClosestNode: Locates the nearest Node in screen space to a given 3-D coordinate.
ResetNodes: Clears the
PreviousNodefields to start a new graph search.
Locate NodeGoal under the StairsPlatform and drag it into the Graph’s goalNode field.
Your Graph now has an end goal assigned for this level.
You can create a path through the level with a graph search.
Add the Pathfinder component to the Graph GameObject.
The included pathfinding algorithm uses a breadth-first search, one of the simplest graph searches.
Here, you designate one Node as your starting point and another as your destination.
The algorithm works iteratively, trying to connect the two. On each step, the search expands outward and increases by one set of neighbors.
Every explored node records a breadcrumb trail back to the starting point.
This outward search, or expansion, then repeats.
With each iteration, you explore more and more of the graph. The search ends when it either finds the destinationNode or is exhausted.
If you located the destinationNode, the Pathfinder creates a backward path to the starting Node.
Next, test the Pathfinder yourself. In the Inspector, set the Start Node to Node5 and the Destination Node to Node10.
Finally, enable Search On Start.
Enter Play mode and you’ll notice that the Scene view shows a line of Gizmo icons drawn like this. Success! You’ve drawn a temporary path between Node5 and Node10.
Uncheck Search On Start and clear the Start Node and Destination Node fields. In the next step, you’ll develop a better system for setting up the pathNodes.
The primary game mechanic uses a click-to-move controller. Each 3-D cube in the HorizontalPlatform, Bridge and StairsPlatform has a Box Collider and a Clickable component attached.
The player can’t move across the surfaces of the VerticalPlatform, so those boxes don’t have Clickable components.
Clickable.cs contains some simple logic to process mouse clicks:
OnPointerDowninvokes whenever the user mouse clicks over a Collider.
- Nodes: Nodes that are parented to the current Transform are kept in a list. The Pathfinder will search these possible childNodes to form the best path.
clickAction: The player will listen to a System.Action called
clickAction, which is stored in each Clickable component. This allows you to invoke custom behavior when you reach a certain part of the level.
Now, you have everything ready to be able to start moving your player around the surfaces.
Controlling the Player
Your player’s current position is the starting point for pathfinding. Start by moving the player to the middle of the HorizontalPlatform at (X:2, Y:0.5, Z:0).
Add the PlayerController and PlayerAnimation components to the Player.
When you’re in Play mode and you click anywhere on the HorizontalPlatform, your character now moves to the corresponding Node. Nice job!
With gizmos turned on for the viewport, you can see the path drawn between the player’s starting position and the clicked destination.
PlayerController has custom behavior to move your player:
PlayerControllerlistens for the
Clickable. When the user clicks a valid Collider, the
OnClick, in turn, calls Pathfinder’s
FindBestPathmethod for all child nodes under
- If it finds a valid path,
MoveToNodeRoutinelerps the character’s position to the
MoveTime. Some nodes may be farther apart than they appear in the Game view. Movement based on time rather than distance keeps the motion more uniform.
FaceNextPositionupdates the character’s rotation to align with the path as it walks.
Animating the Player
PlayerAnimation component has a simple Animator to transition between two
Drag Player’s child object, SamuraiAnimator, into the Animator field.
Enter Play mode. Click to move your character and you’ll see the Animator switches between animations so the character appears to be walking or standing still.
You can view the simple
PlayerAnimController in RW/Animation by selecting Window ▸ Animation ▸ Animator.
This has two animation states, with transitions triggered by the
Do you think the player is moving too quickly or too slowly? Tweak the corresponding Walk Anim Speed slider in
PlayerAnimation to your liking. The Walking animation clip should match the player’s overall motion. Any value between 0.7 and 1.3 should work.
Holding out Geometry
Holding out geometry in this tutorial is essentially the process of masking it off so that it is hidden from the Camera’s view.
Try walking around the HorizontalPlatform. Move to Node7 near the VerticalPlatform and you’ll see your Player draws incorrectly.
To prevent this, you’ll add holdout geometry.
Exit Play mode. Drag an LJoint prefab into the Hierarchy. Rename it: LJoint12.
Adjust its Transform properties:
- Rotation: (X:0, Y:-90, Z:-90)
- Position: (X:6, Y:1.5, Z:0)
- Scale: (X:3, Y:1, Z:1)
With these settings, it should approximately cover Box12. Switch its Layer to Player.
Now, test Play mode again. When the Player approaches the right side of the platform, the foreground LJoint12 now holds out the Player properly.
Now that your character displays correctly, you’ll work on making it easier to navigate the game.
Visualizing the Clicks
Right now, users have no indication of exactly where they’re clicking when they move their character. For your next step, you’ll add visual feedback to highlight the mouse clicks.
Drag the Cursor prefab into the Hierarchy. This is a simple quad mesh with a texture. The Cursor script will automatically rotate the GameObject in 3-D space and aim it at the camera.
The Offset Distance pulls the polygon toward the camera to prevent intersecting the level geometry. The default of 5 units should work.
Select the Player. In the PlayerController, drag the Cursor object into the empty Cursor field.
At runtime, an animated icon appears over your mouse clicks. Cool!
Building Impossible Paths
The “magic” in Monument Valley happens because the character can walk over impossible surfaces. But currently, your player can only move along the nodes of the HorizontalPlatform. Your next step is to build some impossible paths for the character to follow.
Connect the Bridge with the HorizontalPlatform by creating an Edge between Node10 and Node15.
Select Node10. Set the Size of the Edges to 1. Create an Edge with Node15 as the Neighbor. Select isActive.
Now, select Node15, which is already connected to Node16 with an edge. Add an additional Edge to Node10.
Likewise, connect Node21 from the Bridge to Node22 under the StairsPlatform. Repeat the process with Node22.
Make sure to fill out the Neighbor and isActive fields correctly. Otherwise, the Pathfinder will not connect the Nodes.
Enter Play mode to test your handiwork. Use the SpinnerControl to rotate the Bridge to 90 degrees in X.
Click on the Bridge or StairsPlatform and your player will now walk to your selected collider.
Keep the Scene view open as you click in the Game view and see what happens when you walk back and forth across the HorizontalPlatform and Bridge.
Notice how the character appears to transition seamlessly in the Game view. In the Scene view, however, it shoots across the gap from Box10 to Box15.
This is the benefit of using orthogonal cameras. Any movement along the local Z-axis of the camera is invisible in the Game view!
Linking the Bridge Rotation
The Pathfinder currently works regardless of the bridge’s rotation. To make the puzzle a bit trickier, you’ll change this so the player needs to move the spinner to the proper position to cross the bridge.
In Play mode, keep the bridge in its default orientation and click somewhere on the StairsPlatform. The player character “wall walks” and makes its way past the Bridge. Yikes!
You need to be able to disable certain edges under specific conditions. In this case, if the bridge is at the wrong angle, you need to disconnect edges between the platforms. You’ll use another component to do this.
Select the SpinnerControl and add a Linker component. Set the size of the Rotation Links to 2.
In the first element, set the Linked Transform to Bridge and the Active Euler Angle to (X:90, Y:0, Z:0).
Enter Node10 and Node15 for Node A and Node B (or vice versa).
In the second element, use the same values for the Linked Transform and Active Euler Angle. Set Node A and Node B to Node21 and Node22.
Linker does not update automatically, so add a SnapEvent in the DragSpinner by using the + icon.
Fill in the empty field with the SpinnerControl and select Linker ▸ UpdateRotationLinks in the Action drop-down.
In Play mode, select one of those four nodes with the Game and Scene views active. Rotate the SpinnerControl.
The gizmo line switches to its active color when the X rotation is 90 degrees. Likewise, it deactivates when the X rotation is anything else.
Try to cross from the HorizontalPlatform to the Bridge when the links are inactive. The cursor appears, but the player doesn’t move. So you’ve successfully blocked the player from moving across the bridge when it’s in the wrong position.
Adding Node Events
Right now, the SpinnerControl is still active when the player crosses the bridge. That means that if you spin the bridge while the player is walking over it, you get a weird movement like this:
To fix this, you’ll use a node’s GameEvent to set up custom behavior. You want to disable the SpinnerControl when the player reaches Node15 or Node22. This prevents the bridge from rotating while the player is walking across it.
Select Node15. Click the + icon twice in the GameEvent to create two new actions.
Drag the SpinnerControl into the empty field for both actions.
In the first action, select DragSpinner ▸ EnableSpinner. Make sure you’ve unchecked the checkbox for false.
In the second action, select Highlighter ▸ EnableHighlight. Again, be sure you’ve unchecked the checkbox for false.
Repeat these steps for Node21. When the player walks across the bridge, the SpinnerControl will now be inactive, avoiding any weird animation.
But don’t forget, you want to be able to move the bridge again once the character has finished crossing it. So mirror these steps for Node10 and Node22 to re-enable the SpinnerControl once the character has left the bridge. This time, select true for each checkbox.
Now, when the character walks to the bridge, the SpinnerControl disables itself. Walking off the bridge re-enables the Highlighter and Drag Spinner components.
Completing the Game Manager
With the platforms all connected, the player can now walk to the Goal at the top of the stairs. However, you’re not quite done yet. You still need a manager script to check for the win condition. When you add this, you’ll also provide a basic user interface to start and end the level.
Drag the GameManager prefab into the Hierarchy. Nested under the GameManager is a ScreenFader GameObject. This contains a component of the same name that fades in to provide a better starting transition when the game starts.
Update checks each frame if the player has reached the GoalNode. Test this by walking to the top of the StairsPlatform. GameManager invokes Win to end the level.
Use the Restart button to play again.
Hooray! You’ve now created your own Monument Valley-style game.
Where to Go From Here?
Congratulations on finishing this tutorial! Click the Download Materials button at the top or bottom of the tutorial to explore the completed project.
Check out the final version of this project from the downloaded materials. It contains an AlternateLevel in Scenes that shows a slightly more advanced treatment of the bridge platform. Notice that its Clickable tiles have more than one node attached.
The player must now use the sides and bottom of the Bridge as walkable surfaces. This time, you’ll need a few more clicks to reach the goal.
Of course, this prototype just scratches the surface of the game’s unique mechanics.
Use everything included in the sample project to build a more challenging scene. If you’re comfortable with C#, feel free to extend the scripted components. With just a little work, you can make your own perspective-bending puzzler!
There are a load more tutorials on this site that cover how to make certain game types or mechanics. Start your search here and check them out!
Thanks so much for reading this tutorial. If you have any questions or comments, join the discussion below.