Category Archives: Tutorial

Navigation with the Nav Mesh Part 2: Obstacles and Targets

In Part 1, we created a Nav Mesh and a Nav Mesh Agent to navigate on that mesh. We included a simple script to make the agent move from their starting position to a pre-determined target position, and they moved in a straight line. In this part, we will add some obstacles to the terrain to force our agent to navigate around them, and then we will add a way to change the agent’s target destination during runtime. Make sure your project includes everything from Part 1 before beginning Part 2 (you can download the Part 1 project from the link at the bottom of the Part 1 tutorial, linked above).

Add an Obstacle

Add a cube GameObject to the scene and shape it to fill a good chunk of the ground between the agent character and the target, something like this: clip_image001 Make sure the agent can not move in a straight line to the target position. Now try running the scene and see what happens…the agent will walk right through the obstacle! This is because the Nav Mesh still considers the ground beneath the obstacle to be walkable, and it doesn’t know that the cube is supposed to be an obstacle.

Edit the Obstacle

Let’s make that obstacle an actual obstacle:
  1. Select the obstacle in the Hierarchy.
  2. Add a Nav Mesh Obstacle component to the obstacle’s GameObject.
Run the scene, and watch the player navigate around the obstacle! You’ll notice that the agent slows down when near the obstacle. This is because the agent is trying to follow its direct path to the target (the ground beneath the obstacle is still marked as walkable), but finds something in its way. Obstacles have two different ways of influencing navigation. The obstacle we have set up so far is one type. This is generally used for moving obstacles, as our obstacle does not affect the navigation mesh itself; it just gets in the agent’s way and affect’s their navigation. The other way an obstacle can work is by ‘carving’ a space out of the Nav Mesh, effectively creating an area that is not walkable within the Nav Mesh. This is what is generally used for stationary obstacles (e.g. a wall). Unity’s Nav Mesh Obstacle component has an option called ‘Carve’, which carves a hole in the Nav Mesh around the obstacle. This is what we want for our current obstacle, as it doesn’t move.

Set the Obstacle to Carve

Select the Obstacle GameObject, then set its Nav Mesh Obstacle Carve property to on: clip_image002 Now open both the Scene window and the Navigation window to view the Nav Mesh. If you look closely (zoom in if you need to), you’ll see that the Nav Mesh has a hole in it around the obstacle. Run the scene and see how the agent now finds a direct path to the target avoiding the obstacle. In general you will want to enable carving for stationary objects, but leave it disabled for moving objects. Now add some more obstacles to your scene to create a very simple maze and make the agent work a bit harder to reach the target. Aim for something like this: clip_image003

Change the Target

In Part 1, we set the agent’s target as an object in the scene. In a game, you would probably want the target to change regularly. If the agent is the player, the target might be where the player is walking to; if the agent is an enemy, the target might be the player (who moves around all the time).

Move to Mouse Click

We will detect where the player clicks the mouse, then tell the agent to move to that point. Open up the Agent script you created in Part 1, and replace all the code with the following:

using UnityEngine;
using UnityEngine.AI;
public class Agent : MonoBehaviour {

    NavMeshAgent agent;

    void Start()
    {
        // get a reference to the player's Nav Mesh Agent component
        agent = GetComponent<NavMeshAgent>();
    }

    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit))
            {
                agent.SetDestination(hit.point);
            }  
        }
    }
}
In the new code, I’ve removed all reference to the target, and replaced it with some code to detect a mouse click and then tell the agent to move to where the mouse was clicked. This code is relatively self explanatory, and since it isn’t directly navigation-related, I won’t explain it in detail. Again, the only navigation-specific code is the line that calls SetDestination() on the agent. Lastly, remove the target object from the scene entirely – we don’t need it any more. Now run the scene. The agent will be still at first, but once you click somewhere on the screen, the agent will move towards the mouse click. Try clicking in lots of different positions to see the agent change course.

Moving Obstacles

Now that we can move the target, let’s try moving an obstacle. We will create a simple script to move a wall back and forth. Modify one of the existing obstacles in the scene (or create a new one), and make it big enough to act as a door to block off a part of the Nav Mesh. We will create a script that will move back and forth, and will block the player’s navigation when closed. Rename this obstacle to ‘Door’. Again, the code is not navigation –specific, so I won’t explain it in any detail. Create a new C# script called MovingObstacle, and replace everything inside with the following code:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MovingObstacle : MonoBehaviour {

    [SerializeField] Vector3 openOffset = new Vector3(-5, 0, 0);
    Vector3 openPosition;
    Vector3 closedPosition;
    Vector3 targetPosition;
    [SerializeField] float speed = 2;
    [SerializeField] float delay = 2f;
    bool waiting = false;
    float waitElapsed = 0;

    void Start () {
        closedPosition = transform.position;
        openPosition = closedPosition + openOffset;
        targetPosition = openPosition;
    }

    void Update () {
        if(waiting)
        {
            waitElapsed += Time.deltaTime;
            if(waitElapsed >= delay)
            {
                waiting = false;
            }
            else
            {
                return;
            }
        }

        transform.position = Vector3.MoveTowards(transform.position, targetPosition, speed * Time.deltaTime);
        if(Vector3.Distance(transform.position, targetPosition) < 0.1f)
        {
            if (targetPosition == openPosition) targetPosition = closedPosition; else targetPosition = openPosition;
            waiting = true;
            waitElapsed = 0;
        }
    }
}
Don’t forget to add the new script to your Door GameObject. Also make the following changes to the door obstacle:
  1. Untick the Carve option on the moving obstacle (you generally don’t want moving objects to carve the Nav Mesh).
  2. Tweak the settings in the Moving Obstacle script component of the moving obstacle to get the behaviour you want. Here’s what mine looks like:
clip_image004 My moving obstacle moves 5 units to the left, so it has an open offset of [-5,0,0] (i.e. when it is open, it moves 5 units to the left). I have given it a speed of 2 and a delay of 2 (it waits for 2 seconds before opening/closing again). Experiment with position and settings to suit your own project. Run the scene. When the door is closed, try making the agent move to an unreachable spot behind the door - the door should prevent this. Note how the agent waits at the door and then moves through it when it opens. This is because the door is not carving the Nav Mesh, so the agent knows that the obstacle is potentially temporary.

Conclusion

Now we have an agent that can move according to a mouse click, and also is blocked by obstacles on the Nav Mesh. All of this works with very little code, and it is also very efficient. If you implemented the same behaviour with your own code, you would have to do quite a bit of work, and it probably wouldn’t work as well (especially if you scale it up to work with many characters simultaneously.

Download

Here is a link to download the tutorial project as it should be at the end of this part:

TMI! Stop Making Everything Public!

I see a lot of great tutorials that make a big mistake. They advocate making lots of public fields/variables in Unity scripts. Even some of the best tutorials do this, and it’s BAD! It’s no surprise then, that many beginners make all of their variables public. Public fields have their place (or they wouldn’t be available), so using public isn’t always wrong. It is just that many Unity developers use public incorrectly, leading to poor programming habits that can make code hard to work with and debug.

tl;dr

Basically, unless your field needs to be accessed from other scripts, keep it private. Much of the time in Unity tutorials, a field is made public so that its value can be seen and edited in the Inspector window. You don’t need to make a field public for this! If you want your field to be accessible in the Unity Inspector, you can do the following: [code language=”csharp”] // myField is private, but can be seen and edited in the Inspector [SerializeField] float myField; [/code] [SerializeField] makes the field appear in the Inspector, where you can view and change it. But it is still private. You also have the option of using a ‘setter/getter’ if you want other scripts to be able to read a field’s value while only letting the field’s own script modify it: [code language=”csharp”] // myField is publicly readable, but can only be modified in this script public float myField { get; private set; } [/code]  

What is Public in C#?

When a field (or a method for that matter) is public, it can be accessed from other scripts. Most fields and methods should be private (only accessible to its own parent script), as this makes code more robust, manageable, and modular. When fields and methods are being called from all over the place, it’s hard to keep track of what each script is doing; it can become a nightmare to debug. Think about it…if a field is public and you decide to change how it behaves, you must also check that any other script accessing it is reworked to fit the change. If you find that your field value is being set incorrectly or a public method is being called at the wrong time, you will need to figure out exactly where the incorrect code is, which gets harder the more complex your project is. Using public fields and methods can make things easier because you don’t need to think too hard about how to call methods and access variables. But don’t be lazy! It’s really not hard to avoid public stuff in your code, it just needs a bit of experience and practice. The rule of thumb to always keep in mind is to restrict access to everything as much as possible. If your code is well structured, public stuff isn’t going to be needed as much. If you find yourself needing lots of public fields or methods, it’s quite likely your code is poorly structured.

Keeping Privacy

Here’s what I do:
  • make every field and method private (this is easy, as it’s the default).
  • Use [SerializeField] by default for anything that needs to be in the Inspector.
  • Only change things to public when you need to access them from other scripts and after thinking about alternatives that don’t require public access (this becomes easier with experience).
  • Regarding the above point, consider a ‘public get, private set’ field if you only need to read the value from another script without modifying it.

Conclusion

Avoid public fields (variables) and methods/functions unless you have a real need for them to be public. Use [SerializeField] to enabled private fields in the Inspector without making them public. If you want to learn more about the reasons for keeping code private and modular, I recommend picking up a book on C# programming (or even programming in general). It’s one of those topics that doesn’t seem too important to beginners (hey, making a field or method public doesn’t change the way it works, right?), but the more experience you gain, the more important it becomes to make your code higher in quality.

Move Player to Click/Touch Position

In this tutorial, we’ll make a player character move to a position on the screen where the mouse was clicked or a finger tapped, just like in real-time strategy games and many mobile games.

What We’ll Do

The steps required are:
  1. Create a simple player.
  2. Detect a touch/mouse click position on the screen.
  3. Write a script to move the player to the clicked/tapped position.
The skills you’ll learn in this tutorial are:
  • Detecting mouse/touch position and translating that to a point in the game world.
  • Making an object move towards a point on the screen smoothly.

Getting Started

  1. Create a new Unity project, and choose the 2D settings.
As with all my tutorials, if you’re stuck on the basics, or I mention something you’re not familiar with (or just need more detailed steps), please refer to my Pong tutorial, where everything you need to know about using Unity is shown in detail. I haven’t included any sprites with this tutorial, so find any sprite/image you want to use for your player. I always recommend Kenney.nl, where you can get lots of free art to use in your projects. In my example, I’m using a rabbit sprite from Kenney’s Animal Pack.

1. Create a Player

The player is going to be simple:
  1. Add an empty GameObject to your scene.
  2. Rename the GameObject to ‘Player’ (select and press F2 to rename).
  3. Add a SpriteRenderer component to the Player.
  4. Add any sprite to your project and assign it to the Player’s SpriteRenderer.
Here’s the Game window as it should look with your player: image   If you can’t see the player (but have definitely added a sprite), make sure the player’s Transform position is [0,0,0], which is the centre of the screen. That’s all we need for the player.

2. Detect Touch/Mouse Position

Detecting a touch or click position is pretty simple, but has two components. You first need to get the touch position, and then need to convert that to an actual game position so that it matches with your game content. Create a new C# script called ‘MoveToClickInput’, and replace the entire code in that script with the following: [code language=”csharp”] using UnityEngine; public class MoveToClickInput : MonoBehaviour { void Update () { if(Input.GetMouseButtonDown(0)) { Debug.Log("Mouse button clicked"); } } } [/code] Input.mousePosition also works for touch input, so we don’t need to write separate code to get a touch. More complex uses of touch can use the specific Unity APIs for handling touch and multi-touch. The only thing this script does (so far) is check for a mouse click, then print a message to the Unity console window if one is detected. Input.GetMouseButtonDown checks if the mouse button (button 0 in this case, which is the left mouse button or a touchscreen touch) was pressed down during the current frame. Be careful with these, as it’s easy to get mixed up between GetMouseButtonDown and GetMouseButton (which checks if the mouse button is held down regardless of when it was first pressed down). Attach that script to the Player object, then test your scene. Click the mouse anywhere in the Game window, and you will see that a message gets printed in the Console window showing you that the code is working. If you want to try it with a touch device, go ahead and deploy the project and test. It will work the same as with a mouse.

Add a Target

Let’s add a simple target sprite so we can see where the mouse click was (and therefore where the player will move to).
  1. Add a new empty GameObject to the scene.
  2. Rename the new object to ‘Target’.
  3. Add a SpriteRenderer component to Target.
  4. Add a sprite to the Target’s SpriteRenderer component. I used a simple circle sprite and shaded it red and made it semi-transparent.
  5. Make sure the target is centred on [0,0,0] and is visible in the Game window.
If the target is hidden behind the player sprite, change the SpriteRendere’s Order In Layer property so the target has a higher value than the player (which will ensure it is drawn in front of the player sprite). Here’s my red dot target drawn on top of the player: image Now, let’s give the input script a reference to the target, and move the target to the clicked position. Since this script is still very short, here is the entire script with the new code added in: [code language=”csharp”] using UnityEngine; public class MoveToClickInput : MonoBehaviour { [SerializeField] Transform target; void Update () { if(Input.GetMouseButtonDown(0)) { Debug.Log("Mouse button clicked"); target.position = Input.mousePosition; } } } [/code] What we’ve added is a Transform variable called ‘target’, and a line of code to move the target transform to the position of a detected click or tap. To assign the actual Target object to the target variable, go into Unity:
  1. Select the Player GameObject.
  2. Drag-and-drop the Target GameObject into the Target field in the Move to Click script component.
Now run the scene (but beware it won’t work as expected!)… So you click in the scene, but don’t see the target appear where you click? That’s to be expected because the mouse position reported by Unity is not relative to the game screen. If you look for the target in the Scene window, you’ll find it very far away from where you actually clicked!

Adjust the Mouse Click Position to Game Space

When you get Input.mousePosition, you are getting the position of the mouse in pixel co-ordinates, with [0,0] being the bottom left corner (this is called ‘screen space’). But when you are dealing with transforms in Unity, you are dealing with the ‘world space’, which is the Unity co-ordinate system with [0,0] in the centre, and has a different scale where a single unit represents dozens or hundreds of pixels. So we need to translate the mouse position from screen space to world space to use it with transforms. Luckily, this is very easy: [code language=”csharp”] var clickedPos = (Vector2)Camera.main.ScreenToWorldPoint(Input.mousePosition); [/code] The above code converts the mouse’s screen position to a game world position. The (Vector2) at the front forces the value to be Vector2 (rather than Vector3 default), which ensures the position is at [0] on the Z-axis, since we’re working in 2D. Replace the script’s Update() method with the following: [code language=”csharp”] void Update () { if(Input.GetMouseButtonDown(0)) { var clickedPos = (Vector2)Camera.main.ScreenToWorldPoint(Input.mousePosition); target.position = clickedPos; } } [/code] Now run the scene and the red dot (or whatever you used) should move to where you click the mouse.

3. Move the Player Towards the Click

We now have a player and can detect a mouse click in world space. The next thing we need to do is make the player move to the click. This is not as simple as what wo did for the target object because we want the player to ‘walk’ to the clicked spot. Let’s go through the code piece-by-piece, and I’ll include the final script at the end. Firstly, we want to keep track of where the player is heading, so we add a script-level variable that will always remember the player’s current target: [code language=”csharp”] Vector2 targetPos; [/code] We also need to give our player a speed so they move at the rate we want, so a speed variable is required: [code language=”csharp”] float speed = 6f; [/code] When the scene starts, the player doesn’t have a target to move towards, so we set the target position to be equal to the player’s current position in the Start() method: [code language=”csharp”] private void Start() { targetPos = transform.position; } [/code] We should also rejig the code that gets the mouse position and moves the target so it uses the same targetPos variable that the player will move towards, so that will be changed to the following: [code language=”csharp”] if(Input.GetMouseButtonDown(0)) { targetPos = (Vector2)Camera.main.ScreenToWorldPoint(Input.mousePosition); target.position = targetPos; } [/code] And after that code, still in the Update() method, we will do the real magic – move the player towards the targetPos: [code language=”csharp”] if((Vector2)transform.position != targetPos) { transform.position = Vector2.MoveTowards(transform.position, targetPos, speed * Time.deltaTime); } [/code] The above code first checks if the player is already at its target position (if it is, there is no point moving it), then uses Vector2.MoveTowards to (you guessed it) move towards the targetPos from its current position. The third parameter of Vector2.MoveTowards is the movement amount, which is effectively the speed. We multiply our player speed by Time.deltaTime (the time since the last frame) to ensure that the movement amount is kept consistent with the framerate (the framerate is always going to fluctuate a little, so if the player moved the same amount every frame, it would change speed all the time and therefore move very roughly). Here’s the completed code: [code language=”csharp”] using UnityEngine; public class MoveToClickInput : MonoBehaviour { [SerializeField] Transform target; float speed = 6f; Vector2 targetPos; private void Start() { targetPos = transform.position; } void Update () { if(Input.GetMouseButtonDown(0)) { targetPos = (Vector2)Camera.main.ScreenToWorldPoint(Input.mousePosition); target.position = targetPos; } if((Vector2)transform.position != targetPos) { transform.position = Vector2.MoveTowards(transform.position, targetPos, speed * Time.deltaTime); } } [/code] Try out your scene. You should now have everything working! rabbit  

Next Steps

Here are some things you can try adding to improve this little project:
  • Animate the player.
  • Make the target sprite fade away after a short time.
  • Add a particle effect to the target sprite to better indicate a mouse click.
  • Adjust the code so that you can drag your finger around the screen and the player follows (hint: change Input.GetMouseButtonDown to Input.GetMouseButton).