# Unity Tutorial: Making a 3D Compass

6 December 2022*A video version of this tutorial is available here.*

I recently wanted to create a handheld compass for a VR game that I am working on. This seemingly simple goal took me a day of fiddling around with rotations and quaternions, until I found a solution that was simple and satisfactory enough. Here, I share what I learned so that you can easily implement similar functionality in your games and apps. Let’s see how our compass works:

To start, we first create a simple compass object consisting of a compass body and a needle. The *Compass Body* object is a simple cylinder primitive that is squashed along the y-axis to create a flat disc.

Next, we create a simple needle which is an elongated cube that is aligned parallel to the compass body, outward from the center. This *Needle* will rotate along the y-axis of the compass body, to point towards our target direction.

To make rotating the needle easier, we first create an empty GameObject called *Needle Parent* as a child of the compass body, and reset its position and rotation so that it is perfectly aligned with the compass body. *Needle* is then added as a child under *Needle Parent*.

**Tip:** When creating your compass, pay attention to the rotation of each GameObject. It can be helpful to switch Unity’s *Tool Handle Rotation* setting to *Local*, and ensure that their rotations match the ones shown in this tutorial. This tutorial is written with the assumption that *Needle* is aligned towards the positive z-axis when creating the compass.

To make the needle point towards our target, we will write a script to rotate *Needle Parent* along the y-axis. This script will be attached to *Needle Parent*.

We first need to specify a *Target* that our compass will point towards. This *Target* can be a GameObject, a point, or a specific direction (e.g. `Vector3.Forward`

). In our case, we use a sphere GameObject in the scene that is referenced as `targetObject`

. We first obtain the position of `targetObject`

:

`Vector3 target = targetObject.transform.position;`

If we want to use a general direction instead of a GameObject, the target position can be obtained by adding the desired direction vector to the transform of *Needle Parent*:

`Vector3 target = transform.position + Vector3.forward; // (to make needle point north)`

The target position that we obtained is in the **World** space. To correctly determine the needle rotation even when the compass body itself is rotated, we need the obtain the position of our *Target* in the **Local** space of our compass. So, we convert the **World** space coordinates of *Target* to the **Local** space coordinates of our compass body (which is the parent of the *Needle Parent* GameObject that the script is attached to):

`Vector3 relativeTarget = transform.parent.InverseTransformPoint(target);`

Once we have the `relativeTarget`

coordinates, we can determine how much the needle should rotate along the y-axis so that it points towards *Target*. We use the `Mathf.Atan2`

function to calculate the needle rotation. `Atan2(x, z)`

calculates the angle between the ray from the origin to the point `(x, z)`

and the z-axis, as shown in the figure below. This is how much our needle should rotate.

Since `relativeTarget`

is in the **Local** space, the origin in the context of `Atan2`

is the origin of the compass body, not of the world itself. Also, we do not use the y coordinate of `relativeTarget`

since our needle is flat on the body of our compass, and it does not show any information about the y elevation of our target. Since `Atan2`

gives us an angle in radians, we convert it to degrees and obtain our `needleRotation`

as follows:

`float needleRotation = Mathf.Atan2(relativeTarget.x, relativeTarget.z) * Mathf.Rad2Deg;`

Now, all we need to do is to apply this `needleRotation`

to *Needle Parent*. We apply a `localRotation`

to our transform, passing `needleRotation`

as the y rotation and keeping the x and z rotations as zero:

`transform.localRotation = Quaternion.Euler(0, needleRotation, 0);`

Our final script looks like this:

`using System.Collections;`

using System.Collections.Generic;

using UnityEngine;

public class PointCompass : MonoBehaviour

{

public GameObject targetObject;

void Update()

{

// get worldspace coordinates of target

Vector3 target = targetObject.transform.position;

// Vector3 target = transform.position + Vector3.forward; // (to make needle point north)

// convert to local coordinate space of compass body

Vector3 relativeTarget = transform.parent.InverseTransformPoint(target);

// determine needle rotation with atan2

float needleRotation = Mathf.Atan2(relativeTarget.x, relativeTarget.z) * Mathf.Rad2Deg;

// apply needle rotation

transform.localRotation = Quaternion.Euler(0, needleRotation, 0);

}

}

That’s it! The compass should now always point towards the *Target*. The code above is the minimum required to make the compass work, but you can modify it to make it even better. For example, you could make `needleRotation`

change smoothly with a specified speed by using `Mathf.Lerp`

. Hope it helped!