Creating 'Point and Click' Characters with Screen Rays

There are many ways that a play may want to move a character around a space. We've looked at many different options, particularly more 'real-time' actions such moving characters using Character Controller components. Some games, particularly top-down games or point-and-click games, may control a character by clicking where they want them to go, and the character will then move there.

Games that use this method of control include:

  • Disco Elysium

  • League of Legends

  • Grim Fandango

  • Diablo IV

  • Project Zomboid

  • etc.

This type of movement is particularly useful if you want the player to have a wide view in all directions. It allows the player to spot problems around them and respond to it.

We can incorporate this using the NavMesh we've been using for AI agents. The NavMesh also doubles as a particularly useful method for moving players around as well, particularly in keeping players within certain navigable space.

Let's just recap raycasts then:

Rays are made up of two parts:

  • Origin

  • Direction

We can make Ray variables in a script in the usual variable declaration section (the top of the class):

Typically we also declare a variable to store information about what a ray hits here too. That variable is of type RaycastHit.

In our instance, we are going to 'aim' our ray using the screen. Specifically then, we want to draw a ray from the camera to the coordinates of the mouse on screen. This is known as a ScreenPointToRay raycast.

// Check if the left mouse button was clicked
        if (Input.GetMouseButtonDown(0))
        {
            // Create a ray from the camera to the mouse position
            screenRay = Camera.main.ScreenPointToRay(Input.mousePosition);
            //etc...

In this example, I have an if() statement that triggers some code if I click on the left mouse button. Specifically, it 'aims' a new ray using the main camera's ScreenPointToRay() function, which requires an argument of which direction to aim the ray from the camera. In this case, I am using the mouse's coordinates on screen.

Remember, making a ray is like aiming, we haven't actually cast the ray yet!

Still inside the if() statement from earlier, we can cast our ray, and do something with it if it hits something. Remember, we cast rays from inside an if() statement, because if we hit nothing, we don't want any additional code to run regarding that ray. It would be like telling the player character to go somewhere, but not having any actual information to tell them regarding exactly where.

            // Cast a ray to detect if it hits an object in the scene
            if (Physics.Raycast(screenRay, out hitData))
            {
                // Store the hit position as the new target
                pathfindTarget = hitData.point;

                // Set the NavMeshAgent's destination to the target point
                nav.SetDestination(pathfindTarget);

We shoot our ray using the screenRay variable (e.g. our screen point to ray we've been aiming), then we're setting our pathfinding target to whereever the ray hits. Then, we update our NavMeshAgent character with a new pathfinding target to where the ray hit.

Reference Script

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

public class ThirdPersonNavMeshCharacter : MonoBehaviour
{
   // Reference to the NavMeshAgent component, which allows the object to move using Unity's navigation system.
    NavMeshAgent nav;

    // Stores information about objects hit by a raycast.
    RaycastHit hitData;

    // Represents a ray projected from the camera towards the screen.
    Ray screenRay;

    // The target position for pathfinding.
    Vector3 pathfindTarget;

    // Start is called before the first frame update
    void Start()
    {
        // Get the NavMeshAgent component attached to the GameObject.
        nav = GetComponent<NavMeshAgent>();
    }

    // Update is called once per frame
    void Update()
    {
        // Check if the left mouse button was clicked
        if (Input.GetMouseButtonDown(0))
        {
            // Create a ray from the camera to the mouse position
            screenRay = Camera.main.ScreenPointToRay(Input.mousePosition);

            // Cast a ray to detect if it hits an object in the scene
            if (Physics.Raycast(screenRay, out hitData))
            {
                // Store the hit position as the new target
                pathfindTarget = hitData.point;

                // Set the NavMeshAgent's destination to the target point
                nav.SetDestination(pathfindTarget);
            }
        }
    }
}

Last updated