Raycasting in Unity Part 3: Applications

This is the third post in a three part series on Raycasting in Unity. The previous posts are:

Find the code on GitHub


1. Placing Objects

Click to expand section

Build a Camera Rig

Before we jump into to placing objects it will be helpful to have a camera that we can move around in play mode. There are many options out there including the standard assets SimpleCameraController which you are welcome to use, but here we will be making our own much simpler controller.

Rather than modifying the camera position directly, I prefer to place the camera object inside a parent gameObject, called CameraRig or something, and add the script that controls movement to that. This technique allows a little more freedom and control to cameras which is beyond the scope of this workshop.

For now, set the camera object as a child of another gameObject. Make sure the Transform variables of the camera object are reset.

In short the reason we are doing this is so that we can move the camera along the XY plane (forward, back, left, right) easily, while still pointing the camera down at an angle:

Without CameraRig, the “forward” direction of the camera would be pointed slightly down, and moving the camera “forward” would drive it down into the ground. This way, we can keep CameraRig facing along the XY plane (the red arrows) and still tilt the camera down.

So with our setup, use CameraRig to move and pan the camera, and the x value of MainCamera’s rotation to tilt it down.

Then in CameraRig create a new script (I called mine RaycastWorkshopCameraController). We want to use W and S to move the camera forward and back, and A and D to turn (pan) the camera:

public class RaycastWorkshopCameraController : MonoBehaviour
{
    [SerializeField] float rotationSpeed = 1f;
    [SerializeField] float moveSpeed = 0.1f;
    void Update()
    {
        float vert = Input.GetAxis("Vertical");
        float horz = Input.GetAxis("Horizontal");

        transform.Rotate(Vector3.up * horz * rotationSpeed, Space.World);
        transform.Translate(Vector3.forward * vert * moveSpeed, Space.Self);
    }
}

Create an interesting floor

Add a plane and some randomly rotated cubes to the scene – just to create some bumpy geometry.

Make the object you wish to place in the scene

Once you’ve made the object, be sure to prefab it.

I made a sheep

Create an object and script to handle placing objects

This script will raycast from the camera into the scene when you click the mouse, and upon a successful hit will add a sheep at the clicked position.

public class SheepPlacementHandler : MonoBehaviour
{

    [SerializeField] GameObject sheepTemplate;

    //Since this script is not attached to the camera we must store a reference to it:
    Camera cam;

    void Start()
    {
        //Find and store the main camera in the scene
        cam = Camera.main;
    }

    void Update()
    {
        //Only raycast if mouse has been clicked
        if (Input.GetMouseButtonDown(0))
        {
            HandleRaycastPlacement();
        }
    }

    void HandleRaycastPlacement()
    {
        //Create a ray pointing out from the camera into the scene
        Ray ray = cam.ScreenPointToRay(Input.mousePosition);

        RaycastHit hit;

        if (Physics.Raycast(ray, out hit, 100f)){

            //Set the position of out new sheep based on the point we hit:
            Vector3 position = hit.point;

           

            GameObject sheepClone = Instantiate(sheepTemplate, position, Quaternion.identity);

            //Set the 'up' vector of the sheep based on the Normal of the surface we hit. 
            //This will place the sheep standing 'up' on the surface
            sheepClone.transform.up = hit.normal;

            //This isn't necessary but helps keep our hierarchy clean, by childing the sheep to this SheepPlacementHandler object
            sheepClone.transform.parent = transform;
        }
    }
}

If you’d like you can add a rigidbody to your sheep prefab

2. Selecting Objects

Click to expand

Selecting objects is a very similar process to placing them. There is a little more plumbing involved in making sure the object we select is the parent object of all the sheep parts, but the Raycasting is very much the same.

In this case, I am also using the “else” condition of the if statement to detect when no object is hit – to deselect.

public class SheepSelectionHandler : MonoBehaviour
{
    //Keep track of selected object
    GameObject selection = null;

    // Update is called once per frame
    void Update()
    {
        //When mouse is right clicked:
        if (Input.GetMouseButtonDown(1))
        {
            HandleRaycastSelection();
        }
    }

    private void HandleRaycastSelection()
    {
        //Cast ray from screen to world
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

        //We can actually declare 'hit' as a type RaycastHit inline:
        if (Physics.Raycast(ray, out RaycastHit hit, 100f))
        {
            //Reference the object that we hit
            GameObject hitObject = hit.collider.gameObject;


            if (hitObject.GetComponentInParent<Rigidbody>() != null)
            {
                ClearSelection();
                selection = hitObject.GetComponentInParent<Rigidbody>().gameObject;
                SetColourOfChildren(selection, Color.red);
            }
            else
            {
                ClearSelection();
            }
        }
        else
        {
            ClearSelection();
        }
    }

    private void ClearSelection()
    {
        if (selection != null)
        {
            SetColourOfChildren(selection, Color.white);
            selection = null;
        }
    }

    void SetColourOfChildren(GameObject obj, Color col)
    {
        Renderer[] renderers = obj.GetComponentsInChildren<Renderer>();

        for(int i = 0; i < renderers.Length; i++)
        {
            Material mat = renderers[i].material;
            mat.color = col;
        }
    }


}

3. Other Applications

Try these if there’s time

Leave a Reply

Your email address will not be published. Required fields are marked *