Trigonometry for Game Development
Radians vs Degrees
JavaScript's Math.sin(), Math.cos(), and Math.atan2() all work in radians, not degrees. One full rotation is 2 * pi radians (approximately 6.283), which equals 360 degrees. Half a rotation is pi radians (approximately 3.14159), which equals 180 degrees. A right angle is pi / 2 radians, which equals 90 degrees.
To convert degrees to radians, multiply by pi and divide by 180. To convert radians to degrees, multiply by 180 and divide by pi. These conversions are so common in game code that most developers write utility functions: function toRad(deg) { return deg * Math.PI / 180; } and function toDeg(rad) { return rad * 180 / Math.PI; }. Use radians internally for all math operations and convert to degrees only for display or configuration values where degrees are more intuitive for designers.
A common bug is mixing radians and degrees. If you pass 90 (meaning 90 degrees) directly to Math.sin(90), you get 0.894 instead of the expected 1.0, because JavaScript interprets 90 as 90 radians (roughly 14 full rotations plus 54 degrees). Always convert degrees to radians before passing them to trigonometric functions. Framework APIs vary: Phaser uses degrees in many rotation methods while Three.js uses radians. Check the documentation for each function you call.
The Unit Circle and Core Functions
The unit circle is a circle with radius 1 centered at the origin. For any angle theta measured counterclockwise from the positive x-axis, cosine gives the x-coordinate on the unit circle and sine gives the y-coordinate. This relationship is the foundation of all game trigonometry.
Cosine (cos) returns the horizontal component. At 0 degrees (pointing right), cos is 1. At 90 degrees (pointing up), cos is 0. At 180 degrees (pointing left), cos is -1. At 270 degrees (pointing down), cos is 0. Cosine oscillates between -1 and 1 as the angle increases, producing the x-coordinate of circular motion.
Sine (sin) returns the vertical component. At 0 degrees, sin is 0. At 90 degrees, sin is 1. At 180 degrees, sin is 0. At 270 degrees, sin is -1. Sine also oscillates between -1 and 1 but is shifted 90 degrees from cosine. Together, (cos(theta), sin(theta)) traces out a perfect circle as theta increases.
In screen coordinates where y increases downward (the standard for Canvas and DOM), an angle of 0 radians points right, pi/2 radians points down, pi radians points left, and 3*pi/2 radians points up. This is the opposite of the mathematical convention where positive angles go counterclockwise. Some developers negate the y component to match mathematical convention; others work directly in screen coordinates. Either approach works as long as you are consistent throughout your codebase.
Finding Angles with atan2
Math.atan2(y, x) is the most important trigonometric function in game development. Given a direction vector (or the difference between two positions), it returns the angle in radians. Unlike Math.atan(y/x), atan2 handles all four quadrants correctly and never divides by zero. It returns values from -pi to pi.
The canonical use case is aiming. If a turret is at position (tx, ty) and the player is at (px, py), the angle the turret needs to face is Math.atan2(py - ty, px - tx). Note the parameter order: y first, then x. This is a frequent source of bugs when developers assume x comes first. The result is in radians, measured counterclockwise from the positive x-axis (pointing right).
To rotate a sprite to face a target in Phaser, set sprite.rotation = Math.atan2(target.y - sprite.y, target.x - sprite.x). If your sprite's default orientation points upward instead of rightward, add or subtract pi/2 to compensate. In Three.js, use object.lookAt(target) instead of computing angles manually, as the engine handles the 3D rotation internally.
To determine the angle between two direction vectors (not positions), normalize both vectors and use Math.atan2(cross, dot) where cross is the 2D cross product (a.x * b.y - a.y * b.x) and dot is the dot product. This gives a signed angle: positive for counterclockwise, negative for clockwise. The unsigned angle is simply Math.acos(dot(a, b)) when both vectors are normalized, but this loses the sign information.
Rotating Points and Objects
To rotate a point (x, y) around the origin by angle theta, apply the rotation formulas: new x = x * cos(theta) - y * sin(theta), new y = x * sin(theta) + y * cos(theta). These two lines of code are the complete 2D rotation operation. Compute sin(theta) and cos(theta) once and reuse them for both formulas to avoid redundant trigonometric calculations.
To rotate around an arbitrary center point (cx, cy), first translate the point so the center is at the origin by subtracting cx and cy, apply the rotation, then translate back by adding cx and cy. In code: translate the point to (x - cx, y - cy), apply the rotation formulas, then add (cx, cy) to the result. This three-step process (translate, rotate, translate back) is used for rotating weapons around characters, orbiting moons around planets, and spinning UI elements around pivot points.
Incremental rotation (adding a small angle each frame) accumulates floating-point error over time. After thousands of frames, a rotating object may drift off its intended circle or change radius slightly. The fix is to store the total angle and recalculate position from scratch each frame: x = cx + radius * Math.cos(totalAngle); y = cy + radius * Math.sin(totalAngle);. This recalculation eliminates accumulated error and keeps the object on a perfect circle indefinitely.
Circular Motion and Orbits
Circular motion uses the parametric circle equations: x = centerX + radius * Math.cos(angle) and y = centerY + radius * Math.sin(angle). Incrementing the angle each frame by a constant angular velocity produces uniform circular motion. Increasing the angular velocity produces faster orbits. Modulating the radius over time creates spiral motion. Using different radii for x and y creates elliptical orbits.
Satellite objects orbiting a moving parent use the same formulas, but the center point is the parent's current position. This creates orbiting bullets, shield particles, decorative halos, and planetary systems. For multiple objects orbiting the same center, offset their starting angles evenly: for N objects, space them 2*pi/N radians apart. Three objects get angles 0, 2.09, and 4.19 radians, distributing them evenly around the circle.
Spiral motion multiplies the radius by a time-dependent factor. Increasing the radius linearly over time creates an Archimedean spiral (constant gap between loops). Increasing the radius exponentially creates a logarithmic spiral (gaps widen as the spiral expands). Both patterns appear in bullet-hell games, particle effects, and procedural decoration.
Wave Patterns and Oscillation
Sine waves create smooth, repeating oscillation. The formula amplitude * Math.sin(time * frequency + phase) produces a value that oscillates between -amplitude and +amplitude. Amplitude controls how far the oscillation swings. Frequency controls how fast it oscillates (higher frequency means more oscillations per second). Phase shifts the starting point of the wave.
Floating platforms bob up and down using baseY + amplitude * Math.sin(time * speed). An amplitude of 20 pixels and a speed of 2 produces a gentle bob. Increase speed for rapid vibration, increase amplitude for dramatic swoops.
Pulsing effects modulate scale, opacity, or color intensity with a sine wave. To pulse between 0.8 and 1.0 scale: 0.9 + 0.1 * Math.sin(time * 3). The 0.9 is the midpoint, 0.1 is the half-amplitude, and 3 is the speed. This formula maps the -1 to 1 sine range to the 0.8 to 1.0 output range.
Zigzag projectiles combine linear motion on one axis with sine motion on the perpendicular axis. A bullet moving rightward with vertical sine oscillation travels in a smooth wave pattern. Adding a second sine wave with a different frequency creates more complex organic motion that looks less predictable and more threatening.
Screen shake uses random-amplitude sine waves on both x and y axes with rapid decay. Start with a large amplitude and multiply it by a decay factor (like 0.9) each frame. The rapid oscillation combined with shrinking amplitude creates a convincing impact effect that settles naturally.
Projectile Arcs
A projectile launched at angle theta with initial speed v follows a parabolic arc. The horizontal velocity is v * Math.cos(theta) and the vertical velocity is v * Math.sin(theta). Each frame, the horizontal position increases by the horizontal velocity (constant, assuming no air resistance), and the vertical velocity decreases by gravity. In screen coordinates where y increases downward, gravity adds to the vertical velocity each frame: vy += gravity * deltaTime.
To aim a projectile to hit a target at a given distance and height difference, you solve for the launch angle. For flat ground (same height), the optimal angle for maximum range is 45 degrees. For a target below the launcher, the optimal angle is less than 45 degrees. For a target above, it is greater. Most games skip the exact solution and instead let the player aim manually, or use iterative approximation to find a launch angle that lands close enough to the target.
Use atan2(y, x) to find angles between points. Use cos and sin with the rotation formulas to rotate positions. Use parametric circle equations for orbital motion. Use sine waves for oscillation. These four patterns cover nearly every trigonometry use case in 2D game development.