Sine, Cosine, & Tangent in Unity: Moving Game Objects (Part 2)
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.
- 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
.
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”.
- First, in
Update()
, I need to know the direction. Thedirection
is the sum of the destination, which is the cube’s.position
subtracted by the cylinders (this.transform.
)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 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.
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
.
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.
When I run the application, in the Console I can see the value of the angle continuously changes as the cube oscillates.
If I stop the rotation of the Cube by disabling the Sine_Cosine (Script), the Cube will remain in a fixed position.
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.
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.
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
.
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 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
.
Back in the Unity editor, if I press play, the cylinder will rotate but does not point directly at the cube. Why?
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.
Even when I move the cube, the cylinder follows 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.
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.
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.
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.
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.
Back in the Unity editor, I can drag and drop the Cube from the Hierarchy window to the reference provided in the cylinders Inspector.
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.
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
.
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.
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.
Mission accomplished! To summarize:
- I began with the
Mathf.Sin
andMathf.Cos
to create a side-to-side or up-and-down movement with the Cube in Part 1. - Then combined
Mathf.Sin
andMathf.Cos
to create a circling movement with the Cube in Part 1. - Next, I used
Mathf.Atan2
andMathf.Rad2Deg
to calculate the angle and then rotated the cylinder usingQuaternion.angleAxis
OReulerAngles
. - 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.