Sine, Cosine, & Tangent in Unity: Moving Game Objects (Part 2)

Brian David
11 min readSep 18, 2024

--

Rad2Deg — created in collaboration with DALL.E3

Precise control of an object’s movement and rotation when designing enemy AI or managing player movement requires understanding how to calculate angles and move objects based on vectors. These concepts are the foundation of game’s mechanics. In this story, I will guide you through my experience learning how to calculate angles using Mathf.Atan2, converting radians to degrees with Mathf.Rad2Deg, and applying these calculations to move objects forward in 3D space.

In the previous story, I implemented Sine and Cosine. In this story, I will add a new 3D object that will calculate the angle and direction of the cube, calculate the Vector and then move toward the cube until it reaches its destination.

Angle Calculation Using Mathf.Atan2 and Quaternion Rotation

Let’s say I have an cylinder, representing the Enemy, and I w tand what the legs, i.e., sides, of the triangle represent based on the abbreviation TOA, Tangent = Opposite / Adjacent (for calculating angles).

Tangent of Theta, the name of this particular angle, is Opposite over Adjacent, i.e., y over x. This will result in a scalar value, the size of the angle, as a number that identifies the degrees of the angle by finding the inverse value of the method.

Using the position of the cylinder from the previous story I began to calculate the angle of the cylinder to the cube. Tan(theta = y(7) /x(3) = 2.3. To find the inverse of 2.3 I use the inverse tan -1. The angle of theta is approximately 66.80 degrees.

How to determine the inverse of a tangent using a scientific calculator
  • Back in the Unity editor, I will right-click in the Project View to create a new C# script.
  • I will name the script Tangent. After opening the script, I delete the Start() method because it won’t be used.

I have already added a cylinder shape to my Hierarchy View and positioned it in the Scene View. I want the cylinder to rotate towards the cube. I need a reference of what the cylinder needs to aim at. A reference to the _cube will be stored in the variable using a access modifier and reference data type private Transform.

create a reference data type for the script to communicate with the Transform (i.e., game object)

Back in Unity editor, I drag and drop the _cube from the Hierarchy View into the Tangent (Script) field of the Inspector that identifies the “Cube”.

empty slot for reference to 3D game object in the Inspector of Tangent (Script)
  • First, in Update(), I need to know the direction. The direction is the sum of the destination, which is the cube’s .position subtracted by the cylinders (this.transform.)position; .
calculating this game objects Vector based on its current position and the ending position
  • Second, because I am the cylinder, need to calculate the angle using TOA, Tangent Over Adjacent, specifically the inverse of the tangent.

Mathf.Atan2()

In Unity, the Mathf.Atan2() method does the operation automatically.

The y and x are a part of the Vector3(x, y, z) therefore within the Mathf.Atan2() method, the variable direction is used to identify the y over the x. This will return the angle in radians as identified in the Description found in the Unity Documentation.

What is a radian?

Bear with me for a moment for the sake of understanding the how the angle is calculated using Unity’s built-in methods. Imagine you’re standing in the middle of a big circle. Now, if you take a piece of string that’s the same length as the distance from the center of the circle to the edge (the radius), and then you lay that string along the edge of the circle, the angle that the string covers from where you started is called 1 radian.

The angle (Theta) in the figure is a central angle in radians based on the length of the radius (Khan Academy)

The becomes a standard of measure which then allows me to then divide the circle into parts to determine how many radians it takes to make that specific circle as well as providing me with an angle.

Mathf.Rad2Deg() — Converting Radians to Degrees

When the Mathf.Atan2() returns radians, another function can be used to convert radians to degrees, i.e., tan -1 or the inverse of the tangent.

Description of Mathf.Rad2Deg() — Unity Documentation

Mathf.Rad2Deg() will determine the inverse. To actually see the cylinder use the variable angle to track the Vector3 position of its target, the _cube, as it changes, a Debug statements will be included. After the Vector3 direction is calculated the Debug.DrawRay() method will be used to use a ray to visualize this.transform.position, i.e., cylinder, to the direction of the _cube, and assign the Color of the DrawRay to .yellow.

Debug.DrawRay() method — Unity Documentation
DrawRay(Vector3 starting position, Vector3 direction, Color.yellow)

Next, after the angle is calculated, I can create a Debug.Log() to the Console and print the “Angle: ” + based on the variable angle. The float angle is set to Mathf.Atan2(direction.y, direction.x); and it calculates the Tangent of Theta.

calculating the angle with Mathf.Atan2()

When I run the application, in the Console I can see the value of the angle continuously changes as the cube oscillates.

Tangent of Theta change in response to the Cube’s movement based on Sine and Cosine
Cube’s movement based on Sine and Cosine

If I stop the rotation of the Cube by disabling the Sine_Cosine (Script), the Cube will remain in a fixed position.

disabling the Cube’s Script, Sine_Cosine, and it’s movement

When I press play and then pause. I select the Console tab and can see the angle is -113.1986 but previously I determined that the angle of Theta is approximately 66.80 degrees.

Debug.Log(“Angle: “ + angle); prints the scalar value to the console

The radians of the circle are included in the calculation. Starting from the x-axis being identified as the radius and then wrapping the radius around the circle in a clockwise direction until it reaches 180 degrees. When the remaining length of the radian, between negative y and the positive x are added together the result is -113.1986. To determine if the previous calculation of the angle was correct, 180 degrees can be subtracted by the number printed in the Console, resulting in a difference of 66.8014.

calculating the degrees based on Mathf.Rad2Deg()

If the cube remains fixed at this position, -113.1986 (i.e., 66.8014) degrees is the angle the cylinder will need to rotate.

Quaternions

Quaternions handles the rotation of objects in Unity. Using Quaternion, I can define rotation along the z-axis. To define the rotation of the cylinder on the z-axis, the method Quaternion.AngleAxis() is passed the parameters of the variable angle, which calculated the Tangent, and the Vector3 axis, which would be Vector3.forward.

Description of Quaternion.AngleAxis() — Unity documentation
Quaternion component named rotateOnAxis = Quaternion.AngleAxis(float angle, Vector3.axis)

Once the rotation has been defined using the calculation of the angle and a Vector3, I need to know my current rotation, this.transform.rotation , and then set it using the Quaternion.Slerp() method.

Quaternion.Slerp() — Unity documentation

Quaternion a is this.transform’s current rotation, a starting value. Quaternion b is the variable rotateOnAxis, and ending value. The t value is determined in real-time using Time.deltaTime.

setting the cylinder’s rotation as it calculates the angle towards the cube

Back in the Unity editor, if I press play, the cylinder will rotate but does not point directly at the cube. Why?

cylinder rotate 90 degrees beyond it’s target

The rotation was based on the angle calculated as -113,1986 degrees. The cylinder is offset by 90 degrees. To resolve this addition of 90 degrees to the rotation, subtract 90 degrees from the formula calculating the variable named angle. Now, I can see the cylinder, very quickly finds its target, the Cube.

a subtle change in direction by the cylinder as it rotates to find the cube

Even when I move the cube, the cylinder follows it.

cylinder finds the cube as I manually move it

Even when the Cube moves in a circle when I enable the Sine_Cosine (Script), the cylinder continues to try and find the target.

cylinder finds the cube as Sine_Cosine (Script) moves it

eulerAngles()

Another way to find and calculate an angle that is much easier than using Quaternions is to update the Euler angle on the z-axis.

If I comment out all of the code below my float angle variable, then get this.transform’s eulerAngles() method, I can get and set the rotation of the cylinder. First, I identify the axis I will rotate on using the Vector3.forward, i.e., z-axis, and then multiply that by variable angle, which is the calculation of the tangent.

syntax for eulerAngles() method
Vector3 of this.transform.eulerAngles(Vector3.axis * calculation of the angle)

Now when I move the cube in any position, the cylinder immediately begins to rotate towards the cube using only a few lines of code.

cylinder rotates towards the cube using eulerAngles() method

Applying Pythagoras’ Theorem for Directional Movement

Now that the cylinder can find the location of the cube by calculating the angle, I want it to move towards and/or follow the cube until it collides.

The distance between the cube and the cylinder can be calculated using Pythagoras’ theorem a2 + b2 = c2. The direction, i.e., vector, = ending position — starting position. If I use a graph to chart the vector, this could also be identified as the hypotenuse of a right triangle.

https://www.khanacademy.org/math/geometry/hs-geo-trig/xff63fac4:hs-geo-trig-ratios-similarity/a/opposite-adjacent-hypotenuse

If I look at a triangle and imagine the horizontal leg as the x-axis and the vertical leg as the y-axis, I could assign them integer values. For example, the distance of the cylinder from 0 on the x-axis spans the distance of 3 integers and the distance of the cylinder from 0 on the y-axis spans the distance of 7 integers. Using the x and y values, I can calculate the hypotenuse, i.e., the angle.

In the context of the previous use of the cylinder and cube, the cylinder positioned at 7 on the y-axis and 3 on the x-axis. If I perform Pythagoras’ theorem, (7 * 7) + (3 * 3) = 58 sq.root = 7.62, I discover that the distance of the cylinder from the cube is 7.62 meters away.

transform.Translate()

I will use the transform.Translate() method and add it as a component to the cylinder.

  • Create > C# Script. I name the script Calculate_Direction.
  • Attach the script to the Cylinder by dragging it into the Cylinder’s Inspector.
  • Open the script attached to the cylinder and add a reference data type, Transform, that identifies the target/destination the cylinder will move towards. In this case, the Cube is the target.
reference data type of target/ending position

Back in the Unity editor, I can drag and drop the Cube from the Hierarchy window to the reference provided in the cylinders Inspector.

drag and drop a reference of cube in Inspector of cylinder

Next, I…, the cylinder, will calculate the distance from my position and the Cube using Pythagoras theorem. To find the Vector3 direction = the ending position, _cube.position, must be subtracted by the starting position, — this.transform.position;. To check if the magnitude was calculated correctly, add a Debug.Log statement, (“Magnitude ” + the difference, i.e., direction.magnitude);.

When the application is run, the console prints the Magnitude of 7.61, which was the difference I calculated previously.

verification of the magnitude of the Vector

Now that the distance is calculated, actually moving toward the ending position is possible using this.transform.Translate() method. When I open the parentheses, I can see that the Translate method is looking for the Vector3 position, which is the variable named direction. To move at a rate based on real-time, I need to multiply direction by Time.deltaTime.

the transform.Translate(calculates the Vector and moves in real-time)

As I was testing the behavior, it became clear that the order of factors in the equation was causing the cylinder to follow a path that was odd. The cylinder was moving away from the cube before following the perimeter of a circle and then finally reaching it’s end position from beneath the cube.

subtracting the position of the cube from the position of the cylinder
wait for it … from below

By reversing the position of the factors, the cylinder moved directly towards the cube. Keep in mind, the order of operations when subtracting is extremely important when determining the difference, and in this case, the cylinders behavior.

reversing the order of factors

Mission accomplished! To summarize:

  • I began with the Mathf.Sin and Mathf.Cos to create a side-to-side or up-and-down movement with the Cube in Part 1.
  • Then combined Mathf.Sin and Mathf.Cos to create a circling movement with the Cube in Part 1.
  • Next, I used Mathf.Atan2 and Mathf.Rad2Deg to calculate the angle and then rotated the cylinder using Quaternion.angleAxis OR eulerAngles.
  • Finally, I used Pythagoras’ theorem, direction = ending position — starting position, to calculate the Vector between the cylinder and the Cube
  • then used the transform.Translate() method to move the cylinder towards the Cube’s position.

In upcoming stories I will see how I can implement the discoveries made in Part 1 (Sine and Cosine) and 2 (Tangent) of these stories to enhance Enemy behaviors and provide the Player with more challenging gameplay.

--

--

Brian David

an exploration of creativity and software development