Use when implementing game math — vectors, transforms, interpolation, curves, random number generation, and common geometric recipes
Scanned 6/3/2026
Install to Claude Code
npx -y skills add jame581/GodotPrompter --skill math-essentials --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Math Essentials?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/jame581-math-essentials)More formats (shields.io, HTML) on the badges page.
---
name: math-essentials
description: Use when implementing game math — vectors, transforms, interpolation, curves, random number generation, and common geometric recipes
---
# Game Math in Godot 4.3+
All examples target Godot 4.3+ with no deprecated APIs. GDScript is shown first, then C#.
> **Related skills:** **player-controller** for movement physics, **ai-navigation** for pathfinding math, **camera-system** for camera interpolation, **tween-animation** for easing curves, **physics-system** for collision math.
---
## 1. Vector Operations
### Essential Vector Methods
| Method | Returns | Description |
|-------------------------|-----------|-----------------------------------------------|
| `length()` | `float` | Magnitude of the vector |
| `length_squared()` | `float` | Squared magnitude (faster, skip sqrt) |
| `normalized()` | `Vector` | Unit vector (length 1) in the same direction |
| `distance_to(b)` | `float` | Distance between two points |
| `distance_squared_to(b)` | `float` | Squared distance (faster for comparisons) |
| `direction_to(b)` | `Vector` | Normalized direction from this to b |
| `angle_to(b)` | `float` | Angle in radians between two vectors |
| `angle_to_point(b)` | `float` | Angle from this point to b (2D) |
| `dot(b)` | `float` | Dot product |
| `cross(b)` | `float/Vector3` | Cross product (2D returns float, 3D returns vector) |
| `rotated(angle)` | `Vector2` | Rotated by radians (2D) |
| `move_toward(to, delta)` | `Vector` | Move toward target by at most delta |
| `clamp(min, max)` | `Vector` | Clamp each component |
| `snapped(step)` | `Vector` | Snap to grid |
| `reflect(normal)` | `Vector` | Reflect off a surface |
| `bounce(normal)` | `Vector` | Bounce off a surface (inverted reflect) |
| `slide(normal)` | `Vector` | Slide along a surface |
### Direction and Distance
```gdscript
# Get direction from A to B (normalized)
var dir: Vector2 = global_position.direction_to(target.global_position)
# Get distance
var dist: float = global_position.distance_to(target.global_position)
# Use squared distance for comparisons (faster — avoids sqrt)
if global_position.distance_squared_to(target.global_position) < detection_range * detection_range:
chase_target()
```
```csharp
Vector2 dir = GlobalPosition.DirectionTo(target.GlobalPosition);
float dist = GlobalPosition.DistanceTo(target.GlobalPosition);
if (GlobalPosition.DistanceSquaredTo(target.GlobalPosition) < detectionRange * detectionRange)
ChaseTarget();
```
### Dot Product
The dot product tells you how aligned two vectors are.
```gdscript
# Is the target in front of us? (dot > 0 = in front, < 0 = behind)
var forward: Vector2 = Vector2.RIGHT.rotated(rotation)
var to_target: Vector2 = global_position.direction_to(target.global_position)
var dot: float = forward.dot(to_target)
if dot > 0.7: # roughly within ~45° cone
print("Target is ahead")
elif dot < -0.7:
print("Target is behind")
```
```csharp
Vector2 forward = Vector2.Right.Rotated(Rotation);
Vector2 toTarget = GlobalPosition.DirectionTo(target.GlobalPosition);
float dot = forward.Dot(toTarget);
if (dot > 0.7f) GD.Print("Target is ahead");
```
### Cross Product (3D)
The cross product gives a vector perpendicular to two input vectors.
```gdscript
# Get the surface normal from two edge vectors
var edge1: Vector3 = vertex_b - vertex_a
var edge2: Vector3 = vertex_c - vertex_a
var normal: Vector3 = edge1.cross(edge2).normalized()
```
```csharp
Vector3 edge1 = vertexB - vertexA;
Vector3 edge2 = vertexC - vertexA;
Vector3 normal = edge1.Cross(edge2).Normalized();
```
---
## 2. Transforms
### Transform2D
A 2D transform holds position, rotation, and scale.
```gdscript
# Get the global transform
var xform: Transform2D = global_transform
# Convert between local and global space
var local_point: Vector2 = to_local(global_point)
var world_point: Vector2 = to_global(local_point)
# Apply transform to a point
var transformed: Vector2 = xform * Vector2(10, 0) # point in local space → global
# Inverse transform
var local: Vector2 = xform.affine_inverse() * global_point
```
```csharp
Transform2D xform = GlobalTransform;
Vector2 localPoint = ToLocal(globalPoint);
Vector2 worldPoint = ToGlobal(localPoint);
Vector2 transformed = xform * new Vector2(10, 0);
Vector2 local = xform.AffineInverse() * globalPoint;
```
### Transform3D & Basis
```gdscript
# Basis holds rotation and scale as 3 column vectors
var basis: Basis = global_transform.basis
# Forward direction (looking along -Z in Godot)
var forward: Vector3 = -basis.z
var right: Vector3 = basis.x
var up: Vector3 = basis.y
# Look at a target
look_at(target.global_position, Vector3.UP)
# Rotate around an axis
rotate_y(deg_to_rad(90.0))
rotate_object_local(Vector3.UP, deg_to_rad(45.0))
# Interpolate between two transforms (smooth transition)
var a: Transform3D = $Start.global_transform
var b: Transform3D = $End.global_transform
global_transform = a.interpolate_with(b, 0.5) # halfway
```
```csharp
Basis basis = GlobalTransform.Basis;
Vector3 forward = -basis.Z;
Vector3 right = basis.X;
Vector3 up = basis.Y;
LookAt(target.GlobalPosition, Vector3.Up);
RotateY(Mathf.DegToRad(90.0f));
Transform3D a = GetNode<Node3D>("Start").GlobalTransform;
Transform3D b = GetNode<Node3D>("End").GlobalTransform;
GlobalTransform = a.InterpolateWith(b, 0.5f);
```
---
## 3. Interpolation
### lerp — Linear Interpolation
```gdscript
# Interpolate between two values (t = 0.0 to 1.0)
var mid: float = lerp(0.0, 100.0, 0.5) # 50.0
var pos: Vector2 = lerp(start_pos, end_pos, 0.75) # 75% of the way
# Smooth following — lerp with delta for frame-rate independence
func _process(delta: float) -> void:
position = position.lerp(target_position, 5.0 * delta)
```
```csharp
float mid = Mathf.Lerp(0.0f, 100.0f, 0.5f);
Vector2 pos = startPos.Lerp(endPos, 0.75f);
public override void _Process(double delta)
{
Position = Position.Lerp(targetPosition, 5.0f * (float)delta);
}
```
> **Warning:** `lerp(a, b, speed * delta)` is frame-rate dependent and never fully reaches the target. For precise movement, use `move_toward()` instead.
### move_toward — Fixed-Speed Approach
```gdscript
# Move exactly `speed * delta` units toward target each frame
position.x = move_toward(position.x, target_x, speed * delta)
# Vector version
position = position.move_toward(target_position, speed * delta)
```
```csharp
float newX = Mathf.MoveToward(Position.X, targetX, speed * (float)delta);
Position = Position.MoveToward(targetPosition, speed * (float)delta);
```
### slerp — Spherical Interpolation
For smooth rotation interpolation (preserves arc, not straight line).
```gdscript
# Quaternion slerp for smooth 3D rotation
var current_quat: Quaternion = global_transform.basis.get_rotation_quaternion()
var target_quat: Quaternion = target_transform.basis.get_rotation_quaternion()
var result: Quaternion = current_quat.slerp(target_quat, 5.0 * delta)
global_transform.basis = Basis(result)
```
```csharp
Quaternion currentQuat = GlobalTransform.Basis.GetRotationQuaternion();
Quaternion targetQuat = targetTransform.Basis.GetRotationQuaternion();
Quaternion result = currentQuat.Slerp(targetQuat, 5.0f * (float)delta);
GlobalTransform = new Transform3D(new Basis(result), GlobalPosition);
```
### smoothstep — S-Curve Easing
```gdscript
# Returns 0.0 when x <= from, 1.0 when x >= to, smooth curve between
var t: float = smoothstep(0.0, 10.0, distance) # 0→1 as distance goes 0→10
# Useful for soft thresholds (fog density, volume falloff)
var fog_intensity: float = smoothstep(50.0, 100.0, camera_distance)
```
### cubic_interpolate — Smooth Path Following
```gdscript
# Smooth interpolation using 4 control points (catmull-rom style)
var point: Vector2 = p1.cubic_interpolate(p2, p0, p3, t)
# p0 = before start, p1 = start, p2 = end, p3 = after end
```
### Interpolation Comparison
| Function | Speed | Reaches Target | Smooth | Use For |
|--------------------|----------------|----------------|--------|----------------------------|
| `lerp(a, b, t)` | Variable | Only at t=1 | Yes | UI transitions, blending |
| `move_toward()` | Constant | Yes | No | Movement, timers |
| `slerp()` | Variable | Only at t=1 | Yes | Rotation blending |
| `smoothstep()` | S-curve | Soft threshold | Yes | Fog, volume, thresholds |
| `cubic_interpolate()` | Variable | Only at t=1 | Very | Paths, camera rails |
---
## 4. Curves and Paths
`Curve` resource for value-over-time (e.g., damage falloff curves). `Path2D` / `Path3D` for spatial paths sampled by `PathFollow2D` / `PathFollow3D` — use for moving platforms, missile guidance, camera rails.
> See [references/curves-and-paths.md](references/curves-and-paths.md) for `Curve` setup, Path nodes, PathFollow properties.
---
## 5. Random Number Generation
Global functions (`randf()`, `randi() % N`, `randf_range(a, b)`) for one-shot randomness. `RandomNumberGenerator` for seeded, reproducible randomness (procgen, replay, save-state). Weighted selection via cumulative-sum or alias method.
> See [references/random-numbers.md](references/random-numbers.md) for full GDScript on each pattern, plus `FastNoiseLite` for procedural generation noise.
---
## 6. Common Game Math Recipes
Five recipes: **look at target** (2D `Vector2.angle_to_point`), **orbit around a point** (polar coordinates), **sine-wave bob** (floating UI elements, treasure), **angle wrapping** (-PI..PI canonicalization), **clamped approach with deadzone** (analog input + small-input ignore).
> See [references/game-math-recipes.md](references/game-math-recipes.md) for ready-to-use code on each recipe.
---
## 7. Common Pitfalls
| Symptom | Cause | Fix |
|--------------------------------------|----------------------------------------------|------------------------------------------------------------------|
| `lerp` never reaches target | Using `lerp(a, b, speed * delta)` each frame | Use `move_toward()` for exact arrival |
| Rotation jumps at 180° | Using `lerp` instead of `lerp_angle` | Always use `lerp_angle()` for angle interpolation |
| Object faces wrong direction (3D) | Forgot Godot uses -Z as forward | Forward direction is `-global_transform.basis.z` |
| Distance check too slow | Calling `distance_to` on many objects | Use `distance_squared_to` and compare against `range * range` |
| Normalized zero vector crashes | Calling `normalized()` on `Vector2.ZERO` | Check `length() > 0` first, or use `direction_to()` |
| Transform interpolation looks wrong | Lerping euler angles instead of quaternions | Use `Quaternion.slerp()` or `Transform3D.interpolate_with()` |
| Random results repeat after restart | Using `RandomNumberGenerator` with fixed seed | Godot 4.x auto-seeds global RNG; for `RandomNumberGenerator` use `randomize()` or set `seed` |
| Noise values are all ~0 | `frequency` too low | Increase `FastNoiseLite.frequency` (try 0.01–0.1) |
---
## 8. Implementation Checklist
- [ ] Distance comparisons use `distance_squared_to()` for performance
- [ ] Angle interpolation uses `lerp_angle()`, not `lerp()`
- [ ] 3D forward direction is `-transform.basis.z`, not `+z`
- [ ] `move_toward()` is used when exact arrival at target is needed
- [ ] `lerp(a, b, speed * delta)` is understood as frame-rate dependent smooth following, not exact movement
- [ ] `RandomNumberGenerator` is used for deterministic/seeded randomness (procedural generation, replays)
- [ ] Noise-based generation uses `FastNoiseLite` with appropriate frequency and seed
- [ ] Weighted random selection is used for loot tables and probability-based systems
- [ ] Path following uses `PathFollow2D/3D` with `progress` or `progress_ratio`
- [ ] Quaternion slerp is used for 3D rotation interpolation instead of euler angles
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!