Top down, Click To Move functionality Using NavMesh, Raycasting And Blend Trees In Unity.
I was a big Runescape player growing up, and I actually still am almost 18 years later. So being a Runescape player, I’m all too familiar with the top down click to move functionality, which has been the means of travel in good ole ‘Scape since its release way back in 2001.
Why don’t we recreate this classic mechanic in Unity?
The first thing we need to do is the basic setup for our player and Navigation system.
I’m just going to start by throwing down a sphere at origin to represent our player. We will add in a mesh character later.
Now we need to open up the AI window and go into the bake tab. Window -> AI -> Navigation.
Well leave all the settings as they are for now.
Next, lets make sure our terrain is set to Static so it can be included in the baking process. Anything that you want to be traversable needs to be marked as Static.
Now, let's go back to the bake tab in the navigation window and click the Bake button at the bottom right. This could take a while depending on the size of your terrain. You should see a blue surface cover your terrain after the baking process is complete. If you don’t see anything you need to check the Show NavMesh option at the bottom right of the editor and you need to make sure you have gizmos enabled.
If you still can't see anything, go to to the layout tab at the top right of the editor and change it back to default. Sometimes the Unity Editor doesn’t like to play nice with custom layouts.
Nice. Now we have something for our player to navigate on. Now let’s add a few components to the player.
We need to add a NavMesh Agent to the player so it can use the NavMesh we just created. Also, let’s go ahead and put an Animator component on the player, but that will be for later when we have our character mesh in place. Well just use the sphere for now.
Ok, it’s Scripting Time. We need a way to communicate with the NavMesh Agent agent on the player and tell it where we want it to go. Let’s start by creating a script called Mover and lets cache a reference to the component we need, for performance sake. this will allow us to avoid many GetComponent() calls. I also like to keep things tidy with namespaces. They should mirror your folder structure in your project. I have this script in a folder called “Movement” within the the main “Scripts” folder.
Once we have a reference to the NavMesh Agent we can start to make some magic happen. The NavMesh Agent has a property called destination. We can set that in code and the referenced agent will move to that location using the NavMesh and abiding by the settings on the referenced NavMesh Agent component, like Speed, Angular Speed etc.
What we want to happen is for us to click anywhere on the NavMesh and have our player move there. Easy enough right? We can use Raycasting to achieve this. Think of Raycasting like a laser beam coming from an origin point in a certain direction that has information tied to it, such as the location of where the ray hit something, and that is exactly what we want. If we pass in a RayCastHit out parameter to the Raycast, we can access that and check where it hit using the RayCastHit.Point. This is what we’ll use for our NavMesh Agent’s destination. Let’s make it happen in code.
Head back to your Mover script and create a function called MoveToCursor().
First,we need to find a way to cast our ray from our mouse position. Thankfully, our MainCamera has a way to do that easily, with a method called ScreenPointToRay(). We can pass in our current mouse position and get the origin of our Ray.
We make sure our MainCamera isn’t null and then we create our Ray and RayCastHit references. Next, we need to actually fire the ray. When you cast a ray, it returns a True bool when you hit something. Let’s just put the RayCast inside an If statement and run our code only if we hit something.
We can also clean things up a bit and put the RayCastHit reference in-line in the If statement. We use the Out keyword for to access the variable after the raycast and we use Var to do the inline statement. Inside of the If blocks is where we’ll set the destination of our agent.
After we’ve got that down we’re going to put the function inside of Update() and only run the code if we’re pressing down the mouse.
Ok, we’re looking good.
Put the Mover script on the player and let’s set take a second to set our camera to a top down view. You can easily do this by getting the camera how you want it in the Scene view and then selecting the MainCamera in the hierarchy and pressing ctrl/cmd + shift + f. This will realign the camera in the Game view exactly how it is in the Scene view. Once you have the camera in place we can hit play and see what happens.
Left clicking or holding the left mouse button anywhere on the NavMesh will cause the sphere to move towards that location, thanks to our Input.GetMouseButton(0). If we would have chosen Input.GetMouseButtonDown(0), this would only work on down clicks. doing it the first way gives us the best of both worlds. Also, if you click anywhere that isn’t on the NavMesh, the NavMeshAgent will compute a path to the closest spot to that that unreachable location.
We don’t want to be stuck in one view spot, however. We need to make the camera follow us when we move. Childing the camera directly to the player could work, but the problem with that is we would be taking on the player’s rotation too and we don’t want all of that spinning. What we can do is create a simple Camera Follow system.
The way it’ll work is by creating an empty game object and, through code, always setting it’s position to the player’s position. And then we simply child the camera to that empty game object. This will allow us to adjust the camera to whatever distance we want and it won’t take on any of the player’s rotation.
Let’s Create a script called CameraFollow and then serialize a Transform variable called Target. Next, in LateUpdate(), well just set the CameraFollow object’s position to the target’s position, which will be the player. We use LateUpdate() because the the last thing we want to happen is for the camera to follow the player. LateUpdate() comes after Update() in the execution order. This will ensure the camera’s movement always comes after the player’s movement which will be in Update().
Next, we’ll attach this script to the empty CameraFollow object and plug in the Player GameObject into the exposed Target field on the CameraFollow script in the inspector.
We can go ahead and position the CameraFollow object at the players location by copying the transform of the player and pasting the values to the CamerFollow object. This will position it exactly where the player is. Next, child the MainCamera to the CameraFollow object and we will be good to go.
Let’s test it out.
Looks good! This is a primitive camera follow option and it can be done better with Cinemachine, but that is a topic for later.
Ok, we’ve sloughed through a lot of the grunt work and we are about to finish it off with some an actual character and some animations to go along with it. All the real fun stuff.
I’m using a model from one of Synty’s low poly packs, the Fantasy Kingdom pack. Link below.
POLYGON Fantasy Kingdom - Low Poly 3D Art by Synty | 3D Fantasy | Unity Asset Store
Elevate your workflow with the POLYGON Fantasy Kingdom - Low Poly 3D Art by Synty asset from Synty Studios. Find this &…
and for the animations im using Kevin Iglesias’ Basic Motions pack. I have the paid version but the animations used are included in the free version of this pack. Link below.
Basic Motions | 3D Animations | Unity Asset Store
Elevate your workflow with the Basic Motions asset from Kevin Iglesias. Find this & other Animations options on the…
Let’s pull the character into the scene and adjust the NavMesh Agent accordingly so that it matches up.
This looks pretty good! You can adjust the height of the of the NavMeshAgent using the height property and you can readjust the entire position of the NavMeshAgent with the Base offset
When we hit play we see that the bottom of the NavMesh Agent snapped down to the NavMesh.
Now we can add some animations.. almost!
We need to make an animator controller for the player. Right click the asset folder and click “Create” and then find “Animator Controller” and select that and name it “Character”.
let’s open up our Animator window as well. Window -> Animation -> Animator and make sure we’re clicked into the parameters tab.
before we make the blend tree, lets plug that controller we just made into the controller field on the Animator component on the player.
Another thing is we need to make sure of is that Apply Root Motion is unchecked.
If you’re using a Synty pack, the character should come with an Avatar. Plug that in as well if it isn’t already. An Avatar is just a bone mapping configuration that the Animator Controller uses to move the character accordingly.
Click on the Player in the hierarchy and then come down to the Animator window and right click and then select Create State and then finally from New Blend Tree.
Name the blend Tree “Locomotion” on the right in the inspector.
Click into the new Locomotion node and you should also have noticed that a new parameter popped into existence. Long double click on the parameter to change the name to “Forward” and you’ll see that the slider name on the Blend Tree changed to that name as well.
Now we need to add some animations to blend between. Yay!
On the right, in the inspector, add three new motion fields using the little + button.
This is where we’ll put our three animations. idle, walk and run. We’ll also uncheck Automatic Thresholds and put them in manually. We can get that data from the Root motion version of those animations, that came with the pack. For example, I’m looking in the Root Motion version of the Walk animation and find the value 1.5 for the average velocity. Since we are using the non-Root Motion animations, well just get these values manually.
Once we have those values plugged in, we can actually preview the blending of the animations to make sure everything looks good.
Hit the little blend tree tab, smash that play button and you can scrub through the blended animations. nice!
The average velocity for the run animation is around 3.6. We can match that up with our NavMesh Agent’s speed property, which is the maximum speed that it can reach. Now the speed will always match the run animation. Let’s also change the Angular speed back down to a low value, say 250. This actually isn’t what we want in the end but we’ll put it low for now to show the difference it makes with a higher value.
If you don’t have access to that animation velocity info I was talking about earlier, you can just use these values for now and then adjust later on.
Now it’s time to write a little more code to bring all of this together. We’re in the home stretch here.
We need to change that blend value according to our NavMesh Agent’s speed. The way to do this is by taking that global speed value and turning it into a local value that our Animator Component can use.
Let’s head back into our Mover script and cache a reference to our Animator Component.
Since we are wanting to update our Animator component, we’ll create a function called UpdateAnimator() and put that in Update().
The main challenge with this is getting the global speed value and changing it to something that the animator can use. We can use the transform method InverseTransformDirection(). This will take a global vector 3, in our case velocity/speed, and change it to a local value.
Then we take that transformed value’s z axis and set that equal to a speed variable and then we access that “Forward” blend value on our animator and set it equal to the previously mentioned speed value.
Thanks to Resharper’s recommendation, I am using a cached property index for the first string parameter in the SetFloat method. This is way faster than accessing a regular string.
Ok, the moment we’ve all been waiting for. Let’s test it out!
Sweet! We’ve got blended animations driven by our NavMesh Agent and Animator. One more loose end to tie up though. You probably noticed that the Player turns really slow and you would be right. That’s where that Angular Speed value on the NavMesh Agent comes into play. It controls how fast the Agent turns. Let’s bump that up to 5000 and finish this thing off.
There we go! that definitely cleaned things up a bit.
Well that’s it. We have successfully recreated a classic game mechanic that will keep being utilized for years to come, no doubt.
Until next time,