@guinetik/gcanvas 1.0.4 → 1.0.5
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/CNAME +1 -0
- package/dist/animations.html +31 -0
- package/dist/basic.html +38 -0
- package/dist/baskara.html +31 -0
- package/dist/bezier.html +35 -0
- package/dist/beziersignature.html +29 -0
- package/dist/blackhole.html +28 -0
- package/dist/blob.html +35 -0
- package/dist/coordinates.html +698 -0
- package/dist/cube3d.html +23 -0
- package/dist/demos.css +303 -0
- package/dist/dino.html +42 -0
- package/dist/easing.html +28 -0
- package/dist/events.html +195 -0
- package/dist/fluent.html +647 -0
- package/dist/fluid-simple.html +22 -0
- package/dist/fluid.html +37 -0
- package/dist/fractals.html +36 -0
- package/dist/gameobjects.html +626 -0
- package/dist/gcanvas.es.js +517 -0
- package/dist/gcanvas.es.min.js +1 -1
- package/dist/gcanvas.umd.js +1 -1
- package/dist/gcanvas.umd.min.js +1 -1
- package/dist/genart.html +26 -0
- package/dist/gendream.html +26 -0
- package/dist/group.html +36 -0
- package/dist/home.html +587 -0
- package/dist/hyperbolic001.html +23 -0
- package/dist/hyperbolic002.html +23 -0
- package/dist/hyperbolic003.html +23 -0
- package/dist/hyperbolic004.html +23 -0
- package/dist/hyperbolic005.html +22 -0
- package/dist/index.html +398 -0
- package/dist/isometric.html +34 -0
- package/dist/js/animations.js +452 -0
- package/dist/js/basic.js +204 -0
- package/dist/js/baskara.js +751 -0
- package/dist/js/bezier.js +692 -0
- package/dist/js/beziersignature.js +241 -0
- package/dist/js/blackhole/accretiondisk.obj.js +379 -0
- package/dist/js/blackhole/blackhole.obj.js +318 -0
- package/dist/js/blackhole/index.js +409 -0
- package/dist/js/blackhole/particle.js +56 -0
- package/dist/js/blackhole/starfield.obj.js +218 -0
- package/dist/js/blob.js +2276 -0
- package/dist/js/coordinates.js +840 -0
- package/dist/js/cube3d.js +789 -0
- package/dist/js/dino.js +1420 -0
- package/dist/js/easing.js +477 -0
- package/dist/js/fluent.js +183 -0
- package/dist/js/fluid-simple.js +253 -0
- package/dist/js/fluid.js +527 -0
- package/dist/js/fractals.js +932 -0
- package/dist/js/fractalworker.js +93 -0
- package/dist/js/gameobjects.js +176 -0
- package/dist/js/genart.js +268 -0
- package/dist/js/gendream.js +209 -0
- package/dist/js/group.js +140 -0
- package/dist/js/hyperbolic001.js +310 -0
- package/dist/js/hyperbolic002.js +388 -0
- package/dist/js/hyperbolic003.js +319 -0
- package/dist/js/hyperbolic004.js +345 -0
- package/dist/js/hyperbolic005.js +340 -0
- package/dist/js/info-toggle.js +25 -0
- package/dist/js/isometric.js +863 -0
- package/dist/js/kerr.js +1547 -0
- package/dist/js/lavalamp.js +590 -0
- package/dist/js/layout.js +354 -0
- package/dist/js/mondrian.js +285 -0
- package/dist/js/opacity.js +275 -0
- package/dist/js/painter.js +484 -0
- package/dist/js/particles-showcase.js +514 -0
- package/dist/js/particles.js +299 -0
- package/dist/js/patterns.js +397 -0
- package/dist/js/penrose/artifact.js +69 -0
- package/dist/js/penrose/blackhole.js +121 -0
- package/dist/js/penrose/constants.js +73 -0
- package/dist/js/penrose/game.js +943 -0
- package/dist/js/penrose/lore.js +278 -0
- package/dist/js/penrose/penrosescene.js +892 -0
- package/dist/js/penrose/ship.js +216 -0
- package/dist/js/penrose/sounds.js +211 -0
- package/dist/js/penrose/voidparticle.js +55 -0
- package/dist/js/penrose/voidscene.js +258 -0
- package/dist/js/penrose/voidship.js +144 -0
- package/dist/js/penrose/wormhole.js +46 -0
- package/dist/js/pipeline.js +555 -0
- package/dist/js/plane3d.js +256 -0
- package/dist/js/platformer.js +1579 -0
- package/dist/js/scene.js +304 -0
- package/dist/js/scenes.js +320 -0
- package/dist/js/schrodinger.js +410 -0
- package/dist/js/schwarzschild.js +1015 -0
- package/dist/js/shapes.js +628 -0
- package/dist/js/space/alien.js +171 -0
- package/dist/js/space/boom.js +98 -0
- package/dist/js/space/boss.js +353 -0
- package/dist/js/space/buff.js +73 -0
- package/dist/js/space/bullet.js +102 -0
- package/dist/js/space/constants.js +85 -0
- package/dist/js/space/game.js +1884 -0
- package/dist/js/space/hud.js +112 -0
- package/dist/js/space/laserbeam.js +179 -0
- package/dist/js/space/lightning.js +277 -0
- package/dist/js/space/minion.js +192 -0
- package/dist/js/space/missile.js +212 -0
- package/dist/js/space/player.js +430 -0
- package/dist/js/space/powerup.js +90 -0
- package/dist/js/space/starfield.js +58 -0
- package/dist/js/space/starpower.js +90 -0
- package/dist/js/spacetime.js +559 -0
- package/dist/js/sphere3d.js +229 -0
- package/dist/js/sprite.js +473 -0
- package/dist/js/starfaux/config.js +118 -0
- package/dist/js/starfaux/enemy.js +353 -0
- package/dist/js/starfaux/hud.js +78 -0
- package/dist/js/starfaux/index.js +482 -0
- package/dist/js/starfaux/laser.js +182 -0
- package/dist/js/starfaux/player.js +468 -0
- package/dist/js/starfaux/terrain.js +560 -0
- package/dist/js/study001.js +275 -0
- package/dist/js/study002.js +366 -0
- package/dist/js/study003.js +331 -0
- package/dist/js/study004.js +389 -0
- package/dist/js/study005.js +209 -0
- package/dist/js/study006.js +194 -0
- package/dist/js/study007.js +192 -0
- package/dist/js/study008.js +413 -0
- package/dist/js/svgtween.js +204 -0
- package/dist/js/tde/accretiondisk.js +471 -0
- package/dist/js/tde/blackhole.js +219 -0
- package/dist/js/tde/blackholescene.js +209 -0
- package/dist/js/tde/config.js +59 -0
- package/dist/js/tde/index.js +820 -0
- package/dist/js/tde/jets.js +290 -0
- package/dist/js/tde/lensedstarfield.js +154 -0
- package/dist/js/tde/tdestar.js +297 -0
- package/dist/js/tde/tidalstream.js +372 -0
- package/dist/js/tde_old/blackhole.obj.js +354 -0
- package/dist/js/tde_old/debris.obj.js +791 -0
- package/dist/js/tde_old/flare.obj.js +239 -0
- package/dist/js/tde_old/index.js +448 -0
- package/dist/js/tde_old/star.obj.js +812 -0
- package/dist/js/tetris/config.js +157 -0
- package/dist/js/tetris/grid.js +286 -0
- package/dist/js/tetris/index.js +1195 -0
- package/dist/js/tetris/renderer.js +634 -0
- package/dist/js/tetris/tetrominos.js +280 -0
- package/dist/js/tiles.js +312 -0
- package/dist/js/tweendemo.js +79 -0
- package/dist/js/visibility.js +102 -0
- package/dist/kerr.html +28 -0
- package/dist/lavalamp.html +27 -0
- package/dist/layouts.html +37 -0
- package/dist/logo.svg +4 -0
- package/dist/loop.html +84 -0
- package/dist/mondrian.html +32 -0
- package/dist/og_image.png +0 -0
- package/dist/opacity.html +36 -0
- package/dist/painter.html +39 -0
- package/dist/particles-showcase.html +28 -0
- package/dist/particles.html +24 -0
- package/dist/patterns.html +33 -0
- package/dist/penrose-game.html +31 -0
- package/dist/pipeline.html +737 -0
- package/dist/plane3d.html +24 -0
- package/dist/platformer.html +43 -0
- package/dist/scene.html +33 -0
- package/dist/scenes.html +96 -0
- package/dist/schrodinger.html +27 -0
- package/dist/schwarzschild.html +27 -0
- package/dist/shapes.html +16 -0
- package/dist/space.html +85 -0
- package/dist/spacetime.html +27 -0
- package/dist/sphere3d.html +24 -0
- package/dist/sprite.html +18 -0
- package/dist/starfaux.html +22 -0
- package/dist/study001.html +23 -0
- package/dist/study002.html +23 -0
- package/dist/study003.html +23 -0
- package/dist/study004.html +23 -0
- package/dist/study005.html +22 -0
- package/dist/study006.html +24 -0
- package/dist/study007.html +24 -0
- package/dist/study008.html +22 -0
- package/dist/svgtween.html +29 -0
- package/dist/tde.html +28 -0
- package/dist/tetris3d.html +25 -0
- package/dist/tiles.html +28 -0
- package/dist/transforms.html +400 -0
- package/dist/tween.html +45 -0
- package/dist/visibility.html +33 -0
- package/package.json +1 -1
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VoidScene - Inside the Black Hole
|
|
3
|
+
*
|
|
4
|
+
* The void dimension where you end up after hitting a singularity
|
|
5
|
+
* with an artifact. Collect purple void essence to escape.
|
|
6
|
+
*
|
|
7
|
+
* @extends Scene
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Collision, Painter, Scene } from "/gcanvas.es.min.js";
|
|
11
|
+
import { CONFIG } from "./constants.js";
|
|
12
|
+
import { PenroseSounds } from "./sounds.js";
|
|
13
|
+
import { VoidParticle } from "./voidparticle.js";
|
|
14
|
+
import { VoidShip } from "./voidship.js";
|
|
15
|
+
|
|
16
|
+
export class VoidScene extends Scene {
|
|
17
|
+
constructor(game) {
|
|
18
|
+
super(game);
|
|
19
|
+
|
|
20
|
+
// Ship for void navigation
|
|
21
|
+
this.ship = new VoidShip(game);
|
|
22
|
+
this.add(this.ship);
|
|
23
|
+
|
|
24
|
+
// Collectible particles
|
|
25
|
+
this.particles = [];
|
|
26
|
+
this.particleSpawnTimer = 0;
|
|
27
|
+
this.particlesCollected = 0;
|
|
28
|
+
|
|
29
|
+
// Background swirl particles
|
|
30
|
+
this.background = [];
|
|
31
|
+
|
|
32
|
+
// Timer (limited time to escape)
|
|
33
|
+
this.timer = 0;
|
|
34
|
+
|
|
35
|
+
// Callbacks for game state transitions
|
|
36
|
+
this.onEscape = null;
|
|
37
|
+
this.onTimeout = null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Reset the void scene for a new entry
|
|
42
|
+
*/
|
|
43
|
+
reset() {
|
|
44
|
+
this.ship.reset();
|
|
45
|
+
this.particles = [];
|
|
46
|
+
this.particleSpawnTimer = 0;
|
|
47
|
+
this.particlesCollected = 0;
|
|
48
|
+
this.timer = 0;
|
|
49
|
+
|
|
50
|
+
// Generate swirling background
|
|
51
|
+
this.background = [];
|
|
52
|
+
for (let i = 0; i < 100; i++) {
|
|
53
|
+
this.background.push({
|
|
54
|
+
x: Math.random() * this.game.width,
|
|
55
|
+
y: Math.random() * this.game.height,
|
|
56
|
+
size: 1 + Math.random() * 3,
|
|
57
|
+
speed: 0.5 + Math.random() * 1.5,
|
|
58
|
+
angle: Math.random() * Math.PI * 2,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
update(dt) {
|
|
64
|
+
super.update(dt);
|
|
65
|
+
|
|
66
|
+
this.timer += dt;
|
|
67
|
+
|
|
68
|
+
// Spawn particles
|
|
69
|
+
this.particleSpawnTimer += dt;
|
|
70
|
+
if (this.particleSpawnTimer >= CONFIG.voidParticleSpawnRate) {
|
|
71
|
+
this.particleSpawnTimer = 0;
|
|
72
|
+
this.spawnParticle();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Update particles and check collection
|
|
76
|
+
const shipCircle = this.ship.getCircle();
|
|
77
|
+
|
|
78
|
+
for (const p of this.particles) {
|
|
79
|
+
p.update(dt);
|
|
80
|
+
|
|
81
|
+
if (p.active && Collision.circleCircle(shipCircle, p.getCircle())) {
|
|
82
|
+
p.collected = true;
|
|
83
|
+
this.particlesCollected++;
|
|
84
|
+
PenroseSounds.voidParticle();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Remove fully animated collected particles
|
|
89
|
+
this.particles = this.particles.filter(
|
|
90
|
+
(p) => !p.collected || p.collectAnimation < 1
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
// Update background swirl
|
|
94
|
+
for (const bg of this.background) {
|
|
95
|
+
bg.angle += bg.speed * dt * 0.5;
|
|
96
|
+
bg.x += Math.cos(bg.angle) * bg.speed;
|
|
97
|
+
bg.y += Math.sin(bg.angle) * bg.speed;
|
|
98
|
+
|
|
99
|
+
// Wrap around
|
|
100
|
+
if (bg.x < 0) bg.x = this.game.width;
|
|
101
|
+
if (bg.x > this.game.width) bg.x = 0;
|
|
102
|
+
if (bg.y < 0) bg.y = this.game.height;
|
|
103
|
+
if (bg.y > this.game.height) bg.y = 0;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Check win condition
|
|
107
|
+
if (this.particlesCollected >= CONFIG.voidParticlesToCollect) {
|
|
108
|
+
if (this.onEscape) this.onEscape();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Check timeout
|
|
112
|
+
if (this.timer >= CONFIG.voidDuration) {
|
|
113
|
+
if (this.onTimeout) this.onTimeout();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Spawn a particle at random position (not too close to ship)
|
|
119
|
+
*/
|
|
120
|
+
spawnParticle() {
|
|
121
|
+
let px, py;
|
|
122
|
+
do {
|
|
123
|
+
px = 50 + Math.random() * (this.game.width - 100);
|
|
124
|
+
py = 50 + Math.random() * (this.game.height - 100);
|
|
125
|
+
} while (Math.hypot(px - this.ship.x, py - this.ship.y) < 100);
|
|
126
|
+
|
|
127
|
+
this.particles.push(new VoidParticle(px, py));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
render() {
|
|
131
|
+
const ctx = Painter.ctx;
|
|
132
|
+
|
|
133
|
+
// Dark void background
|
|
134
|
+
ctx.fillStyle = "#020008";
|
|
135
|
+
ctx.fillRect(0, 0, this.game.width, this.game.height);
|
|
136
|
+
|
|
137
|
+
// Swirling background particles
|
|
138
|
+
this.renderBackground(ctx);
|
|
139
|
+
|
|
140
|
+
// Void particles
|
|
141
|
+
for (const p of this.particles) {
|
|
142
|
+
this.renderParticle(ctx, p);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Ship (handled by Scene's child rendering)
|
|
146
|
+
super.render();
|
|
147
|
+
|
|
148
|
+
// UI overlay
|
|
149
|
+
this.renderUI(ctx);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
renderBackground(ctx) {
|
|
153
|
+
for (const bg of this.background) {
|
|
154
|
+
ctx.fillStyle = `rgba(50, 20, 80, ${0.3 + Math.sin(bg.angle) * 0.2})`;
|
|
155
|
+
ctx.beginPath();
|
|
156
|
+
ctx.arc(bg.x, bg.y, bg.size, 0, Math.PI * 2);
|
|
157
|
+
ctx.fill();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
renderParticle(ctx, p) {
|
|
162
|
+
const pulse = 0.7 + Math.sin(p.pulsePhase) * 0.3;
|
|
163
|
+
|
|
164
|
+
if (p.collected) {
|
|
165
|
+
// Collection animation - expand and fade
|
|
166
|
+
const scale = 1 + p.collectAnimation * 2;
|
|
167
|
+
const alpha = 1 - p.collectAnimation;
|
|
168
|
+
|
|
169
|
+
ctx.fillStyle = `rgba(200, 100, 255, ${alpha * 0.5})`;
|
|
170
|
+
ctx.beginPath();
|
|
171
|
+
ctx.arc(p.x, p.y, p.radius * scale * 2, 0, Math.PI * 2);
|
|
172
|
+
ctx.fill();
|
|
173
|
+
} else {
|
|
174
|
+
// Outer glow
|
|
175
|
+
const gradient = ctx.createRadialGradient(
|
|
176
|
+
p.x, p.y, 0,
|
|
177
|
+
p.x, p.y, p.radius * 2
|
|
178
|
+
);
|
|
179
|
+
gradient.addColorStop(0, `rgba(200, 100, 255, ${0.9 * pulse})`);
|
|
180
|
+
gradient.addColorStop(0.5, `rgba(150, 50, 200, ${0.5 * pulse})`);
|
|
181
|
+
gradient.addColorStop(1, "rgba(100, 0, 150, 0)");
|
|
182
|
+
|
|
183
|
+
ctx.fillStyle = gradient;
|
|
184
|
+
ctx.beginPath();
|
|
185
|
+
ctx.arc(p.x, p.y, p.radius * 2, 0, Math.PI * 2);
|
|
186
|
+
ctx.fill();
|
|
187
|
+
|
|
188
|
+
// Core
|
|
189
|
+
ctx.fillStyle = `rgba(255, 200, 255, ${pulse})`;
|
|
190
|
+
ctx.beginPath();
|
|
191
|
+
ctx.arc(p.x, p.y, p.radius * 0.5, 0, Math.PI * 2);
|
|
192
|
+
ctx.fill();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
renderUI(ctx) {
|
|
197
|
+
const width = this.game.width;
|
|
198
|
+
const height = this.game.height;
|
|
199
|
+
|
|
200
|
+
// Progress bar at top
|
|
201
|
+
const barWidth = 300;
|
|
202
|
+
const barHeight = 20;
|
|
203
|
+
const barX = (width - barWidth) / 2;
|
|
204
|
+
const barY = 30;
|
|
205
|
+
|
|
206
|
+
// Background
|
|
207
|
+
ctx.fillStyle = "rgba(50, 20, 80, 0.8)";
|
|
208
|
+
ctx.fillRect(barX - 2, barY - 2, barWidth + 4, barHeight + 4);
|
|
209
|
+
|
|
210
|
+
// Progress fill
|
|
211
|
+
const progress = this.particlesCollected / CONFIG.voidParticlesToCollect;
|
|
212
|
+
const progressWidth = barWidth * progress;
|
|
213
|
+
|
|
214
|
+
const gradient = ctx.createLinearGradient(barX, 0, barX + barWidth, 0);
|
|
215
|
+
gradient.addColorStop(0, "#a0f");
|
|
216
|
+
gradient.addColorStop(1, "#f0f");
|
|
217
|
+
ctx.fillStyle = gradient;
|
|
218
|
+
ctx.fillRect(barX, barY, progressWidth, barHeight);
|
|
219
|
+
|
|
220
|
+
// Border
|
|
221
|
+
ctx.strokeStyle = "#c0f";
|
|
222
|
+
ctx.lineWidth = 2;
|
|
223
|
+
ctx.strokeRect(barX, barY, barWidth, barHeight);
|
|
224
|
+
|
|
225
|
+
// Text
|
|
226
|
+
ctx.fillStyle = "#fff";
|
|
227
|
+
ctx.font = "bold 14px monospace";
|
|
228
|
+
ctx.textAlign = "center";
|
|
229
|
+
ctx.fillText(
|
|
230
|
+
`VOID ESSENCE: ${this.particlesCollected} / ${CONFIG.voidParticlesToCollect}`,
|
|
231
|
+
width / 2,
|
|
232
|
+
barY + barHeight + 20
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
// Timer warning
|
|
236
|
+
const timeLeft = CONFIG.voidDuration - this.timer;
|
|
237
|
+
if (timeLeft < 10) {
|
|
238
|
+
const flash = Math.sin(Date.now() / 100) > 0;
|
|
239
|
+
ctx.fillStyle = flash ? "#f00" : "#800";
|
|
240
|
+
ctx.font = "bold 18px monospace";
|
|
241
|
+
ctx.fillText(`TIME: ${timeLeft.toFixed(1)}s`, width / 2, barY + barHeight + 50);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Instructions
|
|
245
|
+
ctx.fillStyle = "#808";
|
|
246
|
+
ctx.font = "12px monospace";
|
|
247
|
+
ctx.fillText(
|
|
248
|
+
"WASD / Arrows to move — Collect purple essence to escape!",
|
|
249
|
+
width / 2,
|
|
250
|
+
height - 30
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
// Title
|
|
254
|
+
ctx.fillStyle = "#c0f";
|
|
255
|
+
ctx.font = "bold 24px monospace";
|
|
256
|
+
ctx.fillText("INSIDE THE SINGULARITY", width / 2, 80);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VoidShip - Player ship in the void dimension (inside black hole)
|
|
3
|
+
*
|
|
4
|
+
* A purple-tinted ship for navigating the singularity void.
|
|
5
|
+
* Uses direct position control (no physics) - the void doesn't
|
|
6
|
+
* follow normal spacetime rules.
|
|
7
|
+
*
|
|
8
|
+
* @extends GameObject
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { GameObject, Group, Keys, Rectangle } from "/gcanvas.es.min.js";
|
|
12
|
+
import { CONFIG } from "./constants.js";
|
|
13
|
+
|
|
14
|
+
const COLLISION_RADIUS = 15;
|
|
15
|
+
|
|
16
|
+
export class VoidShip extends GameObject {
|
|
17
|
+
constructor(game) {
|
|
18
|
+
super(game);
|
|
19
|
+
|
|
20
|
+
// Screen coordinates (not Penrose - we're inside the singularity)
|
|
21
|
+
this.x = 0;
|
|
22
|
+
this.y = 0;
|
|
23
|
+
|
|
24
|
+
// Build ship visual (purple-tinted version of main ship)
|
|
25
|
+
this.shipGroup = new Group({});
|
|
26
|
+
|
|
27
|
+
// Main body
|
|
28
|
+
const body = new Rectangle({
|
|
29
|
+
width: 12,
|
|
30
|
+
height: 16,
|
|
31
|
+
color: "#a0f",
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Nose
|
|
35
|
+
const nose = new Rectangle({
|
|
36
|
+
width: 4,
|
|
37
|
+
height: 8,
|
|
38
|
+
y: -10,
|
|
39
|
+
color: "#a0f",
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Wings
|
|
43
|
+
const leftWing = new Rectangle({
|
|
44
|
+
width: 8,
|
|
45
|
+
height: 6,
|
|
46
|
+
x: -10,
|
|
47
|
+
y: 4,
|
|
48
|
+
color: "#80a",
|
|
49
|
+
});
|
|
50
|
+
const rightWing = new Rectangle({
|
|
51
|
+
width: 8,
|
|
52
|
+
height: 6,
|
|
53
|
+
x: 10,
|
|
54
|
+
y: 4,
|
|
55
|
+
color: "#80a",
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Engine glow (purple flames)
|
|
59
|
+
this.engineLeft = new Rectangle({
|
|
60
|
+
width: 4,
|
|
61
|
+
height: 4,
|
|
62
|
+
x: -4,
|
|
63
|
+
y: 10,
|
|
64
|
+
color: "#f0f",
|
|
65
|
+
});
|
|
66
|
+
this.engineRight = new Rectangle({
|
|
67
|
+
width: 4,
|
|
68
|
+
height: 4,
|
|
69
|
+
x: 4,
|
|
70
|
+
y: 10,
|
|
71
|
+
color: "#c0f",
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
this.shipGroup.add(body);
|
|
75
|
+
this.shipGroup.add(nose);
|
|
76
|
+
this.shipGroup.add(leftWing);
|
|
77
|
+
this.shipGroup.add(rightWing);
|
|
78
|
+
this.shipGroup.add(this.engineLeft);
|
|
79
|
+
this.shipGroup.add(this.engineRight);
|
|
80
|
+
|
|
81
|
+
this.engineTimer = 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Reset ship to center of screen
|
|
86
|
+
*/
|
|
87
|
+
reset() {
|
|
88
|
+
this.x = this.game.width / 2;
|
|
89
|
+
this.y = this.game.height / 2;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Update ship position based on input
|
|
94
|
+
* Direct position control - no velocity/physics in the void
|
|
95
|
+
* @param {number} dt - Delta time in seconds
|
|
96
|
+
*/
|
|
97
|
+
update(dt) {
|
|
98
|
+
super.update(dt);
|
|
99
|
+
|
|
100
|
+
const leftPressed = Keys.isDown("a") || Keys.isDown("A") || Keys.isDown(Keys.LEFT);
|
|
101
|
+
const rightPressed = Keys.isDown("d") || Keys.isDown("D") || Keys.isDown(Keys.RIGHT);
|
|
102
|
+
const upPressed = Keys.isDown("w") || Keys.isDown("W") || Keys.isDown(Keys.UP);
|
|
103
|
+
const downPressed = Keys.isDown("s") || Keys.isDown("S") || Keys.isDown(Keys.DOWN);
|
|
104
|
+
|
|
105
|
+
const speed = CONFIG.voidShipSpeed;
|
|
106
|
+
|
|
107
|
+
if (leftPressed) this.x -= speed * dt;
|
|
108
|
+
if (rightPressed) this.x += speed * dt;
|
|
109
|
+
if (upPressed) this.y -= speed * dt;
|
|
110
|
+
if (downPressed) this.y += speed * dt;
|
|
111
|
+
|
|
112
|
+
// Clamp to screen bounds (with margin)
|
|
113
|
+
const margin = 30;
|
|
114
|
+
this.x = Math.max(margin, Math.min(this.game.width - margin, this.x));
|
|
115
|
+
this.y = Math.max(margin, Math.min(this.game.height - margin, this.y));
|
|
116
|
+
|
|
117
|
+
// Engine flicker
|
|
118
|
+
this.engineTimer += dt * 20;
|
|
119
|
+
const flicker = Math.sin(this.engineTimer) > 0;
|
|
120
|
+
this.engineLeft.color = flicker ? "#f0f" : "#c0f";
|
|
121
|
+
this.engineRight.color = flicker ? "#c0f" : "#f0f";
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Render the void ship
|
|
126
|
+
*/
|
|
127
|
+
render() {
|
|
128
|
+
this.shipGroup.x = this.x;
|
|
129
|
+
this.shipGroup.y = this.y;
|
|
130
|
+
this.shipGroup.render();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Get collision circle for this ship (screen coordinates)
|
|
135
|
+
* @returns {{ x: number, y: number, radius: number }}
|
|
136
|
+
*/
|
|
137
|
+
getCircle() {
|
|
138
|
+
return {
|
|
139
|
+
x: this.x,
|
|
140
|
+
y: this.y,
|
|
141
|
+
radius: COLLISION_RADIUS,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Penrose Wormhole
|
|
3
|
+
*
|
|
4
|
+
* A rare object that teleports the player back to the start of the level,
|
|
5
|
+
* allowing them to farm more points while keeping their score and multiplier!
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { CONFIG } from "./constants.js";
|
|
9
|
+
|
|
10
|
+
export class PenroseWormhole {
|
|
11
|
+
constructor(u, v) {
|
|
12
|
+
this.u = u;
|
|
13
|
+
this.v = v;
|
|
14
|
+
|
|
15
|
+
// Wormhole size
|
|
16
|
+
this.radius = CONFIG.wormholeRadius;
|
|
17
|
+
|
|
18
|
+
// Animation
|
|
19
|
+
this.rotationPhase = Math.random() * Math.PI * 2;
|
|
20
|
+
this.pulsePhase = Math.random() * Math.PI * 2;
|
|
21
|
+
|
|
22
|
+
// State
|
|
23
|
+
this.used = false; // Once used, disappears
|
|
24
|
+
this.spawnTime = 0; // For fade-in effect
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
update(dt) {
|
|
28
|
+
this.rotationPhase += dt * 3; // Faster rotation than black holes
|
|
29
|
+
this.pulsePhase += dt * 5;
|
|
30
|
+
this.spawnTime += dt;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Get circle bounds for collision detection
|
|
35
|
+
*/
|
|
36
|
+
getCircle() {
|
|
37
|
+
return { x: this.u, y: this.v, radius: this.radius };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Check if wormhole is active (not used)
|
|
42
|
+
*/
|
|
43
|
+
get active() {
|
|
44
|
+
return !this.used;
|
|
45
|
+
}
|
|
46
|
+
}
|