Skip to content

UI & 2D games

Galatrix is a 3D engine that also makes 2D games and on-screen UI first-class — the Unity model, where the same editor builds both. This guide covers sprites (the building block of a 2D game), the two UI systems (a visual Canvas you design by hand, and a script-driven HUD), and the shared pieces they lean on — fonts, masks, and the Canvas Scaler that keeps a layout looking right on every screen.

Open the editor with ?example=uidemo for a ready-made canvas to poke at.

Everything here works in all four runtimes — the editor play-test, a published multiplayer game, and a standalone build all render the same UI and sprites from the same map data.

Sprites — the 2D building block

A sprite is a flat, unlit, textured quad that always faces the camera (a billboard). Add one from the Hierarchy + → Sprite, or just drag a texture from the Project panel into the viewport — that drops a sprite entity at the point you release, named after the texture.

Select a sprite and the Inspector's Sprite (2D) section configures it:

  • Texture — a block texture or any imported texture asset (the Tint multiplies it).
  • Opacity, Flip X / Y, Billboard (face the camera — turn it off to lock the sprite to its transform rotation, e.g. a floor decal), and Sorting Order (see sorting).
  • PPU and Pivot — how the quad is sized and where it sits (below).
  • Sheet Cols / Rows / Frame — sprite-sheet framing, for animation.

PPU and pivot

By default a sprite is a 1×1 quad sized by the entity's Scale. Set PPU (Pixels Per Unit, Unity's import default is 100) and the quad instead sizes itself from the texture's pixel dimensionsworldSize = pixels ÷ PPU — with Scale multiplying on top. A 64×32 texture at PPU 100 becomes exactly 0.64×0.32 world units, so all your art shares one consistent scale.

Pivot [x, y] (0–1) picks which point of the sprite sits at the entity position. The default [0.5, 0.5] is centered; [0.5, 0] puts the position at the bottom-center — the classic "feet on the ground" setup for a 2D character, so moving the entity moves its feet.

Sheet sprites size from one frame's pixels, not the whole atlas.

Sprite animation

A sprite sheet is a grid of equal frames in one texture. Set Sheet Cols × Rows on the Sprite (2D) component and it shows one cell at a time; Frame picks which (frame 0 is the top-left, counting left-to-right, top-to-bottom).

For flipbook animation add a Sprite Animator component. It holds named clips, each a frame range:

FieldMeaning
nameclip name (scripts switch by this)
from / tofirst and last frame (from > to plays backwards)
fpsframes per second
looploop (default) or hold the last frame

The animator plays clips locally in every runtime. A script switches clips with setComponent:

js
class PlayerAnim extends Behaviour {
  onUpdate() {
    const moving = /* … */ false
    this.entity.setComponent('spriteAnimator', { play: moving ? 'run' : 'idle' })
  }
}

Runtime playback state lives outside the saved data, so playing an animation never dirties your map.

The 2D scene view

The toolbar's 2D button (next to Snap) swaps the viewport to an orthographic front view of the XY plane — the wheel zooms, middle/right-drag pans. Selection, the gizmos, and the measure tool all follow the 2D camera; toggle back and the 3D orbit camera is exactly where you left it. Use it to lay out a 2D scene without perspective getting in the way.

2D physics

For a side-scroller you want bodies that only move in the XY plane. Add a Rigid Body and click the ⬛ 2D Body (lock to XY plane) preset — it freezes Z position and X/Y rotation so a body falls, slides, and stacks in 2D while staying upright.

Sprite sorting

Sprites don't write depth, so overlapping sprites stack by Sorting Order (higher draws on top) rather than z-fighting by camera distance — Unity's sprite model. World geometry still occludes sprites normally (they depth-test), so a sprite behind a wall is hidden.

Texture filtering

Each texture asset has a Filtering mode (in its Project-panel inspector): Point (default — crisp, nearest-neighbor, right for pixel art) or Smooth (bilinear + mipmaps, right for photos and painted art). It applies everywhere the texture is used.

Tilemaps

A tilemap builds a 2D level out of a grid of repeating tiles taken from a single sprite sheet — the Unity/2D way to make a platformer or top-down map without placing hundreds of individual objects. Open the Tilemap window (Windows → Tilemap), click + New Tilemap, and pick a Tileset (any imported image that is a grid of tiles) plus its Sheet dimensions (columns × rows) and cell size (world units per tile).

Painting

Pick a tool, then left-drag in the viewport to paint onto the tilemap's plane:

  • 🖌 Paint — stamp the selected tile (brush size 1–16 cells).
  • 🧽 Erase — clear cells back to empty.
  • 🪣 Fill — flood-fill a contiguous region with the selected tile.
  • 💧 Pick — sample the tile under the cursor into the palette.

The palette shows every tile in the sheet: click to select one to paint; shift-click to mark a tile solid (it turns red). Painting builds a single merged mesh with per-tile UVs, so a big level is still one draw call. The whole grid is stored compactly (run-length-encoded per chunk) and rides in the map — nothing is written to disk as separate files.

Solid tiles and collision

A tile only collides if you mark it solid. At play time the solid cells are greedy-merged into as few box colliders as possible, so the player stands on ground/platform tiles and bumps walls, while decorative tiles (coins, foliage, background) render but never block. Collision is identical in the editor play-test, in standalone builds, and in multiplayer — the server builds the same boxes from the same shared code, so what you paint is what everyone collides with. A tilemap is a normal object in the Hierarchy: move it to place the whole level, and a map can hold several.

For a true 2D feel, pair a tilemap with an orthographic camera and a characterController player whose Z is pinned to 0 (see the 2D Platformer Kit). Load the worked example with ?example=tilemap: a side-view level whose floor, platforms, and walls are all tiles, with solid ground and decorative coins/bushes, and a small editable Platformer script driving a z-locked player.

With the AI editor

The assistant can build tilemaps too: createTilemap (name, position, tileset, sheet dimensions, cell size, and which tile indices are solid) then paintTiles (fill a rect of cells or an explicit cells list with a tile, or erase). For example, "make a 2D platformer floor with stone walls" creates the tilemap and paints the ground and walls in cell coordinates.

Two ways to build UI

Galatrix has two UI systems. They can coexist in one game:

Visual CanvasScript HUD
Built withthe UI Editor (drag & drop) + Hierarchyscript calls (ui.createX)
Lives asuiCanvas + uiElement entities in the mapruntime-only, created by a script
Best forHUDs you design once and placedynamic UI (scores, inventories, dialogs that change)
Interactionwire a control to a script functioncallbacks in the options (onClick, onValueChanged)

Both render on top of the game, scale identically (see Canvas Scaler), and work in every runtime.

The visual Canvas

Create a Canvas from the Hierarchy + → UI → Canvas, then add elements under it — Label, Panel, Bar, Image, Button, Toggle, Slider, Text Input, Dropdown. Design them in the UI Editor window (drag to move, handles to resize) and fine-tune in the Inspector. It's a WYSIWYG layout: what you place is what players see.

Anchors — position and stretch

Every element has a position (0–1 of the screen) and an anchor — the pivot the position refers to. The Inspector's Anchors grid is Unity's 3×3 preset picker plus a stretch row and column:

  • A point anchor pins the element at a screen fraction — top-left, center, bottom-right, etc. It keeps its fixed size as the window resizes.
  • A stretch anchor ties the element's edges to the screen so it grows and shrinks with the viewport. When stretched, the Margins fields (Left / Top / Right / Bottom, in px) inset it from those edges.

Each axis is independent, so a bottom bar can stretch across the full width (Stretch X) while staying a fixed height pinned to the bottom (point Y).

Interactive controls

Buttons and the value controls (slider, toggle, text input, dropdown) can drive your game. In the Inspector's Action row set:

  • Function — the script function to call. A button calls it on click; a control calls it on value change, passing the new value (a number for slider/dropdown, boolean for toggle, string for input).
  • On Entity — the entity whose script has that function (leave empty to call it on every script in the scene, like Unity's BroadcastMessage).
js
class Shop extends Behaviour {
  onBuy() { /* the Buy button's Function */ }
  onVolume(v) { /* the volume slider's Function — v is the new 0..1 value */ }
}

Actions run where scripts run: locally in the editor/standalone, on the server in multiplayer (so the UI stays authoritative).

Fonts, wrapping, masks

  • Labels take a Font (see Fonts), and a Wrap toggle flows long text onto multiple lines within a width instead of one clipped line.
  • Images take a MaskCircle (a round avatar/minimap crop) or Rounded (corner radius) — and stretch to fill their frame.

Authoring aids

The UI Preview window (the visual editor) has a few Unity-style helpers:

  • Multi-resolution preview — a device dropdown (Phone / Tablet / Desktop / …) reshapes the preview to that aspect so you can see how the Canvas Scaler and stretch anchors respond, without changing the Canvas's own resolution.
  • Smart guides — dragging an element snaps it to the canvas thirds/edges and to other elements, drawing a guide line; hold Alt to move freely.
  • Align & Distribute — multi-select 2+ elements and use the Align strip (X/Y Min·Center·Max), or Distribute (3+) for even spacing.
  • UI prefabs — select an element and Save as Prefab (Inspector); re-stamp it into any Canvas from the Hierarchy's + → UI → UI Prefabs. Children, controls, and action wiring all come along.

The script HUD

For UI that changes at runtime, scripts build it with the ui API. Each create… returns a handle to update or remove the element later.

js
class HUD extends Behaviour {
  onStart() {
    this.score = ui.createLabel({ text: 'Score: 0', position: [0.5, 0.05], fontSize: 32 })
    this.hp = ui.createBar({ value: 1, position: [0.05, 0.05], anchor: [0, 0], barColor: '#f38ba8' })
    ui.createButton({ text: 'Jump', position: [0.9, 0.9], onClick: () => this.jump() })
  }
  addPoint() { this.points++; this.score.setText(`Score: ${this.points}`) }
}

The factories:

CallMakes
ui.createLabeltext (supports wrap + maxWidth, fontFamily, bestFit)
ui.createBara progress / health bar (value 0–1)
ui.createPanela background panel
ui.createImagean image from a project texture ({ texture: 'Coin' }, plus mask)
ui.createButtona button (onClick)
ui.createSlider / createScrollbar / createToggle / createInput / createDropdownvalue controls (onValueChanged)
ui.createContainera layout group (Unity LayoutGroup + ScrollRect); .add(...children) nests elements

A scrollbar (ui.createScrollbar) is a draggable sized handle in a track — Unity's Scrollbar. Its onValueChanged fires with a 0..1 position as you drag; handleSize sets the handle's fraction of the track and direction picks the axis ('horizontal' / 'vertical').

Best Fit (ui.createLabel({ bestFit: true, width, height })) shrinks the font so the text fits its box — Unity Text's auto-size. It needs a bounded box, so give the label a width and height (or, on the Canvas, a fixed-size / stretched frame); the font size becomes the ceiling and bestFitMin the floor.

ui.showMessage(text) shows a quick floating message. See the Script API reference for every option.

Images by name, and masks

ui.createImage({ texture }) takes a texture asset by name (or id) — the client resolves it to the image locally, so multiplayer never streams the pixels through the HUD. Add mask: 'circle' for a round crop or mask: 'rounded' (with borderRadius) for rounded corners.

Containers and clipping

A container arranges its children (vertical / horizontal / grid), optionally scrolls them (scroll: 'y'), and can clip them to its box (clip: true — Unity's RectMask2D), which composes with borderRadius for a rounded window.

Pointer & drag events

Any HUD handle takes an onPointer callback — Unity's EventTrigger for the script HUD. It fires for the pointer's whole lifecycle over the element: enter, exit, down, up, click, and a drag cycle beginDragdragendDrag, plus drop on the element the pointer is released over (for drag-and-drop). A press that never moves past a small threshold stays a plain click.

js
onStart() {
  const slot = ui.createPanel({ position: [0.5, 0.5], width: 96, height: 96 })
  slot.onPointer = (e) => { if (e === 'drop') this.acceptItem() }
}

Like the value callbacks, onPointer runs where scripts run — locally in the editor/standalone, on the server in multiplayer. On the visual Canvas the same events come from an element's pointer function (set in the Inspector), which receives the event name.

Keyboard & gamepad navigation

Every interactive control — from either UI system — can be driven without a mouse, Unity's EventSystem. Tab moves between controls; the arrow keys move by layout (the nearest control in that direction, like Unity's Automatic navigation); Enter / Space activate the selected one; Escape deselects. A gamepad works the same — the D-pad / left stick navigate, A submits, B cancels. The selected control shows a clear highlight ring.

It never steals gameplay input: arrows and the stick only navigate once a UI control is actually selected (Tab into the UI, or the gamepad picks the first control when nothing else has focus), so in-game movement keys are untouched. A selected slider or scrollbar consumes the arrows along its own axis (they adjust it); the cross axis still navigates. This works in every runtime — the editor play-test, standalone, and published multiplayer.

Fonts

Import a real font file (.ttf / .otf / .woff / .woff2) from Project → + New → Import Font. Its bytes travel inside the map — like every texture, model, and sound — so a published game or standalone export carries the font with nothing external to fetch.

Reference it by name: it appears in the Inspector's Font dropdown under Project Fonts, and in scripts as ui.createLabel({ fontFamily: 'MyFont' }). Built-in web-safe stacks (Serif, Monospace, and so on) are always available too.

The Canvas Scaler

Both UI systems author against a 1920×1080 reference and scale with the viewport (Unity's Canvas Scaler, log-blend match 0.5) — a HUD holds its proportions on every screen, and at 1080p the scale is exactly 1. Every pixel dimension you set (font size, widths, margins) is in reference pixels and scales automatically; normalized positions (0–1) don't need scaling. You rarely think about it — it just means your UI won't be tiny on a 4K monitor or huge on a phone.

The 2D Platformer Kit

The fastest way to see the whole 2D stack working together is the 2D Platformer kit (Packs → Built-in Kits → 2D Platformer → Import, then stamp its 2D Platformer Demo scene). It's a complete side-scroller — run/jump player, spinning collectible coins with a HUD counter, and a goal flag — built entirely from editable map scripts, so it doubles as a worked example: flipbook sprite sheets, a character-controller constrained to the 2D plane, a pixel-perfect orthographic follow camera, and a script HUD.

One detail worth knowing if you build your own character controller: a characterController entity fires onTriggerEnter / onTriggerStay / onTriggerExit when it overlaps a trigger collider, exactly like the built-in player — that's how the kit's coins detect the player. The visible sprite is a child of the controller entity, nudged forward in Z so the platform boxes never draw over it.

Publishing

Nothing extra to do. Sprites, canvases, fonts, and script HUDs all save with the map and render in the editor play-test, published multiplayer, and standalone builds. The export prune keeps only the textures and fonts your map actually references (by id or by name, so a texture used only as ui.createImage({ texture: 'Coin' }) survives) and drops the rest to keep the build lean.