Skip to content
Back to skills

Math Essentials

ASecurity

Use when implementing game math — vectors, transforms, interpolation, curves, random number generation, and common geometric recipes

  • 786 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added June 3, 2026
developmentgoc#noderailsapiperformance

Works with

  • api

Security analysis

A100/100

Pro scans all 4 files and shows the line behind each finding

Scanned June 3, 2026

npx -y skills add jame581/GodotPrompter --skill math-essentials --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Math Essentials?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Math Essentials
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/jame581-math-essentials/badge)](https://www.skillsdirectory.com/skills/jame581-math-essentials)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
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

Files in this skill

  • SKILL.md12.4 KB
  • references/curves-and-paths.md1.9 KB
  • references/game-math-recipes.md2.7 KB
  • references/random-numbers.md2.7 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.