Chapters

Hide chapters

Unity Apprentice

First Edition · Unity 2020.3.x LTS Release · C# · Unity

Before You Begin

Section 0: 4 chapters
Show chapters Hide chapters

9. Basic AI & Navigation
Written by Matt Larson

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

Introduction to the Arena

This chapter is the first installment in the three-part adventure arc of veggie combat! Can you survive as one tank against an army of mad veggie gladiators armed with cutlery???

You begin with a starter project containing an arena filled with veggie spectators eagerly awaiting the combatants. By the end of this chapter, you’ll have added some point-and-click navigation and basic enemy AI to the scene.

Are you afraid of an unlimited army of veggie gladiators? Not when you’re driving a tank!

First, you’ll need to learn how to add a basic navigation AI to your games through the Unity Navigation System. Through this system, you can create agents that are able to navigate the geometry of a scene through the generation of navigation meshes — or simply NavMeshes.

This movement system is the perfect choice for a variety of games where the dominant mechanic is not the coordination of the player, but rather the strategy of exploring and interacting with a complex world of objects. Top-down adventure games and turn-based strategy games are both excellent examples of mechanics where the Unity Navigation system is a great fit.

Navigation

Navigation in Unity is automatically handled by intelligent agents that find the shortest paths on NavMeshes. NavMeshes are data structures composed of polygons, where there are no barriers between points within a given polygon. Paths across a map are found by stepping between the polygons of the NavMesh structures. Overall, the NavMesh simplifies the geometry of objects in the scene to less varied surfaces that face upwards.

Building a NavMesh

Building a NavMesh is done through a process called NavMesh Baking, where all the geometry of the scene is analyzed and GameObjects and terrains are processed to make a simplified surface that can be transversed by a NavMesh Agent. Shortest paths are calculated over this surface using algorithms such as the A* pathfinding algorithm.

Configuring a player NavMesh Agent

The NavMesh Agent component enables characters to utilize the baked NavMesh to find pathways to a target and avoid obstacles. It’s time to make your first moving NavMesh Agent to be guided by point-and-click mouse input of the new Unity Input System.

Game logic

Look at the Hierarchy view of the tank prefab. Your tank prefab is actually constructed of a hierarchy of different 3D models — from the wheels all the way to the cannon. As an animating effect, you’ll allow the cannon to freely move and target enemies to fire the projectile. You’ll implement the PlayerController script logic next to allow this.

Setting a destination from Input System

The first thing you need to do is to get the tank to move. The goal is to convert a position of the mouse to a 3D location to direct the tank as a navigation target.

RaycastHit hit; // 1
Debug.Log("Try to move to a new position");

// 2
if (Physics.Raycast(Camera.main.ScreenPointToRay(
                        Mouse.current.position.ReadValue()),
                    out hit,
                    100))
{
    agent.destination = hit.point;  // 3
    Debug.Log("Moving to a new position");
}

Setting aim from Input System

Return to the PlayerController script and find the OnFire method stub. Fill in the logic for the projectile firing with this:

// 1. On a mouse click, get the mouse current position
float x = Mouse.current.position.x.ReadValue();
float y = Mouse.current.position.y.ReadValue();

// 2. Ray trace to identify the location clicked.
Ray ray = Camera.main.ScreenPointToRay(new Vector3(x, y, 
              Camera.main.nearClipPlane));

// 3. Raycast to hit the surface, turn turret to face.
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
    Vector3 target = hit.point;
    target.y = cannonHorizontal.transform.position.y;
    cannonHorizontal.transform.LookAt(target);
}

// 4. Find the direction forward of the cannon
Vector3 forward = cannonVertical.transform.forward;
Vector3 velocity = forward * launchVelocity;
Vector3 velocityHand =
    new Vector3(velocity.z, velocity.y, velocity.x);

// 5. Instantiate a projectile and send it forward
Transform cannon = cannonVertical.transform;
GameObject fork =
    Instantiate(projectile, cannon.position, cannon.rotation);
fork.GetComponent<Rigidbody>().AddForce(velocity);

Enemy AI and NavMesh Agents

Next, you’ll incorporate NavMesh Agents to provide automated movements for some enemies.

// 1. States
enum States { Ready, Attack, Dead };
// 1. Set the destination as the player
agent.SetDestination(player.transform.position);
characterAnimator.SetFloat("Speed", agent.velocity.magnitude);

// 2. Stop when close and animate an attack
if (agent.remainingDistance < 5.0f)
{
    agent.isStopped = true;
    characterAnimator.SetBool("Attack", true);

    state = States.Attack;
    timeRemaining = 1f;  
} 
else
{
    // 3. Stop attacking and allow movement
    agent.isStopped = false;
    characterAnimator.SetBool("Attack", false);
}
timeRemaining -= Time.deltaTime;
if (timeRemaining < 0)
{
    state = States.Ready;
    if (Vector3.Distance(player.transform.position, 
            gameObject.transform.position) < 5.0f)
    {
        player.GetComponent<PlayerController>().
                    DamagePlayer();
    }
}

characterAnimator.SetBool("Death", true);
Destroy(gameObject, 5);
state = States.Dead;
agent.isStopped = true;

Game state

To fully finish the game design, you’ll implement waves of Carrot Warriors to battle against. You’ll also track the player’s health as the attack happens.

void UpdateGUI()
{
    switch (state)
    {
        case States.Countdown:
            int timer = (int) Math.Ceiling(timeRemaining);
            MessageBar.text = timer.ToString();
            break;
        case States.Fight:
            MessageBar.text = "Fight!";
            break;
        case States.Battle:
            MessageBar.text = "";
            break;
        case States.Lose:
            MessageBar.text = "You Lose!";
            break;
    }

    HealthBar.sizeDelta = new Vector2(735 * player.GetComponent<PlayerController>().GetPercentHealth(), 65);
}

Adding the enemy waves

The enemy waves spawn at the gates of the arena. Each of the Scene / GateWall GameObjects has a component GateSpawner that can instantiate a crowd of Warrior_Carrot enemies. You need to connect each of these into the Game State to provide spawn locations for the enemies.

agent.enabled = false;

Common pitfalls with NavMesh Agents

While NavMesh Agents provide out-of-the-box navigation, they do have limitations:

Key points

  1. The Unity Navigation System provides advanced pathfinding intelligence.
  2. Window ▸ AI ▸ Navigation enables the Unity Navigation panel.
  3. Bake a NavMesh for a selected GameObject to create a navigation geometry.
  4. GameObjects can be marked Navigation Static to allow them to be baked into the NavMesh.
  5. A component called the Nav Mesh Agent must be added to a GameObject to provide pathfinding and obstacle-avoiding intelligence.
  6. You can create complex behaviors using a NavMesh Agent by calling its API from your MonoBehaviour scripts.
  7. The Navigation System is a great way to build enemy AI that can intelligently march around a field, chase after targets and avoid obstacles.

Where to go from here?

In this chapter, you learned about using the Unity Navigation System to implement point-and-click navigation and some basic enemy AI.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.

Unlock now