Graphics
GraphicsComponent is the right primitive when you need to draw shapes
procedurally — debug overlays, prototype art, gradient bars, custom UI
chrome that doesn’t fit a flexbox model. For laid-out widgets with text and
backgrounds, prefer UISurface instead.
import { GraphicsComponent } from "@yagejs/renderer";
entity.add( new GraphicsComponent({ layer: "world", tint: 0x88ccff }).draw((g) => { g.circle(0, 0, 50).fill({ color: 0x38bdf8 }); }),);Call .draw() again at any time to redraw. The callback receives a PixiJS
Graphics object, so all standard drawing methods are available — rect,
roundRect, poly, moveTo/lineTo, stroke, and fill.
GraphicsComponent takes the same visible / tint / alpha /
blendMode / interactive options as SpriteComponent, plus setMask() / fx
for masking and filters. Graphics are runtime resources and are not part of an
@yagejs/save state root. Save the game facts that determine the drawing, then
let normal scene setup call .draw() when the scene is constructed.
Escape hatch: .graphics (and the g passed to .draw(fn)) is a raw
pixi Graphics instance. The fluent API mirrors Canvas 2D vocabulary —
moveTo, lineTo, arc, bezierCurveTo, plus shape shortcuts like
rect and circle. arc continues the current path: moveTo() to its
start point first for a standalone arc, otherwise a line connects it from
the previous point. Fill and stroke take options bags ({ color, alpha, width, ... }). See the pixi Graphics docs.
Where the drawing turns
Section titled “Where the drawing turns”The drawing’s (0, 0) sits on the entity’s position, and the entity’s
rotation turns the drawing about that point. A barrel drawn upwards from
(0, 0) therefore tips from side to side about its base and never rolls.
pivot names a different point of the drawing to put on the entity position,
in the drawing’s own pixels. Rotation and scale then act about that point:
import { GraphicsComponent } from "@yagejs/renderer";
// The circle's centre sits on the entity, so the barrel rolls in place.entity.add( new GraphicsComponent({ layer: "world", pivot: { x: 0, y: -24 } }).draw( (g) => { g.circle(0, -24, 24).fill({ color: 0x8b5a2b }); }, ),);pivot is the GraphicsComponent counterpart of the anchor that sprites and
text take. It is in pixels rather than a 0–1 fraction, because a drawing has no
texture size to take a fraction of. Both numbers must be finite; a NaN throws
naming the component and the coordinate.
Drawing the shape around (0, 0) in the first place reaches the same result,
and is the simpler choice when you control the callback. pivot is for the
drawings whose coordinates are fixed by something else — a callback shared by
entities that turn about different points, or a shape whose numbers come from
data.
Neither route turns a drawing about a point outside its own entity. For that, parent the entity under another entity placed at the turning point and rotate the parent: an entity’s rotation carries every descendant around its own position.
Repeating a shape
Section titled “Repeating a shape”The context carries a transform stack, so a part that repeats draws in its own
coordinates and the callback works out no per-copy offsets.
translateTransform(x, y?), scaleTransform(x, y?) and
rotateTransform(angle) multiply into the current transform.
setTransform(matrix) — or six numbers — replaces it, and resetTransform()
returns it to identity. save() pushes the current transform and restore()
pops it. Each of them returns the context, so calls chain:
import { GraphicsComponent } from "@yagejs/renderer";
entity.add( new GraphicsComponent({ layer: "world" }).draw((g) => { for (let i = 0; i < 6; i++) { g.save(); g.translateTransform(i * 24, 0).rotateTransform(i * 0.2); g.rect(-6, -6, 12, 12).fill(0xffcc00); // square drawn around its origin g.restore(); } }),);Each square is described once, at the origin, and the transform places and
turns it. The save() / restore() pair is what keeps the six copies
independent: without it each transform would build on the one before, so the
squares would spread further apart and turn further with every step.
Gradient fills
Section titled “Gradient fills”For linear and radial gradients, use linearGradient / radialGradient
instead of importing FillGradient from pixi.js. Stops use yage’s
numeric color + alpha pairs (no CSS color strings), and the result plugs
straight into .fill():
import { GraphicsComponent, linearGradient, radialGradient,} from "@yagejs/renderer";
const fade = linearGradient({ axis: "vertical", // or "horizontal", or explicit start/end points stops: [ { offset: 0, color: 0x000000, alpha: 0.8 }, { offset: 1, color: 0x000000, alpha: 0 }, ],});
entity.add( new GraphicsComponent({ layer: "fog" }).draw((g) => { g.rect(0, 0, 200, 40).fill(fade); }),);By default, coordinates and radii are in local space (the 0-1 range of
the filled shape), so one gradient instance scales to any rect you apply it
to. Pass space: "global" to treat them as absolute pixels instead.
radialGradient additionally accepts an outerCenter for non-concentric
radial effects (rim lighting, directional glows).
Gradients own a GPU texture — call .destroy() in a component’s
onDestroy() when you’re done with them.
Re-export: GradientFill IS pixi FillGradient. The factories convert
yage’s numeric stops to pixi color stops and forward the rest as-is. See
the pixi FillGradient docs.
Texture fills
Section titled “Texture fills”A fill also takes a texture, and the texture tiles across the shape. A small baked strip covers a shape of any size with a repeating pattern — you do not need one texture per band, and you do not need a tiling sprite:
createTexture hands back a GPU texture the caller owns, so bake it inside a
component that destroys it again:
import { Component } from "@yagejs/core";import { GraphicsComponent, RendererKey } from "@yagejs/renderer";import type { TextureResource } from "@yagejs/renderer";
class SkyBand extends Component { private band!: TextureResource;
onAdd(): void { // Bake the tile once, then fill a much larger shape with it. this.band = this.use(RendererKey).createTexture( (g) => { g.rect(0, 0, 64, 16).fill(0x1b3a5c); g.rect(0, 16, 64, 16).fill(0x24507d); }, { width: 64, height: 32 }, );
this.entity.add( new GraphicsComponent({ layer: "sky" }).draw((g) => { g.rect(0, 0, 1200, 400).fill({ texture: this.band, textureSpace: "global", }); }), ); }
onDestroy(): void { this.band.destroy(); // nothing draws it after the owner goes }}textureSpace: "global" measures the texture in the shape’s own coordinates,
so the pattern repeats every texture.source.width pixels however large the
shape is. A texture from createTexture is its own source, so that is
texture.width. A frame cut from a spritesheet fills with the whole sheet,
not the frame. The default, "local", stretches exactly one copy over the shape’s
bounds — useful when you want the texture to fit the shape rather than tile
inside it.
The size passed to createTexture names the region it bakes, measured from
(0, 0). Without a size the texture is the drawn bounds, so its top-left
corner is the first pixel drawn: a drawing that starts at (10, 4) bakes
shifted by that much and tiles at the smaller size.
To scroll the pattern, pass a matrix and redraw:
new Matrix().translate(-offset, 0) moves it sideways. Matrix comes from
pixi.js, like the Graphics object .draw() hands you.
Coding agents: fetch https://yage.dev/llms.txt first and prefer the Markdown references it links over these HTML pages. This page's Markdown counterpart is /llms/packages/renderer.md.