Skip to main content
The G3Engine 2D Editor provides a complete toolset for building 2D games, pixel art, UI interfaces, and sprite-based content. Built with HTML5 Canvas, it offers real-time rendering with layer support and professional editing tools.

Overview

The 2D viewport uses a custom Canvas2D renderer with camera controls, layer management, and precision editing tools optimized for game development.
The 2D editor supports unlimited sprites, layers, and game canvas sizes up to 4096x4096 pixels.

Canvas Workspace

Game Area

Your game’s playable area is shown as a bordered region in the center of the viewport:
  • Default size: 800x600 pixels
  • Customizable dimensions: Configure in editor settings
  • Visual boundary: Dashed green outline marks the game area
  • Origin marker: Shows (0,0) coordinate at center
// From editor2DStore.ts:79-80
canvasWidth: 800,
canvasHeight: 600,

Camera System

Navigate your scene with intuitive camera controls:
  • Middle-click + drag to pan the camera
  • Alt + left-click + drag for one-handed panning
  • Camera position updates in real-time
// Camera state from editor2DStore.ts:56
interface Camera2D {
  x: number;      // Pan offset X
  y: number;      // Pan offset Y  
  zoom: number;   // Zoom level (0.1 - 5.0)
}

Grid System

Precision placement with customizable grid:
  • Grid size: Default 32px, fully customizable
  • Snap to grid: Toggle with snapToGrid setting
  • Visual grid: Toggle with showGrid setting
  • Origin lines: Highlighted X/Y axes at (0,0)
// Grid rendering from Viewport2D.tsx:212-239
if (showGrid) {
  ctx.strokeStyle = 'rgba(255,255,255,0.03)';
  ctx.lineWidth = 1 / camera.zoom;
  
  // Draw grid lines at gridSize intervals
  for (let x = startX; x <= endX; x += gridSize) {
    ctx.beginPath();
    ctx.moveTo(x, -halfH);
    ctx.lineTo(x, halfH);
    ctx.stroke();
  }
}
Grid snapping applies to sprite movement and placement, making pixel-perfect alignment effortless.

Sprite System

Sprites are the building blocks of 2D scenes:
interface Sprite2D {
  id: string;
  name: string;
  type: 'sprite' | 'shape' | 'text' | 'tilemap';
  
  // Transform
  x: number;
  y: number;
  width: number;
  height: number;
  rotation: number;      // Degrees
  scaleX: number;
  scaleY: number;
  
  // Rendering
  opacity: number;       // 0-1
  visible: boolean;
  locked: boolean;
  layerId: string;
  
  // Appearance
  fillColor: string;
  strokeColor: string;
  strokeWidth: number;
  
  // Type-specific
  shapeType?: 'rect' | 'circle' | 'line' | 'polygon';
  cornerRadius?: number;
  text?: string;
  fontSize?: number;
  fontFamily?: string;
  emoji?: string;        // Quick prototyping
}

Sprite Types

Vector shapes with fill and stroke:
  • Rectangle: With optional corner radius
  • Circle/Ellipse: Perfect for particles, bullets
  • Line: For connections, trails
  • Polygon: Custom shapes
// Shape rendering from Viewport2D.tsx:260-276
if (sp.fillColor && sp.fillColor !== 'transparent') {
  ctx.fillStyle = sp.fillColor;
  ctx.beginPath();
  if (sp.shapeType === 'circle') {
    ctx.ellipse(0, 0, hw, hh, 0, 0, Math.PI * 2);
  } else {
    ctx.roundRect(-hw, -hh, sp.width, sp.height, sp.cornerRadius || 0);
  }
  ctx.fill();
}

Layer Management

Organize sprites with a professional layer system:
interface Layer2D {
  id: string;
  name: string;
  visible: boolean;  // Show/hide entire layer
  locked: boolean;   // Prevent editing
  opacity: number;   // Layer-wide opacity (0-1)
  order: number;     // Render order (higher = on top)
}

Default Layers

G3Engine provides three starter layers:
  1. Background (order: 0) - For backgrounds and environments
  2. Main (order: 1) - Primary game content
  3. UI (order: 2) - User interface elements
1

Create Layer

Call addLayer(name) to create a new layer. It’s automatically placed on top.
2

Reorder Layers

Use reorderLayer(id, newOrder) to change render order. Higher order renders on top.
3

Layer Visibility

Toggle visible to show/hide all sprites on a layer without deleting them.
4

Lock Layer

Set locked: true to prevent accidental edits while keeping layer visible.

Layer Rendering

// From Viewport2D.tsx:242-248
const visibleLayers = allLayers
  .filter((l) => l.visible)
  .sort((a, b) => a.order - b.order);

for (const layer of visibleLayers) {
  const layerSprites = allSprites.filter((sp) => 
    sp.layerId === layer.id && sp.visible
  );
  // Render sprites with combined opacity
  ctx.globalAlpha = sp.opacity * layer.opacity;
}
Sprite opacity is multiplied by layer opacity for fine-grained control.

Editing Tools

The 2D editor provides multiple tools for different tasks:
ToolHotkeyDescription
SelectVSelect and click sprites
MoveGDrag sprites to reposition
DrawBFree-hand drawing
EraseERemove pixels or sprites
ShapeUCreate geometric shapes
TextTAdd text labels
// Tool type from editor2DStore.ts:8
type Tool2D = 'select' | 'move' | 'draw' | 'erase' | 'fill' | 'shape' | 'text';

Selection & Transform

When a sprite is selected:
  • Green outline: Dashed border indicates selection
  • Corner handles: Four handles for future resize support
  • Drag to move: Click and drag to reposition
  • Grid snapping: Automatic if snapToGrid is enabled
// Selection rendering from Viewport2D.tsx:298-322
if (sp.id === selectedSpriteId) {
  ctx.save();
  ctx.translate(sp.x, sp.y);
  ctx.rotate((sp.rotation * Math.PI) / 180);
  
  // Selection outline
  ctx.strokeStyle = '#14f195';
  ctx.lineWidth = 2 / camera.zoom;
  ctx.setLineDash([6 / camera.zoom, 3 / camera.zoom]);
  ctx.strokeRect(-hw - 2, -hh - 2, sp.width + 4, sp.height + 4);
  
  // Corner handles
  const handleSize = 6 / camera.zoom;
  ctx.fillStyle = '#14f195';
  const corners = [
    [-hw - 2, -hh - 2], [hw + 2, -hh - 2],
    [-hw - 2, hh + 2], [hw + 2, hh + 2],
  ];
  for (const [cx, cy] of corners) {
    ctx.fillRect(cx - handleSize / 2, cy - handleSize / 2, handleSize, handleSize);
  }
  
  ctx.restore();
}

Hit Testing

The editor uses precise bounding box hit detection:
// From Viewport2D.tsx:64-80
for (let i = sprites.length - 1; i >= 0; i--) {
  const sp = sprites[i];
  
  // Check layer visibility and lock state
  if (!sp.visible || !visibleLayers.has(sp.layerId) || lockedLayers.has(sp.layerId)) 
    continue;
  
  // Bounding box test
  if (
    world.x >= sp.x - sp.width / 2 &&
    world.x <= sp.x + sp.width / 2 &&
    world.y >= sp.y - sp.height / 2 &&
    world.y <= sp.y + sp.height / 2
  ) {
    selectSprite(sp.id);
    return;
  }
}
Hit testing respects layer order - sprites on higher layers are tested first.

Keyboard Shortcuts

Rapid workflow with comprehensive shortcuts:
ShortcutAction
VSelect tool
GMove tool
BDraw/Brush tool
EErase tool
UShape tool
TText tool
Delete / BackspaceDelete selected sprite
Cmd/Ctrl + ZUndo
Cmd/Ctrl + Shift + ZRedo

Coordinate System

The 2D editor uses a center-origin coordinate system:
  • Origin (0, 0): Center of the game canvas
  • +X: Right
  • +Y: Down (standard canvas convention)
  • Sprite positions: Center-anchored by default

Screen to World Conversion

// From Viewport2D.tsx:26-31
const screenToWorld = (sx: number, sy: number) => {
  return {
    x: (sx - camera.x) / camera.zoom,
    y: (sy - camera.y) / camera.zoom,
  };
};

World to Screen Conversion

// From Viewport2D.tsx:33-39
const worldToScreen = (wx: number, wy: number) => {
  return {
    x: wx * camera.zoom + camera.x,
    y: wy * camera.zoom + camera.y,
  };
};

Rendering Pipeline

The 2D editor uses a requestAnimationFrame render loop:
1

Clear Canvas

Fill with dark background color #12121c
2

Apply Camera Transform

Translate by camera position and scale by zoom level
3

Render Game Area

Draw background and dashed border for game bounds
4

Draw Grid

Render grid lines if showGrid is enabled
5

Render Sprites by Layer

Loop through visible layers in order, rendering sprites with transforms and opacity
6

Draw Selection

Render selection outline and handles for selected sprite
7

HUD Overlays

Canvas dimensions label, origin marker
// Main render loop from Viewport2D.tsx:164-346
const render = () => {
  // Resize canvas to container
  const { width: cw, height: ch } = container.getBoundingClientRect();
  canvas.width = cw;
  canvas.height = ch;
  
  // Clear
  ctx.fillStyle = '#12121c';
  ctx.fillRect(0, 0, cw, ch);
  
  // Apply camera
  ctx.save();
  ctx.translate(cam.x, cam.y);
  ctx.scale(cam.zoom, cam.zoom);
  
  // ... render scene ...
  
  ctx.restore();
  
  frameId = requestAnimationFrame(render);
};

History System

Full undo/redo support for all sprite operations:
// History structure from editor2DStore.ts:83-84
history: { sprites: Sprite2D[] }[];
historyIndex: number;
  • Auto-save: History snapshots after add, remove, move, update
  • Unlimited undo: Navigate back through all changes
  • State restoration: Sprite properties fully restored on undo/redo

Performance

Optimized for smooth 60fps rendering:
  • Efficient Canvas2D: Direct pixel manipulation
  • Layer culling: Hidden layers skip rendering
  • Camera frustum: Only visible sprites processed (future optimization)
  • Transform caching: Reuse calculations where possible

Next Steps

3D Editor

Switch to 3D mode for immersive worlds

Visual Scripting

Add interactivity to your 2D sprites

Asset Library

Import and manage 2D assets

Web3 Integration

Tokenize 2D sprites as NFTs

Build docs developers (and LLMs) love