Appearance
Rotations: Euler & quaternions
Galatrix speaks two rotation languages, and every rotation you'll ever touch is one of them:
- Euler angles — three numbers in degrees,
[x, y, z]: "tilt 30° on X, turn 45° on Y". This is the human language: the Inspector shows it, the terminal prints it, the animation timeline is authored in it, and[0, 90, 0]obviously means "quarter-turn about Y". - Quaternion — four numbers
[x, y, z, w]. This is the math the engine actually runs on: it composes cleanly, blends smoothly (slerp), and never gimbal-locks. It is whatentity.rotationreads back, what the map file stores, and what physics uses. The trade-off: the numbers mean nothing to a human — you never type one by hand, you build it with a helper.
You don't have to pick one. The API takes both and tells them apart by shape.
Writing a rotation: the 3-vs-4 rule
Every rotation write in the script API — entity.rotation, entity.eulerAngles, this.transform.rotation, Instantiate(...)'s rotation, physics.warpBody(...)'s rotation, camera.setRotation(...) — follows one rule:
| You write | It means |
|---|---|
3 values — [x, y, z], a Vector3, or {x, y, z} | Euler angles in degrees |
4 values — [x, y, z, w], a Quaternion, or {x, y, z, w} | a quaternion (re-normalized for you if its length drifted) |
js
this.entity.rotation = [0, 45, 0] // 3 values → Euler degrees: 45° about Y
this.entity.rotation = { x: 30, y: 0, z: 0 } // same idea, object form
this.entity.rotation = Quaternion.Euler(0, 45, 0) // 4 values → the same rotation, as a quaternion
this.entity.eulerAngles = [0, 90, 0] // explicit Euler property (Unity's eulerAngles)Three numbers are never a valid quaternion, so there's no ambiguity — the natural write does the natural thing. Euler angles apply in Unity's order (Z, then X, then Y), so values copied from a Unity project rotate the same way here.
Degrees, not radians
Everything Euler in Galatrix is degrees — script writes, the Inspector, the terminal, animation keys, rotationDeg in AI/terminal ops. If something turns ~57× too far, you passed radians.
Reading a rotation
Reads give you the quaternion — with an escape hatch back to degrees:
js
const q = this.entity.rotation // Quaternion [x, y, z, w] (indexable like an array)
const e = this.entity.eulerAngles // Vector3 of degrees, e.g. [0, 45, 0]Two things to know about reading Euler angles back:
- The degrees you read are equivalent, not always identical, to what was written — one rotation has many Euler spellings (
[180, 0, 180]is the same turn as[0, 180, 0]). Compare rotations withQuaternion.Angle(a, b), not by comparing Euler triples. - Often you don't want angles at all — you want a direction.
entity.forward/right/upare unit vectors already rotated into world space, ready for raycasts, aim checks, or movement:
js
const aim = this.entity.forward // where this entity points (-Z rotated)
const hit = this.physics.raycast(this.entity.position, aim, 50)this.transform.eulerAngles is the world rotation (it walks the parent chain, like Unity); entity.rotation / entity.eulerAngles are the entity's own transform (parent-local on a child).
Turning things — the recipes
Spin forever — rotate() applies an Euler-degrees delta (world-relative), so scale it by deltaTime:
js
onUpdate() {
this.entity.rotate([0, 90 * this.time.deltaTime, 0]) // 90°/s about Y
}Snap to face a point — lookAt aims the entity's forward (-Z) at a world position:
js
this.entity.lookAt(this.game.findEntity('Player').position)(For "always face X" with no script at all, use the Look At component.)
Turn smoothly toward a target — build the goal rotation, then close the angle at a fixed degrees-per-second with RotateTowards (or ease with Slerp):
js
onUpdate() {
const to = this.game.findEntity('Player').position
const from = this.entity.position
const goal = Quaternion.LookRotation([to[0] - from[0], 0, to[2] - from[2]]) // yaw only: flatten Y
this.entity.rotation = Quaternion.RotateTowards(this.entity.rotation, goal, 180 * this.time.deltaTime)
}Face where you're moving — LookRotation on the velocity:
js
const v = this.physics.getVelocity(this.entity.id)
if (Math.hypot(v[0], v[2]) > 0.1) this.entity.rotation = Quaternion.LookRotation([v[0], 0, v[2]])Compose rotations — quaternion multiply applies right-to-left (a.mul(b) = "b first, then a"):
js
const lean = Quaternion.AngleAxis(15, [0, 0, 1]) // 15° roll
this.entity.rotation = this.entity.rotation.mul(lean) // lean on top of the current facingThe Quaternion toolbox
You never write [x, y, z, w] by hand — build and blend with these (all on the global Quaternion, same names as Unity):
| Helper | Gives you |
|---|---|
Quaternion.Euler(x, y, z) | rotation from Euler degrees |
Quaternion.AngleAxis(deg, axis) | deg degrees around an axis |
Quaternion.LookRotation(forward, up?) | rotation whose forward is a direction |
Quaternion.FromToRotation(a, b) | the turn that carries direction a onto b |
Quaternion.Slerp(a, b, t) / Lerp | blend between two rotations (use for smoothing — never lerp raw Euler numbers across 180°) |
Quaternion.RotateTowards(a, b, maxDeg) | step toward b, at most maxDeg |
Quaternion.Angle(a, b) | degrees between two rotations |
Quaternion.Inverse(q) / Quaternion.identity | the opposite turn / no turn |
q.mulVec3(v) | rotate a vector (q * v in Unity) — turn local offsets into world ones |
q.eulerAngles | read any quaternion back as degrees |
Physics owns dynamic bodies
On an entity with a dynamic rigidBody, the simulation drives the transform — a plain entity.rotation = … write is overwritten by the next physics step (same as Unity). Rotate a dynamic body through physics instead:
js
this.physics.setAngularVelocity(this.entity.id, [0, 3, 0]) // spin it (rad/s)
this.physics.applyTorqueImpulse(this.entity.id, [0, 5, 0]) // kick it
this.physics.warpBody(this.entity.id, [0, 3, 0], [0, 45, 0]) // teleport to a pose (3 values = Euler deg)Kinematic and script-moved entities are yours: write entity.rotation freely and the collider follows.
Where each language shows up
| Surface | Speaks |
|---|---|
| Inspector rotation fields | Euler degrees |
Terminal — get Box.rotation | Euler degrees (labeled (euler deg)) |
Terminal — set Box.rotation = 0 45 0, rotate Box 0 45 0 | Euler degrees (absolute) |
AI / terminal ops (spawn, modify) | rotationDeg — Euler degrees |
| Animation timeline rotation tracks | Euler degrees per key |
Script reads (entity.rotation) | quaternion (.eulerAngles for degrees) |
| Script writes | both — 3 values = Euler degrees, 4 = quaternion |
Map file (components.transform.rotation) | quaternion — edit via the tools above, not by hand |
See also
entity,Quaternion, andphysicsin the Script API reference.- Transform constraints — face/follow a target with no script.
- Scripting basics — the Behaviour lifecycle these snippets run in.