Math
import "ghost:math"
Maths a game needs on nearly every frame — square roots, rounding, angles, interpolation — all come from Ghost's own math module, imported as ghost:math. This page is the part of it a game reaches for most; Math and Arrays document the whole thing.
Angles
Lumen's rotations are all in radians.
angle = math.atan2(target.y - player.y, target.x - player.x)
bullet.vx = math.cos(angle) * speed
bullet.vy = math.sin(angle) * speed
math.atan2(y, x) is correct in all four quadrants, which math.atan() cannot be. math.angle(x1, y1, x2, y2) is the same thing given two points instead of a difference, and math.degrees() converts for the places a game thinks in degrees — source.setPosition(), for one.
Following and bounds
function update(dt) {
camera.x = math.lerp(camera.x, player.x - window.width / 2, 8 * dt)
camera.x = math.clamp(camera.x, 0, map.pixelWidth - window.width)
}
math.lerp(from, to, amount) is how a camera follows smoothly rather than snapping. math.clamp(value, low, high) keeps it inside the map, and a health bar between empty and full. math.smoothstep(low, high, value) is lerp with easing at both ends.
Tiles and distance
tileX = math.floor(player.x / tileSize)
if (math.distance(player.x, player.y, enemy.x, enemy.y) < 64) {
enemy.chase(player)
}
math.floor(), math.ceil(), math.round(), and math.truncate() all answer with whole numbers, so they can index a tile map directly. math.round(n, places) takes an optional number of decimal places.
Randomness
| Method | Returns |
|---|---|
math.randomInt(n) | A whole number from 1 to n, both ends included. |
math.randomInt(low, high) | A whole number from low to high, both ends included. |
math.randomSeed(n) | Seeds the generator. |
math.noise(x, [y]) | Smooth noise for the given coordinate, between 0 and 1. |
random.random() | A decimal, from the same generator. |
Lumen seeds the generator with 1 before your game runs. A procedural level therefore looks the same every time it is opened, which is what makes it something to iterate on rather than something that shifts underfoot. Call math.randomSeed() yourself for a different world each run:
function load() {
math.randomSeed(timer.getTime()) // a new world every launch
}
math and random modules share one generator, so Lumen's seed governs random.random() too, and either module's seed call reseeds both.noise() is what you want when variation should be smooth rather than jittery — terrain height, cloud cover, a torch flicker. Neighbouring inputs give neighbouring outputs, which is exactly what randomInt() does not do, and the same input always gives the same result.
height = math.noise(x * 0.05) * 40
Everything else
Ghost's math module also carries statistics, whole-number mathematics, and linear algebra — vectors, matrices, dot and cross products, and solving systems. Vector maths in particular is worth knowing about for movement and collision:
direction = math.normalize([target.x - player.x, target.y - player.y])
player.x = player.x + direction[0] * speed * dt
player.y = player.y + direction[1] * speed * dt