@guinetik/gcanvas 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/demos/coordinates.html +698 -0
  2. package/demos/cube3d.html +23 -0
  3. package/demos/demos.css +17 -3
  4. package/demos/dino.html +42 -0
  5. package/demos/gameobjects.html +626 -0
  6. package/demos/index.html +17 -7
  7. package/demos/js/coordinates.js +840 -0
  8. package/demos/js/cube3d.js +789 -0
  9. package/demos/js/dino.js +1420 -0
  10. package/demos/js/gameobjects.js +176 -0
  11. package/demos/js/plane3d.js +256 -0
  12. package/demos/js/platformer.js +1579 -0
  13. package/demos/js/sphere3d.js +229 -0
  14. package/demos/js/sprite.js +473 -0
  15. package/demos/js/tde/accretiondisk.js +3 -3
  16. package/demos/js/tde/tidalstream.js +2 -2
  17. package/demos/plane3d.html +24 -0
  18. package/demos/platformer.html +43 -0
  19. package/demos/sphere3d.html +24 -0
  20. package/demos/sprite.html +18 -0
  21. package/docs/concepts/coordinate-system.md +384 -0
  22. package/docs/concepts/shapes-vs-gameobjects.md +187 -0
  23. package/docs/fluid-dynamics.md +99 -97
  24. package/package.json +1 -1
  25. package/src/game/game.js +11 -5
  26. package/src/game/objects/index.js +3 -0
  27. package/src/game/objects/platformer-scene.js +411 -0
  28. package/src/game/objects/scene.js +14 -0
  29. package/src/game/objects/sprite.js +529 -0
  30. package/src/game/pipeline.js +20 -16
  31. package/src/game/ui/theme.js +123 -121
  32. package/src/io/input.js +75 -45
  33. package/src/io/mouse.js +44 -19
  34. package/src/io/touch.js +35 -12
  35. package/src/shapes/cube3d.js +599 -0
  36. package/src/shapes/index.js +2 -0
  37. package/src/shapes/plane3d.js +687 -0
  38. package/src/shapes/sphere3d.js +75 -6
  39. package/src/util/camera2d.js +315 -0
  40. package/src/util/index.js +1 -0
  41. package/src/webgl/shaders/plane-shaders.js +332 -0
  42. package/src/webgl/shaders/sphere-shaders.js +4 -2
@@ -13,7 +13,7 @@ import { applyGravitationalLensing } from "../../../src/math/gr.js";
13
13
  // Stream-specific config
14
14
  const STREAM_CONFIG = {
15
15
  gravity: 120000, // Strong gravity (linear falloff G/r)
16
- maxParticles: 6000,
16
+ maxParticles: 10000,
17
17
  particleLifetime: 20, // Seconds - long lifetime so particles can orbit the BH
18
18
 
19
19
  // Velocity inheritance - how much of star's velocity particles get
@@ -42,7 +42,7 @@ const STREAM_CONFIG = {
42
42
 
43
43
  // Particle size
44
44
  sizeMin: 1,
45
- sizeMax: 3,
45
+ sizeMax: 1.2,
46
46
 
47
47
  // Gravitational lensing (visual effect)
48
48
  // These are multipliers relative to the BH's current radius
@@ -0,0 +1,24 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Plane3D Showcase - GCanvas</title>
7
+ <link rel="stylesheet" href="demos.css" />
8
+ <script src="./js/info-toggle.js"></script>
9
+ </head>
10
+ <body>
11
+ <div id="info">
12
+ <strong>Plane3D Showcase</strong> — 3D plane rendering modes<br/>
13
+ <span style="color:#CCC">
14
+ <li>Solid Color (Canvas 2D rendering)</li>
15
+ <li>Gradient (WebGL shader)</li>
16
+ <li>Grid (WebGL shader)</li>
17
+ <li>Drag to rotate camera</li>
18
+ <li>Double-click to reset view</li>
19
+ </span>
20
+ </div>
21
+ <canvas id="game"></canvas>
22
+ <script type="module" src="./js/plane3d.js"></script>
23
+ </body>
24
+ </html>
@@ -0,0 +1,43 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Tron Platformer - GCanvas Demo</title>
7
+ <link rel="stylesheet" href="demos.css" />
8
+ <script src="./js/info-toggle.js"></script>
9
+ <style>
10
+ /* Override for full immersion */
11
+ body {
12
+ background: #000;
13
+ }
14
+
15
+ canvas#game {
16
+ width: 100vw;
17
+ height: 100vh;
18
+ }
19
+
20
+ #info {
21
+ background: rgba(0, 0, 0, 0.9);
22
+ border-bottom: 1px solid #00ff00;
23
+ }
24
+
25
+ #info strong {
26
+ color: #00ff00;
27
+ }
28
+ </style>
29
+ </head>
30
+ <body>
31
+ <div id="info">
32
+ <strong>NETRUNNER.exe</strong> — platformer demo<br/>
33
+ <span style="color:#666">
34
+ <li>ARROWS / WASD: move left/right</li>
35
+ <li>SPACE / UP / W: jump</li>
36
+ <li>stomp enemies • collect data orbs</li>
37
+ <li>reach the portal to complete the level</li>
38
+ </span>
39
+ </div>
40
+ <canvas id="game"></canvas>
41
+ <script type="module" src="./js/platformer.js"></script>
42
+ </body>
43
+ </html>
@@ -0,0 +1,24 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Sphere3D Showcase - GCanvas</title>
7
+ <link rel="stylesheet" href="demos.css" />
8
+ <script src="./js/info-toggle.js"></script>
9
+ </head>
10
+ <body>
11
+ <div id="info">
12
+ <strong>Sphere3D Showcase</strong> — 3D sphere rendering modes<br/>
13
+ <span style="color:#CCC">
14
+ <li>Solid Color (Canvas 2D rendering)</li>
15
+ <li>Gas Giant (WebGL shader with bands)</li>
16
+ <li>Star (WebGL shader with glow)</li>
17
+ <li>Drag to rotate camera</li>
18
+ <li>Double-click to reset view</li>
19
+ </span>
20
+ </div>
21
+ <canvas id="game"></canvas>
22
+ <script type="module" src="./js/sphere3d.js"></script>
23
+ </body>
24
+ </html>
@@ -0,0 +1,18 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8" />
6
+ <title>Sprite Timeline Demo</title>
7
+ <link rel="stylesheet" href="demos.css" />
8
+ </head>
9
+
10
+ <body>
11
+ <div id="info">
12
+ <strong>Sprite Demo</strong> - Demonstrates the Sprite class, a MovieClip-style GameObject with frame-by-frame timeline animation. Each Sprite contains a collection of Shapes as frames, with timeline controls including play(), pause(), stop(), goto(), gotoAndStop(), gotoAndPlay(), and rewind(). This showcases how to create frame-by-frame animations similar to Adobe Flash MovieClips within the gcanvas engine's abstractions.
13
+ </div>
14
+ <canvas id="game"></canvas>
15
+ <script type="module" src="./js/sprite.js"></script>
16
+ </body>
17
+
18
+ </html>
@@ -0,0 +1,384 @@
1
+ # Coordinate System
2
+
3
+ GCanvas uses a **center-based coordinate system** where `x` and `y` refer to an object's center point, not its top-left corner. This guide explains how positioning works throughout the library.
4
+
5
+ ## TL;DR
6
+
7
+ | Situation | How It Works |
8
+ |-----------|--------------|
9
+ | Object at `(100, 100)` | Center is at screen position (100, 100) |
10
+ | Child at `(10, 20)` in a Scene at `(100, 100)` | Child center is at screen position (110, 120) |
11
+ | Camera3D `project()` returns `(0, 0)` | Object is at the center of the 3D view |
12
+ | Scene3D at `(width/2, height/2)` | 3D origin appears at canvas center |
13
+
14
+ ---
15
+
16
+ ## 1. Center-Based Positioning
17
+
18
+ Unlike many graphics libraries that use top-left corners, GCanvas positions objects by their **center point**. This makes rotation and scaling more intuitive since transforms happen around the object's center.
19
+
20
+ ```javascript
21
+ // This circle's CENTER is at (100, 100)
22
+ const circle = new Circle(50, { x: 100, y: 100, color: "#0f0" });
23
+
24
+ // The circle extends from:
25
+ // - Left edge: 50 (100 - radius)
26
+ // - Right edge: 150 (100 + radius)
27
+ // - Top edge: 50 (100 - radius)
28
+ // - Bottom edge: 150 (100 + radius)
29
+ ```
30
+
31
+ ### Comparison with Top-Left Systems
32
+
33
+ | System | `x: 100, y: 100` means... | Rotation pivot |
34
+ |--------|---------------------------|----------------|
35
+ | **GCanvas (center)** | Center at (100, 100) | Around center |
36
+ | Top-left systems | Top-left at (100, 100) | Around top-left corner |
37
+
38
+ **Why center-based?** When you rotate or scale an object, it transforms around its center naturally. No need to manually adjust the pivot point.
39
+
40
+ ---
41
+
42
+ ## 2. Basic Positioning
43
+
44
+ When you add an object directly to the pipeline, its coordinates are **absolute screen coordinates**.
45
+
46
+ ```javascript
47
+ class MyGame extends Game {
48
+ init() {
49
+ super.init();
50
+
51
+ // Circle centered at screen position (200, 150)
52
+ const circle = new Circle(30, { x: 200, y: 150, color: "#0ff" });
53
+ this.pipeline.add(circle);
54
+
55
+ // Rectangle centered at screen position (400, 300)
56
+ const rect = new Rectangle({
57
+ x: 400, y: 300,
58
+ width: 100, height: 60,
59
+ color: "#f0f"
60
+ });
61
+ this.pipeline.add(rect);
62
+ }
63
+ }
64
+ ```
65
+
66
+ Canvas origin `(0, 0)` is the **top-left corner**:
67
+ - **X increases** going right
68
+ - **Y increases** going down
69
+
70
+ ---
71
+
72
+ ## 3. Parent-Child Relationships
73
+
74
+ When you add objects to a **Scene**, their coordinates become **relative to the parent's center**.
75
+
76
+ ```javascript
77
+ // Scene centered at (200, 200)
78
+ const scene = new Scene(this, { x: 200, y: 200 });
79
+
80
+ // Child at (50, 30) relative to scene
81
+ const child = new Circle(20, { x: 50, y: 30, color: "#0f0" });
82
+ scene.add(child);
83
+
84
+ // Child's screen position: (200 + 50, 200 + 30) = (250, 230)
85
+ ```
86
+
87
+ ### Visual Diagram
88
+
89
+ ```
90
+ Canvas (0,0)───────────────────────────────►X
91
+
92
+ │ Scene at (200, 200)
93
+ │ ┌─────────────────────┐
94
+ │ │ Scene's local │
95
+ │ │ origin (0, 0) │
96
+ │ │ ●───────────┼──► Scene's X
97
+ │ │ │ │
98
+ │ │ │ Child at │
99
+ │ │ │ (50, 30) │
100
+ │ │ ▼ ◉ │
101
+ │ │ Scene's Y │
102
+ │ └─────────────────────┘
103
+
104
+
105
+ Y
106
+ ```
107
+
108
+ When the Scene renders:
109
+ 1. Canvas translates to `(200, 200)` — Scene's position
110
+ 2. Child renders at `(50, 30)` — relative to Scene's origin
111
+ 3. Final screen position: `(250, 230)`
112
+
113
+ ### Nested Scenes
114
+
115
+ Coordinates stack through the hierarchy:
116
+
117
+ ```javascript
118
+ const outer = new Scene(this, { x: 100, y: 100 });
119
+ const inner = new Scene(this, { x: 50, y: 50 });
120
+ const shape = new Circle(10, { x: 25, y: 25, color: "#ff0" });
121
+
122
+ inner.add(shape);
123
+ outer.add(inner);
124
+ this.pipeline.add(outer);
125
+
126
+ // Screen position calculation:
127
+ // outer.x + inner.x + shape.x = 100 + 50 + 25 = 175
128
+ // outer.y + inner.y + shape.y = 100 + 50 + 25 = 175
129
+ // Shape appears at screen (175, 175)
130
+ ```
131
+
132
+ ---
133
+
134
+ ## 4. Camera3D and Scene3D
135
+
136
+ When using 3D projection, an additional coordinate transformation layer is introduced.
137
+
138
+ ### How Camera3D.project() Works
139
+
140
+ `Camera3D.project(x, y, z)` transforms a 3D world position to 2D screen coordinates:
141
+
142
+ ```javascript
143
+ const camera = new Camera3D({ perspective: 800 });
144
+
145
+ // Object at 3D position (100, 0, 200)
146
+ const projected = camera.project(100, 0, 200);
147
+ // Returns: { x: 80, y: 0, z: 200, scale: 0.8 }
148
+ ```
149
+
150
+ **Key insight:** The returned `x` and `y` are offsets **from the center of the view**, not absolute screen coordinates. An object at world origin `(0, 0, 0)` projects to screen `(0, 0)`.
151
+
152
+ ### Why Scene3D Centers the View
153
+
154
+ Since Camera3D returns origin-centered coordinates, Scene3D must translate to the canvas center:
155
+
156
+ ```javascript
157
+ // Scene3D at canvas center
158
+ const scene3d = new Scene3D(this, {
159
+ x: this.width / 2, // 400 for 800px canvas
160
+ y: this.height / 2, // 300 for 600px canvas
161
+ camera: this.camera
162
+ });
163
+ ```
164
+
165
+ This means:
166
+ - 3D origin `(0, 0, 0)` appears at screen center
167
+ - Objects with positive X move right of center
168
+ - Objects with positive Y move down from center
169
+ - Objects with positive Z are further away (smaller due to perspective)
170
+
171
+ ### Code Example: Basic Scene3D
172
+
173
+ ```javascript
174
+ import { Game, Scene3D, Sphere3D, Camera3D } from "gcanvas";
175
+
176
+ class My3DDemo extends Game {
177
+ init() {
178
+ super.init();
179
+
180
+ // Create camera with perspective
181
+ this.camera = new Camera3D({
182
+ perspective: 800,
183
+ rotationY: 0.3
184
+ });
185
+
186
+ // Scene3D MUST be centered for 3D projection to work correctly
187
+ this.scene = new Scene3D(this, {
188
+ x: this.width / 2,
189
+ y: this.height / 2,
190
+ camera: this.camera
191
+ });
192
+
193
+ // Sphere at 3D origin - appears at screen center
194
+ const sphere1 = new Sphere3D(50, {
195
+ x: 0, y: 0, z: 0,
196
+ color: "#0ff",
197
+ camera: this.camera
198
+ });
199
+
200
+ // Sphere offset in 3D space - appears right of center
201
+ const sphere2 = new Sphere3D(30, {
202
+ x: 150, y: 0, z: 0,
203
+ color: "#f0f",
204
+ camera: this.camera
205
+ });
206
+
207
+ this.scene.add(sphere1);
208
+ this.scene.add(sphere2);
209
+ this.pipeline.add(this.scene);
210
+ }
211
+
212
+ update(dt) {
213
+ super.update(dt);
214
+ this.camera.update(dt); // For auto-rotation/inertia
215
+ }
216
+ }
217
+ ```
218
+
219
+ ### Common Gotcha: Manual Centering
220
+
221
+ If you're rendering 3D-projected content **outside** of Scene3D, you must manually center:
222
+
223
+ ```javascript
224
+ // In ParticleSystem.renderWithDepthSort():
225
+ // When NOT inside a Scene3D, we must translate to center ourselves
226
+ if (!this.worldSpace && !isInsideScene3D) {
227
+ ctx.save();
228
+ ctx.translate(this.game.width / 2, this.game.height / 2);
229
+ // ... render particles at projected positions ...
230
+ ctx.restore();
231
+ }
232
+ ```
233
+
234
+ Similarly, `Sphere3D` with shader rendering extracts the current transform to find the scene center:
235
+
236
+ ```javascript
237
+ // From sphere3d.js - getting scene center for WebGL compositing
238
+ const transform = ctx.getTransform();
239
+ const sceneX = transform.e; // Translation X (scene center)
240
+ const sceneY = transform.f; // Translation Y (scene center)
241
+ ```
242
+
243
+ ---
244
+
245
+ ## 5. Anchoring and Layouts
246
+
247
+ ### Position Anchoring
248
+
249
+ The `applyAnchor` mixin positions objects relative to the game canvas or a parent container:
250
+
251
+ ```javascript
252
+ import { Text, applyAnchor, Position } from "gcanvas";
253
+
254
+ // Anchor text to top-center of canvas
255
+ const title = new Text(this, "Game Title", { font: "24px sans-serif" });
256
+ applyAnchor(title, {
257
+ anchor: Position.TOP_CENTER,
258
+ anchorMargin: 20 // 20px from top edge
259
+ });
260
+ this.pipeline.add(title);
261
+ ```
262
+
263
+ Available positions:
264
+ - `Position.TOP_LEFT`, `Position.TOP_CENTER`, `Position.TOP_RIGHT`
265
+ - `Position.CENTER_LEFT`, `Position.CENTER`, `Position.CENTER_RIGHT`
266
+ - `Position.BOTTOM_LEFT`, `Position.BOTTOM_CENTER`, `Position.BOTTOM_RIGHT`
267
+
268
+ ### Relative Anchoring
269
+
270
+ Anchor relative to another object:
271
+
272
+ ```javascript
273
+ const panel = new Scene(this, { x: 200, y: 200, width: 300, height: 200 });
274
+
275
+ const label = new Text(this, "Panel Title", {});
276
+ applyAnchor(label, {
277
+ anchor: Position.TOP_CENTER,
278
+ anchorRelative: panel, // Position relative to panel
279
+ anchorMargin: 10
280
+ });
281
+
282
+ panel.add(label);
283
+ ```
284
+
285
+ ### Layout Utilities
286
+
287
+ Layouts automatically position multiple items:
288
+
289
+ ```javascript
290
+ import { verticalLayout, applyLayout, Scene, Text } from "gcanvas";
291
+
292
+ const items = [
293
+ new Text(this, "Option 1", {}),
294
+ new Text(this, "Option 2", {}),
295
+ new Text(this, "Option 3", {})
296
+ ];
297
+
298
+ // Compute vertical layout
299
+ const layout = verticalLayout(items, {
300
+ spacing: 15,
301
+ padding: 10,
302
+ align: "center"
303
+ });
304
+
305
+ // Apply positions to items
306
+ applyLayout(items, layout.positions);
307
+
308
+ // Add to scene (layout is centered by offset)
309
+ const menu = new Scene(this, { x: this.width / 2, y: this.height / 2 });
310
+ items.forEach(item => menu.add(item));
311
+ ```
312
+
313
+ **How layouts work internally:**
314
+ 1. Layouts compute positions in a top-left coordinate system starting at `(0, 0)`
315
+ 2. `applyLayout` applies these positions plus any offset
316
+ 3. LayoutScene subclasses (like `VerticalLayout`) apply a centering offset:
317
+ - Vertical: `offsetY = -totalHeight / 2`
318
+ - Horizontal: `offsetX = -totalWidth / 2`
319
+
320
+ ---
321
+
322
+ ## 6. Practical Tips
323
+
324
+ ### Quick Reference
325
+
326
+ | I want to... | Do this |
327
+ |--------------|---------|
328
+ | Place object at screen position | Set `x`, `y` directly, add to pipeline |
329
+ | Place object relative to parent | Add to Scene, set local `x`, `y` |
330
+ | Center object on screen | `x: game.width / 2, y: game.height / 2` |
331
+ | Anchor to screen edge | Use `applyAnchor` with `Position` constant |
332
+ | Use 3D projection | Use `Scene3D` centered at canvas center |
333
+ | Position particles with Camera3D | Set `camera` and `depthSort: true` on ParticleSystem |
334
+
335
+ ### Common Mistakes
336
+
337
+ **1. Forgetting to center Scene3D**
338
+ ```javascript
339
+ // WRONG - 3D origin appears at top-left
340
+ const scene = new Scene3D(this, { x: 0, y: 0, camera });
341
+
342
+ // CORRECT - 3D origin appears at canvas center
343
+ const scene = new Scene3D(this, {
344
+ x: this.width / 2,
345
+ y: this.height / 2,
346
+ camera
347
+ });
348
+ ```
349
+
350
+ **2. Confusing local and screen coordinates**
351
+ ```javascript
352
+ const scene = new Scene(this, { x: 100, y: 100 });
353
+ const child = new Circle(20, { x: 50, y: 50 });
354
+ scene.add(child);
355
+
356
+ // WRONG assumption: child is at screen (50, 50)
357
+ // CORRECT: child is at screen (150, 150)
358
+ ```
359
+
360
+ **3. Not updating Camera3D in game loop**
361
+ ```javascript
362
+ update(dt) {
363
+ super.update(dt);
364
+ // If using auto-rotation or inertia, camera needs update
365
+ this.camera.update(dt);
366
+ }
367
+ ```
368
+
369
+ **4. Using world coordinates with Camera3D outside Scene3D**
370
+ ```javascript
371
+ // If using Camera3D projection directly (not in Scene3D),
372
+ // remember to translate to canvas center before drawing
373
+ Painter.useCtx((ctx) => {
374
+ ctx.translate(this.width / 2, this.height / 2);
375
+ // Now draw at projected coordinates
376
+ });
377
+ ```
378
+
379
+ ---
380
+
381
+ ## See Also
382
+
383
+ - [Shapes vs GameObjects](./shapes-vs-gameobjects.md) - Understanding the two hierarchies
384
+ - [Rendering Pipeline](./rendering-pipeline.md) - How objects are rendered each frame
@@ -0,0 +1,187 @@
1
+ # Shapes vs GameObjects
2
+
3
+ GCanvas serves two distinct use cases with different abstractions. Understanding when to use each is key to working effectively with the library.
4
+
5
+ ## Overview
6
+
7
+ | Aspect | Shapes | GameObjects |
8
+ |--------|--------|-------------|
9
+ | Purpose | Direct canvas rendering | Managed game pipeline |
10
+ | Game loop | Not required | Required |
11
+ | Rendering | Call `render()` manually | Automatic via pipeline |
12
+ | State | Stateless between frames | Maintains state |
13
+ | Lifecycle | None | `update(dt)` / `draw()` |
14
+ | Best for | Generative art, static visuals | Games, animations, interactive apps |
15
+
16
+ ## The Two Hierarchies
17
+
18
+ ### Shape Hierarchy (Drawing)
19
+
20
+ ```
21
+ Euclidian → Geometry2d → Renderable → Transformable → Shape
22
+ ```
23
+
24
+ - **Euclidian**: Basic positioning (x, y, width, height)
25
+ - **Geometry2d**: Bounds constraints, bounding box calculations
26
+ - **Renderable**: Visibility, opacity, shadows
27
+ - **Transformable**: Rotation, scaling
28
+ - **Shape**: Styling (fill, stroke, lineWidth)
29
+
30
+ ### GameObject Hierarchy (Pipeline)
31
+
32
+ ```
33
+ GameObject → Scene / Sprite / Text
34
+ ```
35
+
36
+ - **GameObject**: Base class with `update(dt)` and `draw()` lifecycle
37
+ - **Scene**: Container for other GameObjects
38
+ - **Sprite**: Animated GameObject with frame-by-frame timeline
39
+ - **Text**: Text rendering with automatic updates
40
+
41
+ ### Containers
42
+
43
+ | Container | Holds | Has update()? |
44
+ |-----------|-------|---------------|
45
+ | `Group` | Shapes | No |
46
+ | `Scene` | GameObjects | Yes |
47
+
48
+ ## When to Use What
49
+
50
+ | Use Case | Use This | Why |
51
+ |----------|----------|-----|
52
+ | Static visualization | `Shape` + `render()` | No update loop needed |
53
+ | Generative art | `Shape`, `Group` | Direct control, no overhead |
54
+ | Game character | `Sprite` (GameObject) | Needs update cycle for animation |
55
+ | UI elements | `Text`, `Scene` | Pipeline handles positioning |
56
+ | Complex scene | `Scene` with children | Hierarchical transforms, lifecycle |
57
+ | Composite shape | `Group` | Transform multiple shapes together |
58
+
59
+ ## Example: Shapes Only (No Game Loop)
60
+
61
+ You can use GCanvas purely for drawing without any game infrastructure:
62
+
63
+ ```javascript
64
+ import { Circle, Rectangle, Group, Painter } from "gcanvas";
65
+
66
+ // Get canvas context
67
+ const canvas = document.getElementById("canvas");
68
+ const ctx = canvas.getContext("2d");
69
+ Painter.ctx = ctx;
70
+
71
+ // Create shapes
72
+ const circle = new Circle(50, { x: 100, y: 100, color: "#0f0" });
73
+ const rect = new Rectangle({ x: 200, y: 100, width: 80, height: 60, color: "#0ff" });
74
+
75
+ // Render directly - no game loop needed
76
+ circle.render();
77
+ rect.render();
78
+
79
+ // Group multiple shapes
80
+ const group = new Group({ x: 300, y: 100, rotation: Math.PI / 4 });
81
+ group.add(new Circle(20, { color: "#f0f" }));
82
+ group.add(new Rectangle({ y: 30, width: 40, height: 20, color: "#ff0" }));
83
+ group.render();
84
+ ```
85
+
86
+ > **Note:** This is perfect for one-off drawings, generative art, or when you want full control over the render loop.
87
+
88
+ ## Example: GameObjects with Pipeline
89
+
90
+ When you need animation, input handling, and managed lifecycles:
91
+
92
+ ```javascript
93
+ import { Game, Scene, Sprite, Text, Circle, Keys } from "gcanvas";
94
+
95
+ class MyGame extends Game {
96
+ init() {
97
+ super.init();
98
+
99
+ // Create a scene (GameObject container)
100
+ this.scene = new Scene(this, { anchor: "center" });
101
+
102
+ // Create a sprite with animation (GameObject)
103
+ this.player = new Sprite(this, { x: 0, y: 0, frameRate: 10 });
104
+ this.player.addFrame(new Circle(20, { color: "#0f0" }));
105
+ this.player.addFrame(new Circle(25, { color: "#0ff" }));
106
+ this.player.play();
107
+
108
+ // Add to scene
109
+ this.scene.add(this.player);
110
+
111
+ // Add scene to pipeline - now it's managed
112
+ this.pipeline.add(this.scene);
113
+
114
+ // Input handling
115
+ this.events.on(Keys.SPACE, () => this.player.pause());
116
+ }
117
+
118
+ update(dt) {
119
+ super.update(dt);
120
+ // Player sprite automatically updates via pipeline
121
+ }
122
+ }
123
+ ```
124
+
125
+ > **Note:** GameObjects get their `update(dt)` called automatically by the pipeline each frame.
126
+
127
+ ## Bridging: Shape to GameObject
128
+
129
+ Sometimes you have a Shape (or Group of shapes) that you want to add to the pipeline. Use `ShapeGOFactory`:
130
+
131
+ ```javascript
132
+ import { Game, Group, Circle, Rectangle, ShapeGOFactory } from "gcanvas";
133
+
134
+ class MyGame extends Game {
135
+ init() {
136
+ super.init();
137
+
138
+ // Create a Group of shapes
139
+ const avatar = new Group();
140
+ avatar.add(new Circle(30, { color: "#0f0" })); // head
141
+ avatar.add(new Rectangle({ y: 50, width: 40, height: 60, color: "#0f0" })); // body
142
+
143
+ // Wrap it as a GameObject so it can join the pipeline
144
+ const avatarGO = ShapeGOFactory.create(this, avatar, {
145
+ x: 100,
146
+ y: 100,
147
+ interactive: true // enables click/hover events
148
+ });
149
+
150
+ // Now it's a proper GameObject
151
+ this.pipeline.add(avatarGO);
152
+ }
153
+ }
154
+ ```
155
+
156
+ > **Important:** Never put GameObjects inside Groups. Group is a Shape container - it won't call `update()` on its children. Always use Scene for GameObjects.
157
+
158
+ ## Quick Reference
159
+
160
+ | Class | Type | Container | Has update()? |
161
+ |-------|------|-----------|---------------|
162
+ | `Circle`, `Rectangle`, etc. | Shape | Group | No |
163
+ | `Group` | Shape container | Group | No |
164
+ | `TextShape` | Shape | Group | No |
165
+ | `GameObject` | Base GO | Scene / Pipeline | Yes |
166
+ | `Scene` | GO container | Scene / Pipeline | Yes |
167
+ | `Sprite` | Animated GO | Scene / Pipeline | Yes |
168
+ | `Text` | Text GO | Scene / Pipeline | Yes |
169
+
170
+ ## TL;DR
171
+
172
+ **Just Drawing?**
173
+ - Use `Shape`, `Group`
174
+ - Call `.render()` yourself
175
+ - No Game class needed
176
+
177
+ **Building a Game?**
178
+ - Use `GameObject`, `Scene`, `Sprite`
179
+ - Add to `pipeline`
180
+ - Extend `Game` class
181
+
182
+ ## See Also
183
+
184
+ - [Coordinate System](./coordinate-system.md) - How positioning works (center-based, parent-child, Camera3D)
185
+ - [Rendering Pipeline](./rendering-pipeline.md) - Deep dive into the Shape hierarchy
186
+ - [Architecture Overview](./architecture-overview.md) - High-level system design
187
+ - [Lifecycle](./lifecycle.md) - GameObject lifecycle details