How to Make a Game Like Monument Valley

Learn to create a simple click-to-move game where you navigate a series of seemingly impossible platforms like in Monument Valley. By Wilmer Lin.

4.7 (20) · 2 Reviews

Download materials
Save for later
Share
You are currently viewing page 3 of 5 of this article. Click here to view the first page.

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.

Graph component in the Inspector

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.

Node6 in the Inspector, showing Node5 and Node7 as Edges

Select other Nodes under HorizontalPlatform. Each Node is represented by a Gizmo sphere with lines drawn to its neighbors.

Node6 Gizmo lines

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.

Ramp Stairs Edges

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.

Ramp Stairs Gizmos

To save time, the other vertical or diagonal Edges for the level platforms are already set up. Check the Gizmos to verify that.

Note: Though it won’t be used in this tutorial scene, ExcludedNodes can help you cull any unwanted automatic Edges.

Setting the Goal Node

The 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 PreviousNode fields to start a new graph search.

Locate NodeGoal under the StairsPlatform and drag it into the Graph’s goalNode field.

Goal Node

Your Graph now has an end goal assigned for this level.

Node Goal Settings

Starting Pathfinding

You can create a path through the level with a graph search.

Add the Pathfinder component to the Graph GameObject.

Pathfinder component

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.

Node5 to Node10 path example

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.

Node5 to Node10 Gizmos

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.

Clicking Input

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.

Box Collider with a Clickable Script

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:

  • Clickable: Implements IPointerDownHandler from UnityEngine.EventSystems. OnPointerDown invokes 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.

PlayerController and PlayerAnimation scripts

When you’re in Play mode and you click anywhere on the HorizontalPlatform, your character now moves to the corresponding Node. Nice job!

Player moving in response to a mouse click

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:

  • PlayerController listens for the clickAction defined in Clickable. When the user clicks a valid Collider, the PlayerController invokes OnClick
  • OnClick, in turn, calls Pathfinder’s FindBestPath method for all child nodes under Clickable.
  • If it finds a valid path, MoveToNodeRoutine lerps the character’s position to the destinationNode based on 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.
  • FaceNextPosition updates the character’s rotation to align with the path as it walks.

Animating the Player

The PlayerAnimation component has a simple Animator to transition between two AnimationClips: Idle and Walking.

Drag Player’s child object, SamuraiAnimator, into the Animator field.

Player Controller script settings

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.

Player Animation

You can view the simple PlayerAnimController in RW/Animation by selecting Window ▸ Animation ▸ Animator.

Animation Controller

This has two animation states, with transitions triggered by the isMoving parameter.

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.

Player showing a drawing glitch when it moves near the VerticalPlatform

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.

Holdout fixed so the VerticalPlatform is now opaque

Now that your character displays correctly, you’ll work on making it easier to navigate the game.