Understanding how positioning works throughout the library
GCanvas uses a center-based coordinate system where x and y
refer to an object's center point, not its top-left corner. This makes rotation and scaling more
intuitive since transforms happen around the object's center.
| Situation | How It Works |
|---|---|
Object at (100, 100) |
Center is at screen position (100, 100) |
Child at (10, 20) in Scene at (100, 100) |
Child center is at screen (110, 120) |
Camera3D project() returns (0, 0) |
Object is at the center of the 3D view |
Scene3D at (width/2, height/2) |
3D origin appears at canvas center |
Unlike many graphics libraries that use top-left corners, GCanvas positions objects by their
center point. When you set x: 100, y: 100, the object's center
is at that position.
// This circle's CENTER is at (100, 100)
const circle = new Circle(50, { x: 100, y: 100, color: "#0f0" });
// The circle extends from:
// - Left edge: 50 (100 - radius)
// - Right edge: 150 (100 + radius)
// - Top edge: 50 (100 - radius)
// - Bottom edge: 150 (100 + radius)
| System | x: 100, y: 100 means... |
Rotation pivot |
|---|---|---|
| GCanvas (center) | Center at (100, 100) | Around center |
| Top-left systems | Top-left at (100, 100) | Around top-left corner |
When you add an object directly to the pipeline, its coordinates are absolute screen
coordinates. Canvas origin (0, 0) is the top-left corner.
// Circle centered at screen position (200, 150)
const circle = new Circle(30, { x: 200, y: 150, color: "#0ff" });
this.pipeline.add(circle);
// X increases going right
// Y increases going down
When you add objects to a Scene, their coordinates become relative to the parent's center. This is the key to understanding hierarchical positioning.
// Scene centered at (200, 150)
const scene = new Scene(this, { x: 200, y: 150 });
// Child at (50, 30) relative to scene
const child = new Circle(20, { x: 50, y: 30, color: "#0f0" });
scene.add(child);
// Child's screen position: (200 + 50, 150 + 30) = (250, 180)
Coordinates stack through the hierarchy:
const outer = new Scene(this, { x: 100, y: 100 });
const inner = new Scene(this, { x: 50, y: 50 });
const shape = new Circle(10, { x: 25, y: 25, color: "#ff0" });
inner.add(shape);
outer.add(inner);
this.pipeline.add(outer);
// Screen position: 100 + 50 + 25 = 175 for both X and Y
When using 3D projection, an additional coordinate transformation layer is introduced.
Camera3D.project() transforms 3D world positions to 2D screen coordinates
centered at the origin.
const camera = new Camera3D({ perspective: 800 });
// Scene3D MUST be centered for 3D projection to work correctly
const scene = new Scene3D(this, {
x: this.width / 2,
y: this.height / 2,
camera: camera
});
// Object at 3D origin (0, 0, 0) appears at screen center
// Object at (100, 0, 0) appears right of center
| Step | What Happens |
|---|---|
| 1. World Position | Object at 3D coordinates (x, y, z) |
| 2. Camera Transform | Subtract camera position, apply rotation |
| 3. Perspective | scale = perspective / (perspective + z) |
| 4. Screen Offset | Returns (screenX, screenY) relative to origin |
| 5. Scene3D Centering | Adds (width/2, height/2) to get final position |
If you're rendering 3D-projected content outside of Scene3D, you must manually center:
// When NOT inside a Scene3D, you must translate to center yourself
Painter.useCtx((ctx) => {
ctx.save();
ctx.translate(this.game.width / 2, this.game.height / 2);
// Now draw at projected coordinates
const projected = camera.project(x, y, z);
ctx.fillRect(projected.x - 5, projected.y - 5, 10, 10);
ctx.restore();
});
The applyAnchor mixin and layout utilities help position objects relative
to the canvas edges or other containers.
import { applyAnchor, Position } from "gcanvas";
// Anchor text to top-center of canvas
const title = new Text(this, "Game Title", { font: "24px sans-serif" });
applyAnchor(title, {
anchor: Position.TOP_CENTER,
anchorMargin: 20 // 20px from top edge
});
// Available positions:
// TOP_LEFT, TOP_CENTER, TOP_RIGHT
// CENTER_LEFT, CENTER, CENTER_RIGHT
// BOTTOM_LEFT, BOTTOM_CENTER, BOTTOM_RIGHT
Layouts automatically position multiple items:
import { verticalLayout, applyLayout } from "gcanvas";
const items = [
new Text(this, "Option 1", {}),
new Text(this, "Option 2", {}),
new Text(this, "Option 3", {})
];
// Compute vertical layout
const layout = verticalLayout(items, {
spacing: 15,
padding: 10,
align: "center"
});
// Apply positions to items
applyLayout(items, layout.positions);
| I want to... | Do this |
|---|---|
| Place object at screen position | Set x, y directly, add to pipeline |
| Place object relative to parent | Add to Scene, set local x, y |
| Center object on screen | x: game.width / 2, y: game.height / 2 |
| Anchor to screen edge | Use applyAnchor with Position constant |
| Use 3D projection | Use Scene3D centered at canvas center |
// WRONG - 3D origin appears at top-left
const scene = new Scene3D(this, { x: 0, y: 0, camera });
// CORRECT - 3D origin appears at canvas center
const scene = new Scene3D(this, {
x: this.width / 2,
y: this.height / 2,
camera
});
const scene = new Scene(this, { x: 100, y: 100 });
const child = new Circle(20, { x: 50, y: 50 });
scene.add(child);
// WRONG assumption: child is at screen (50, 50)
// CORRECT: child is at screen (150, 150)
update(dt) {
super.update(dt);
// If using auto-rotation or inertia, camera needs update
this.camera.update(dt);
}