@guinetik/gcanvas 1.0.2 → 1.0.4
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.
- package/dist/gcanvas.es.js +25656 -0
- package/dist/gcanvas.es.min.js +1 -0
- package/dist/gcanvas.umd.js +1 -0
- package/dist/gcanvas.umd.min.js +1 -0
- package/package.json +23 -6
- package/src/game/objects/index.js +1 -0
- package/src/game/objects/spritesheet.js +260 -0
- package/src/game/ui/theme.js +6 -0
- package/src/io/keys.js +9 -1
- package/src/math/boolean.js +481 -0
- package/src/math/index.js +1 -0
- package/.github/workflows/release.yaml +0 -70
- package/.jshintrc +0 -4
- package/.vscode/settings.json +0 -22
- package/CLAUDE.md +0 -310
- package/blackhole.jpg +0 -0
- package/demo.png +0 -0
- package/demos/CNAME +0 -1
- package/demos/animations.html +0 -31
- package/demos/basic.html +0 -38
- package/demos/baskara.html +0 -31
- package/demos/bezier.html +0 -35
- package/demos/beziersignature.html +0 -29
- package/demos/blackhole.html +0 -28
- package/demos/blob.html +0 -35
- package/demos/coordinates.html +0 -698
- package/demos/cube3d.html +0 -23
- package/demos/demos.css +0 -303
- package/demos/dino.html +0 -42
- package/demos/easing.html +0 -28
- package/demos/events.html +0 -195
- package/demos/fluent.html +0 -647
- package/demos/fluid-simple.html +0 -22
- package/demos/fluid.html +0 -37
- package/demos/fractals.html +0 -36
- package/demos/gameobjects.html +0 -626
- package/demos/genart.html +0 -26
- package/demos/gendream.html +0 -26
- package/demos/group.html +0 -36
- package/demos/home.html +0 -587
- package/demos/index.html +0 -376
- package/demos/isometric.html +0 -34
- package/demos/js/animations.js +0 -452
- package/demos/js/basic.js +0 -204
- package/demos/js/baskara.js +0 -751
- package/demos/js/bezier.js +0 -692
- package/demos/js/beziersignature.js +0 -241
- package/demos/js/blackhole/accretiondisk.obj.js +0 -379
- package/demos/js/blackhole/blackhole.obj.js +0 -318
- package/demos/js/blackhole/index.js +0 -409
- package/demos/js/blackhole/particle.js +0 -56
- package/demos/js/blackhole/starfield.obj.js +0 -218
- package/demos/js/blob.js +0 -2276
- package/demos/js/coordinates.js +0 -840
- package/demos/js/cube3d.js +0 -789
- package/demos/js/dino.js +0 -1420
- package/demos/js/easing.js +0 -477
- package/demos/js/fluent.js +0 -183
- package/demos/js/fluid-simple.js +0 -253
- package/demos/js/fluid.js +0 -527
- package/demos/js/fractals.js +0 -931
- package/demos/js/fractalworker.js +0 -93
- package/demos/js/gameobjects.js +0 -176
- package/demos/js/genart.js +0 -268
- package/demos/js/gendream.js +0 -209
- package/demos/js/group.js +0 -140
- package/demos/js/info-toggle.js +0 -25
- package/demos/js/isometric.js +0 -863
- package/demos/js/kerr.js +0 -1556
- package/demos/js/lavalamp.js +0 -590
- package/demos/js/layout.js +0 -354
- package/demos/js/mondrian.js +0 -285
- package/demos/js/opacity.js +0 -275
- package/demos/js/painter.js +0 -484
- package/demos/js/particles-showcase.js +0 -514
- package/demos/js/particles.js +0 -299
- package/demos/js/patterns.js +0 -397
- package/demos/js/penrose/artifact.js +0 -69
- package/demos/js/penrose/blackhole.js +0 -121
- package/demos/js/penrose/constants.js +0 -73
- package/demos/js/penrose/game.js +0 -943
- package/demos/js/penrose/lore.js +0 -278
- package/demos/js/penrose/penrosescene.js +0 -892
- package/demos/js/penrose/ship.js +0 -216
- package/demos/js/penrose/sounds.js +0 -211
- package/demos/js/penrose/voidparticle.js +0 -55
- package/demos/js/penrose/voidscene.js +0 -258
- package/demos/js/penrose/voidship.js +0 -144
- package/demos/js/penrose/wormhole.js +0 -46
- package/demos/js/pipeline.js +0 -555
- package/demos/js/plane3d.js +0 -256
- package/demos/js/platformer.js +0 -1579
- package/demos/js/scene.js +0 -304
- package/demos/js/scenes.js +0 -320
- package/demos/js/schrodinger.js +0 -410
- package/demos/js/schwarzschild.js +0 -1023
- package/demos/js/shapes.js +0 -628
- package/demos/js/space/alien.js +0 -171
- package/demos/js/space/boom.js +0 -98
- package/demos/js/space/boss.js +0 -353
- package/demos/js/space/buff.js +0 -73
- package/demos/js/space/bullet.js +0 -102
- package/demos/js/space/constants.js +0 -85
- package/demos/js/space/game.js +0 -1884
- package/demos/js/space/hud.js +0 -112
- package/demos/js/space/laserbeam.js +0 -179
- package/demos/js/space/lightning.js +0 -277
- package/demos/js/space/minion.js +0 -192
- package/demos/js/space/missile.js +0 -212
- package/demos/js/space/player.js +0 -430
- package/demos/js/space/powerup.js +0 -90
- package/demos/js/space/starfield.js +0 -58
- package/demos/js/space/starpower.js +0 -90
- package/demos/js/spacetime.js +0 -559
- package/demos/js/sphere3d.js +0 -229
- package/demos/js/sprite.js +0 -473
- package/demos/js/svgtween.js +0 -204
- package/demos/js/tde/accretiondisk.js +0 -471
- package/demos/js/tde/blackhole.js +0 -219
- package/demos/js/tde/blackholescene.js +0 -209
- package/demos/js/tde/config.js +0 -59
- package/demos/js/tde/index.js +0 -820
- package/demos/js/tde/jets.js +0 -290
- package/demos/js/tde/lensedstarfield.js +0 -154
- package/demos/js/tde/tdestar.js +0 -297
- package/demos/js/tde/tidalstream.js +0 -372
- package/demos/js/tde_old/blackhole.obj.js +0 -354
- package/demos/js/tde_old/debris.obj.js +0 -791
- package/demos/js/tde_old/flare.obj.js +0 -239
- package/demos/js/tde_old/index.js +0 -448
- package/demos/js/tde_old/star.obj.js +0 -812
- package/demos/js/tiles.js +0 -312
- package/demos/js/tweendemo.js +0 -79
- package/demos/js/visibility.js +0 -102
- package/demos/kerr.html +0 -28
- package/demos/lavalamp.html +0 -27
- package/demos/layouts.html +0 -37
- package/demos/logo.svg +0 -4
- package/demos/loop.html +0 -84
- package/demos/mondrian.html +0 -32
- package/demos/og_image.png +0 -0
- package/demos/opacity.html +0 -36
- package/demos/painter.html +0 -39
- package/demos/particles-showcase.html +0 -28
- package/demos/particles.html +0 -24
- package/demos/patterns.html +0 -33
- package/demos/penrose-game.html +0 -31
- package/demos/pipeline.html +0 -737
- package/demos/plane3d.html +0 -24
- package/demos/platformer.html +0 -43
- package/demos/scene.html +0 -33
- package/demos/scenes.html +0 -96
- package/demos/schrodinger.html +0 -27
- package/demos/schwarzschild.html +0 -27
- package/demos/shapes.html +0 -16
- package/demos/space.html +0 -85
- package/demos/spacetime.html +0 -27
- package/demos/sphere3d.html +0 -24
- package/demos/sprite.html +0 -18
- package/demos/svgtween.html +0 -29
- package/demos/tde.html +0 -28
- package/demos/tiles.html +0 -28
- package/demos/transforms.html +0 -400
- package/demos/tween.html +0 -45
- package/demos/visibility.html +0 -33
- package/docs/README.md +0 -230
- package/docs/api/FluidSystem.md +0 -173
- package/docs/concepts/architecture-overview.md +0 -204
- package/docs/concepts/coordinate-system.md +0 -384
- package/docs/concepts/lifecycle.md +0 -255
- package/docs/concepts/rendering-pipeline.md +0 -279
- package/docs/concepts/shapes-vs-gameobjects.md +0 -187
- package/docs/concepts/tde-zorder.md +0 -106
- package/docs/concepts/two-layer-architecture.md +0 -229
- package/docs/fluid-dynamics.md +0 -99
- package/docs/getting-started/first-game.md +0 -354
- package/docs/getting-started/hello-world.md +0 -269
- package/docs/getting-started/installation.md +0 -175
- package/docs/modules/collision/README.md +0 -453
- package/docs/modules/fluent/README.md +0 -1075
- package/docs/modules/game/README.md +0 -303
- package/docs/modules/isometric-camera.md +0 -210
- package/docs/modules/isometric.md +0 -275
- package/docs/modules/painter/README.md +0 -328
- package/docs/modules/particle/README.md +0 -559
- package/docs/modules/shapes/README.md +0 -221
- package/docs/modules/shapes/base/euclidian.md +0 -123
- package/docs/modules/shapes/base/geometry2d.md +0 -204
- package/docs/modules/shapes/base/renderable.md +0 -215
- package/docs/modules/shapes/base/shape.md +0 -262
- package/docs/modules/shapes/base/transformable.md +0 -243
- package/docs/modules/shapes/hierarchy.md +0 -218
- package/docs/modules/state/README.md +0 -577
- package/docs/modules/util/README.md +0 -99
- package/docs/modules/util/camera3d.md +0 -412
- package/docs/modules/util/scene3d.md +0 -395
- package/index.html +0 -17
- package/jsdoc.json +0 -50
- package/scripts/build-demo.js +0 -69
- package/scripts/bundle4llm.js +0 -276
- package/scripts/clearconsole.js +0 -48
- package/test/math/orbital.test.js +0 -61
- package/test/math/tensor.test.js +0 -114
- package/test/particle/emitter.test.js +0 -204
- package/test/particle/particle-system.test.js +0 -310
- package/test/particle/particle.test.js +0 -116
- package/test/particle/updaters.test.js +0 -386
- package/test/setup.js +0 -120
- package/test/shapes/euclidian.test.js +0 -44
- package/test/shapes/geometry.test.js +0 -86
- package/test/shapes/group.test.js +0 -86
- package/test/shapes/rectangle.test.js +0 -64
- package/test/shapes/transform.test.js +0 -379
- package/test/util/camera3d.test.js +0 -428
- package/test/util/scene3d.test.js +0 -352
- package/vite.config.js +0 -50
- package/vitest.config.js +0 -13
|
@@ -1,384 +0,0 @@
|
|
|
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
|
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
# Game Lifecycle
|
|
2
|
-
|
|
3
|
-
> Understanding the update/render cycle and frame timing.
|
|
4
|
-
|
|
5
|
-
## Overview
|
|
6
|
-
|
|
7
|
-
The Game class manages the core game loop using `requestAnimationFrame`. It provides a fixed-timestep update cycle with automatic delta time calculation, ensuring consistent gameplay regardless of frame rate.
|
|
8
|
-
|
|
9
|
-
## The Game Loop
|
|
10
|
-
|
|
11
|
-
```
|
|
12
|
-
┌─────────────────────────────────────────────────────────────────┐
|
|
13
|
-
│ Game Loop │
|
|
14
|
-
│ │
|
|
15
|
-
│ game.start() │
|
|
16
|
-
│ │ │
|
|
17
|
-
│ ▼ │
|
|
18
|
-
│ ┌─────────────────────────────────────────────────────────┐ │
|
|
19
|
-
│ │ requestAnimationFrame │ │
|
|
20
|
-
│ │ │ │ │
|
|
21
|
-
│ │ ▼ │ │
|
|
22
|
-
│ │ ┌─────────────────────────────────────────────────┐ │ │
|
|
23
|
-
│ │ │ loop(timestamp) │ │ │
|
|
24
|
-
│ │ │ │ │ │
|
|
25
|
-
│ │ │ 1. Calculate elapsed time │ │ │
|
|
26
|
-
│ │ │ elapsed = timestamp - lastTime │ │ │
|
|
27
|
-
│ │ │ │ │ │
|
|
28
|
-
│ │ │ 2. Accumulate time │ │ │
|
|
29
|
-
│ │ │ accumulator += elapsed │ │ │
|
|
30
|
-
│ │ │ │ │ │
|
|
31
|
-
│ │ │ 3. While accumulator >= frameInterval: │ │ │
|
|
32
|
-
│ │ │ │ │ │ │
|
|
33
|
-
│ │ │ ├─► update(dt) │ │ │
|
|
34
|
-
│ │ │ │ │ │ │ │
|
|
35
|
-
│ │ │ │ ├─► Pipeline.update(dt) │ │ │
|
|
36
|
-
│ │ │ │ │ └─► GameObject.update(dt) │ │ │
|
|
37
|
-
│ │ │ │ │ │ │ │
|
|
38
|
-
│ │ │ │ └─► Tweenetik.updateAll(dt) │ │ │
|
|
39
|
-
│ │ │ │ │ │ │
|
|
40
|
-
│ │ │ └─► accumulator -= frameInterval │ │ │
|
|
41
|
-
│ │ │ │ │ │
|
|
42
|
-
│ │ │ 4. render() │ │ │
|
|
43
|
-
│ │ │ │ │ │ │
|
|
44
|
-
│ │ │ ├─► Painter.clear() │ │ │
|
|
45
|
-
│ │ │ │ │ │ │
|
|
46
|
-
│ │ │ └─► Pipeline.render() │ │ │
|
|
47
|
-
│ │ │ └─► GameObject.render() │ │ │
|
|
48
|
-
│ │ │ └─► Shape.draw() │ │ │
|
|
49
|
-
│ │ │ │ │ │
|
|
50
|
-
│ │ │ 5. requestAnimationFrame(loop) │ │ │
|
|
51
|
-
│ │ │ │ │ │
|
|
52
|
-
│ │ └─────────────────────────────────────────────────┘ │ │
|
|
53
|
-
│ │ │ │ │
|
|
54
|
-
│ │ └───────────────────────────────┼───┘
|
|
55
|
-
│ └─────────────────────────────────────────────────────────┘ │
|
|
56
|
-
│ │
|
|
57
|
-
└─────────────────────────────────────────────────────────────────┘
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
## Fixed Timestep
|
|
61
|
-
|
|
62
|
-
GCanvas uses a **fixed timestep** pattern. This means:
|
|
63
|
-
|
|
64
|
-
- Updates happen at a consistent rate (default: 60 FPS)
|
|
65
|
-
- `dt` (delta time) is always the same value
|
|
66
|
-
- Game logic is frame-rate independent
|
|
67
|
-
|
|
68
|
-
```js
|
|
69
|
-
// Default: 60 FPS = 16.67ms per frame
|
|
70
|
-
game.setFPS(60);
|
|
71
|
-
|
|
72
|
-
// dt in update() is always 1/60 = 0.01667 seconds
|
|
73
|
-
update(dt) {
|
|
74
|
-
// Move 200 pixels per second, regardless of actual frame rate
|
|
75
|
-
this.x += 200 * dt;
|
|
76
|
-
}
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
## Lifecycle Methods
|
|
80
|
-
|
|
81
|
-
### Game Lifecycle
|
|
82
|
-
|
|
83
|
-
```js
|
|
84
|
-
class MyGame extends Game {
|
|
85
|
-
constructor(canvas) {
|
|
86
|
-
super(canvas);
|
|
87
|
-
// 1. Constructor: Setup canvas, context, pipeline
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
init() {
|
|
91
|
-
super.init();
|
|
92
|
-
// 2. Init: Create scenes, objects, set up game state
|
|
93
|
-
// Called once before start()
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
update(dt) {
|
|
97
|
-
super.update(dt);
|
|
98
|
-
// 3. Update: Game logic (called every frame)
|
|
99
|
-
// dt = time since last update in seconds
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
render() {
|
|
103
|
-
super.render();
|
|
104
|
-
// 4. Render: Drawing (called every frame after update)
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const game = new MyGame(canvas);
|
|
109
|
-
game.start(); // Calls init() then starts the loop
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
### GameObject Lifecycle
|
|
113
|
-
|
|
114
|
-
```js
|
|
115
|
-
class Player extends GameObject {
|
|
116
|
-
constructor(game) {
|
|
117
|
-
super(game);
|
|
118
|
-
// 1. Constructor: Create shapes, set initial state
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
update(dt) {
|
|
122
|
-
// 2. Update: Movement, physics, game logic
|
|
123
|
-
// Called every frame while active
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
render() {
|
|
127
|
-
// 3. Render: Draw the object
|
|
128
|
-
// Called every frame while visible
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
destroy() {
|
|
132
|
-
super.destroy();
|
|
133
|
-
// 4. Destroy: Cleanup when removed
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
## Delta Time (dt)
|
|
139
|
-
|
|
140
|
-
The `dt` parameter represents time elapsed since the last update, in **seconds**.
|
|
141
|
-
|
|
142
|
-
```js
|
|
143
|
-
update(dt) {
|
|
144
|
-
// dt ≈ 0.01667 at 60 FPS
|
|
145
|
-
// dt ≈ 0.03333 at 30 FPS
|
|
146
|
-
|
|
147
|
-
// Movement: pixels per second
|
|
148
|
-
this.x += this.speed * dt; // speed = 200 means 200 px/sec
|
|
149
|
-
|
|
150
|
-
// Rotation: radians per second
|
|
151
|
-
this.rotation += Math.PI * dt; // Half rotation per second
|
|
152
|
-
|
|
153
|
-
// Timers
|
|
154
|
-
this.timer += dt;
|
|
155
|
-
if (this.timer >= 2.0) { // 2 seconds
|
|
156
|
-
this.doSomething();
|
|
157
|
-
this.timer = 0;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
## Frame Rate Control
|
|
163
|
-
|
|
164
|
-
```js
|
|
165
|
-
// Set target FPS
|
|
166
|
-
game.setFPS(60); // Default
|
|
167
|
-
game.setFPS(30); // Lower for mobile/performance
|
|
168
|
-
|
|
169
|
-
// Current frame number
|
|
170
|
-
console.log(game.frame);
|
|
171
|
-
|
|
172
|
-
// Pause on blur (tab switch)
|
|
173
|
-
game.enablePauseOnBlur(true);
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
## Pipeline Execution Order
|
|
177
|
-
|
|
178
|
-
The Pipeline manages multiple objects and ensures proper execution order:
|
|
179
|
-
|
|
180
|
-
```
|
|
181
|
-
Pipeline.update(dt)
|
|
182
|
-
├─► Filter active objects
|
|
183
|
-
├─► Sort by zIndex
|
|
184
|
-
├─► For each object:
|
|
185
|
-
│ └─► object.update(dt)
|
|
186
|
-
└─► Tweenetik.updateAll(dt)
|
|
187
|
-
|
|
188
|
-
Pipeline.render()
|
|
189
|
-
├─► Filter visible objects
|
|
190
|
-
├─► Sort by zIndex (low to high)
|
|
191
|
-
└─► For each object:
|
|
192
|
-
└─► object.render()
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
## Scenes and Hierarchy
|
|
196
|
-
|
|
197
|
-
Scenes create nested lifecycles:
|
|
198
|
-
|
|
199
|
-
```js
|
|
200
|
-
// Scene contains GameObjects
|
|
201
|
-
const scene = new Scene(game);
|
|
202
|
-
scene.add(player);
|
|
203
|
-
scene.add(enemy);
|
|
204
|
-
game.pipeline.add(scene);
|
|
205
|
-
|
|
206
|
-
// Lifecycle flows through hierarchy:
|
|
207
|
-
// Pipeline.update()
|
|
208
|
-
// └─► Scene.update()
|
|
209
|
-
// ├─► player.update()
|
|
210
|
-
// └─► enemy.update()
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
## State Management
|
|
214
|
-
|
|
215
|
-
```js
|
|
216
|
-
class MyGame extends Game {
|
|
217
|
-
init() {
|
|
218
|
-
this.state = 'menu'; // menu, playing, paused, gameover
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
update(dt) {
|
|
222
|
-
switch (this.state) {
|
|
223
|
-
case 'menu':
|
|
224
|
-
this.menuScene.update(dt);
|
|
225
|
-
break;
|
|
226
|
-
case 'playing':
|
|
227
|
-
this.gameScene.update(dt);
|
|
228
|
-
break;
|
|
229
|
-
case 'paused':
|
|
230
|
-
// Don't update game objects
|
|
231
|
-
break;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
## Performance Tips
|
|
238
|
-
|
|
239
|
-
1. **Use `active` flag** - Set `gameObject.active = false` to skip updates
|
|
240
|
-
2. **Use `visible` flag** - Set `shape.visible = false` to skip rendering
|
|
241
|
-
3. **Pool objects** - Reuse objects instead of creating/destroying
|
|
242
|
-
4. **Batch similar operations** - Group shapes in containers
|
|
243
|
-
5. **Profile with DevTools** - Use browser performance tools
|
|
244
|
-
|
|
245
|
-
## Related
|
|
246
|
-
|
|
247
|
-
- [Architecture Overview](./architecture-overview.md) - Full system architecture
|
|
248
|
-
- [Two-Layer Architecture](./two-layer-architecture.md) - Shape vs Game layer
|
|
249
|
-
- [Rendering Pipeline](./rendering-pipeline.md) - Shape inheritance chain
|
|
250
|
-
|
|
251
|
-
## See Also
|
|
252
|
-
|
|
253
|
-
- [Game Module](../modules/game/README.md)
|
|
254
|
-
- [Pipeline](../modules/game/pipeline.md)
|
|
255
|
-
- [GameObject](../modules/game/gameobject.md)
|