@guinetik/gcanvas 1.0.0
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/.github/workflows/release.yaml +70 -0
- package/.jshintrc +4 -0
- package/.vscode/settings.json +22 -0
- package/CLAUDE.md +310 -0
- package/blackhole.jpg +0 -0
- package/demo.png +0 -0
- package/demos/CNAME +1 -0
- package/demos/animations.html +31 -0
- package/demos/basic.html +38 -0
- package/demos/baskara.html +31 -0
- package/demos/bezier.html +35 -0
- package/demos/beziersignature.html +29 -0
- package/demos/blackhole.html +28 -0
- package/demos/blob.html +35 -0
- package/demos/demos.css +289 -0
- package/demos/easing.html +28 -0
- package/demos/events.html +195 -0
- package/demos/fluent.html +647 -0
- package/demos/fractals.html +36 -0
- package/demos/genart.html +26 -0
- package/demos/gendream.html +26 -0
- package/demos/group.html +36 -0
- package/demos/home.html +587 -0
- package/demos/index.html +364 -0
- package/demos/isometric.html +34 -0
- package/demos/js/animations.js +452 -0
- package/demos/js/basic.js +204 -0
- package/demos/js/baskara.js +751 -0
- package/demos/js/bezier.js +692 -0
- package/demos/js/beziersignature.js +241 -0
- package/demos/js/blackhole/accretiondisk.obj.js +379 -0
- package/demos/js/blackhole/blackhole.obj.js +318 -0
- package/demos/js/blackhole/index.js +409 -0
- package/demos/js/blackhole/particle.js +56 -0
- package/demos/js/blackhole/starfield.obj.js +218 -0
- package/demos/js/blob.js +2263 -0
- package/demos/js/easing.js +477 -0
- package/demos/js/fluent.js +183 -0
- package/demos/js/fractals.js +931 -0
- package/demos/js/fractalworker.js +93 -0
- package/demos/js/genart.js +268 -0
- package/demos/js/gendream.js +209 -0
- package/demos/js/group.js +140 -0
- package/demos/js/info-toggle.js +25 -0
- package/demos/js/isometric.js +863 -0
- package/demos/js/kerr.js +1556 -0
- package/demos/js/lavalamp.js +590 -0
- package/demos/js/layout.js +354 -0
- package/demos/js/mondrian.js +285 -0
- package/demos/js/opacity.js +275 -0
- package/demos/js/painter.js +484 -0
- package/demos/js/particles-showcase.js +514 -0
- package/demos/js/particles.js +299 -0
- package/demos/js/patterns.js +397 -0
- package/demos/js/penrose/artifact.js +69 -0
- package/demos/js/penrose/blackhole.js +121 -0
- package/demos/js/penrose/constants.js +73 -0
- package/demos/js/penrose/game.js +943 -0
- package/demos/js/penrose/lore.js +278 -0
- package/demos/js/penrose/penrosescene.js +892 -0
- package/demos/js/penrose/ship.js +216 -0
- package/demos/js/penrose/sounds.js +211 -0
- package/demos/js/penrose/voidparticle.js +55 -0
- package/demos/js/penrose/voidscene.js +258 -0
- package/demos/js/penrose/voidship.js +144 -0
- package/demos/js/penrose/wormhole.js +46 -0
- package/demos/js/pipeline.js +555 -0
- package/demos/js/scene.js +304 -0
- package/demos/js/scenes.js +320 -0
- package/demos/js/schrodinger.js +410 -0
- package/demos/js/schwarzschild.js +1023 -0
- package/demos/js/shapes.js +628 -0
- package/demos/js/space/alien.js +171 -0
- package/demos/js/space/boom.js +98 -0
- package/demos/js/space/boss.js +353 -0
- package/demos/js/space/buff.js +73 -0
- package/demos/js/space/bullet.js +102 -0
- package/demos/js/space/constants.js +85 -0
- package/demos/js/space/game.js +1884 -0
- package/demos/js/space/hud.js +112 -0
- package/demos/js/space/laserbeam.js +179 -0
- package/demos/js/space/lightning.js +277 -0
- package/demos/js/space/minion.js +192 -0
- package/demos/js/space/missile.js +212 -0
- package/demos/js/space/player.js +430 -0
- package/demos/js/space/powerup.js +90 -0
- package/demos/js/space/starfield.js +58 -0
- package/demos/js/space/starpower.js +90 -0
- package/demos/js/spacetime.js +559 -0
- package/demos/js/svgtween.js +204 -0
- package/demos/js/tde/accretiondisk.js +418 -0
- package/demos/js/tde/blackhole.js +219 -0
- package/demos/js/tde/blackholescene.js +209 -0
- package/demos/js/tde/config.js +59 -0
- package/demos/js/tde/index.js +695 -0
- package/demos/js/tde/jets.js +290 -0
- package/demos/js/tde/lensedstarfield.js +147 -0
- package/demos/js/tde/tdestar.js +317 -0
- package/demos/js/tde/tidalstream.js +356 -0
- package/demos/js/tde_old/blackhole.obj.js +354 -0
- package/demos/js/tde_old/debris.obj.js +791 -0
- package/demos/js/tde_old/flare.obj.js +239 -0
- package/demos/js/tde_old/index.js +448 -0
- package/demos/js/tde_old/star.obj.js +812 -0
- package/demos/js/tiles.js +312 -0
- package/demos/js/tweendemo.js +79 -0
- package/demos/js/visibility.js +102 -0
- package/demos/kerr.html +28 -0
- package/demos/lavalamp.html +27 -0
- package/demos/layouts.html +37 -0
- package/demos/logo.svg +4 -0
- package/demos/loop.html +84 -0
- package/demos/mondrian.html +32 -0
- package/demos/og_image.png +0 -0
- package/demos/opacity.html +36 -0
- package/demos/painter.html +39 -0
- package/demos/particles-showcase.html +28 -0
- package/demos/particles.html +24 -0
- package/demos/patterns.html +33 -0
- package/demos/penrose-game.html +31 -0
- package/demos/pipeline.html +737 -0
- package/demos/scene.html +33 -0
- package/demos/scenes.html +96 -0
- package/demos/schrodinger.html +27 -0
- package/demos/schwarzschild.html +27 -0
- package/demos/shapes.html +16 -0
- package/demos/space.html +85 -0
- package/demos/spacetime.html +27 -0
- package/demos/svgtween.html +29 -0
- package/demos/tde.html +28 -0
- package/demos/tiles.html +28 -0
- package/demos/transforms.html +400 -0
- package/demos/tween.html +45 -0
- package/demos/visibility.html +33 -0
- package/disk_example.png +0 -0
- package/docs/README.md +222 -0
- package/docs/concepts/architecture-overview.md +204 -0
- package/docs/concepts/lifecycle.md +255 -0
- package/docs/concepts/rendering-pipeline.md +279 -0
- package/docs/concepts/tde-zorder.md +106 -0
- package/docs/concepts/two-layer-architecture.md +229 -0
- package/docs/getting-started/first-game.md +354 -0
- package/docs/getting-started/hello-world.md +269 -0
- package/docs/getting-started/installation.md +157 -0
- package/docs/modules/collision/README.md +453 -0
- package/docs/modules/fluent/README.md +1075 -0
- package/docs/modules/game/README.md +303 -0
- package/docs/modules/isometric-camera.md +210 -0
- package/docs/modules/isometric.md +275 -0
- package/docs/modules/painter/README.md +328 -0
- package/docs/modules/particle/README.md +559 -0
- package/docs/modules/shapes/README.md +221 -0
- package/docs/modules/shapes/base/euclidian.md +123 -0
- package/docs/modules/shapes/base/geometry2d.md +204 -0
- package/docs/modules/shapes/base/renderable.md +215 -0
- package/docs/modules/shapes/base/shape.md +262 -0
- package/docs/modules/shapes/base/transformable.md +243 -0
- package/docs/modules/shapes/hierarchy.md +218 -0
- package/docs/modules/state/README.md +577 -0
- package/docs/modules/util/README.md +99 -0
- package/docs/modules/util/camera3d.md +412 -0
- package/docs/modules/util/scene3d.md +395 -0
- package/index.html +17 -0
- package/jsdoc.json +50 -0
- package/package.json +55 -0
- package/readme.md +599 -0
- package/scripts/build-demo.js +69 -0
- package/scripts/bundle4llm.js +276 -0
- package/scripts/clearconsole.js +48 -0
- package/src/collision/collision-system.js +332 -0
- package/src/collision/collision.js +303 -0
- package/src/collision/index.js +10 -0
- package/src/fluent/fluent-game.js +430 -0
- package/src/fluent/fluent-go.js +1060 -0
- package/src/fluent/fluent-layer.js +152 -0
- package/src/fluent/fluent-scene.js +291 -0
- package/src/fluent/index.js +98 -0
- package/src/fluent/sketch.js +380 -0
- package/src/game/game.js +467 -0
- package/src/game/index.js +49 -0
- package/src/game/objects/go.js +220 -0
- package/src/game/objects/imagego.js +30 -0
- package/src/game/objects/index.js +54 -0
- package/src/game/objects/isometric-scene.js +260 -0
- package/src/game/objects/layoutscene.js +549 -0
- package/src/game/objects/scene.js +175 -0
- package/src/game/objects/scene3d.js +118 -0
- package/src/game/objects/text.js +221 -0
- package/src/game/objects/wrapper.js +232 -0
- package/src/game/pipeline.js +243 -0
- package/src/game/ui/button.js +396 -0
- package/src/game/ui/cursor.js +93 -0
- package/src/game/ui/fps.js +91 -0
- package/src/game/ui/index.js +5 -0
- package/src/game/ui/togglebutton.js +93 -0
- package/src/game/ui/tooltip.js +249 -0
- package/src/index.js +25 -0
- package/src/io/events.js +20 -0
- package/src/io/index.js +86 -0
- package/src/io/input.js +70 -0
- package/src/io/keys.js +152 -0
- package/src/io/mouse.js +61 -0
- package/src/io/touch.js +39 -0
- package/src/logger/debugtab.js +138 -0
- package/src/logger/index.js +3 -0
- package/src/logger/loggable.js +47 -0
- package/src/logger/logger.js +113 -0
- package/src/math/complex.js +37 -0
- package/src/math/constants.js +1 -0
- package/src/math/fractal.js +1271 -0
- package/src/math/gr.js +201 -0
- package/src/math/heat.js +202 -0
- package/src/math/index.js +12 -0
- package/src/math/noise.js +433 -0
- package/src/math/orbital.js +191 -0
- package/src/math/patterns.js +1339 -0
- package/src/math/penrose.js +259 -0
- package/src/math/quantum.js +115 -0
- package/src/math/random.js +195 -0
- package/src/math/tensor.js +1009 -0
- package/src/mixins/anchor.js +131 -0
- package/src/mixins/draggable.js +72 -0
- package/src/mixins/index.js +2 -0
- package/src/motion/bezier.js +132 -0
- package/src/motion/bounce.js +58 -0
- package/src/motion/easing.js +349 -0
- package/src/motion/float.js +130 -0
- package/src/motion/follow.js +125 -0
- package/src/motion/hop.js +52 -0
- package/src/motion/index.js +82 -0
- package/src/motion/motion.js +1124 -0
- package/src/motion/orbit.js +49 -0
- package/src/motion/oscillate.js +39 -0
- package/src/motion/parabolic.js +141 -0
- package/src/motion/patrol.js +147 -0
- package/src/motion/pendulum.js +48 -0
- package/src/motion/pulse.js +88 -0
- package/src/motion/shake.js +83 -0
- package/src/motion/spiral.js +144 -0
- package/src/motion/spring.js +150 -0
- package/src/motion/swing.js +47 -0
- package/src/motion/tween.js +92 -0
- package/src/motion/tweenetik.js +139 -0
- package/src/motion/waypoint.js +210 -0
- package/src/painter/index.js +8 -0
- package/src/painter/painter.colors.js +331 -0
- package/src/painter/painter.effects.js +230 -0
- package/src/painter/painter.img.js +229 -0
- package/src/painter/painter.js +295 -0
- package/src/painter/painter.lines.js +189 -0
- package/src/painter/painter.opacity.js +41 -0
- package/src/painter/painter.shapes.js +277 -0
- package/src/painter/painter.text.js +273 -0
- package/src/particle/emitter.js +124 -0
- package/src/particle/index.js +11 -0
- package/src/particle/particle-system.js +322 -0
- package/src/particle/particle.js +71 -0
- package/src/particle/updaters.js +170 -0
- package/src/shapes/arc.js +43 -0
- package/src/shapes/arrow.js +33 -0
- package/src/shapes/bezier.js +42 -0
- package/src/shapes/circle.js +62 -0
- package/src/shapes/clouds.js +56 -0
- package/src/shapes/cone.js +219 -0
- package/src/shapes/cross.js +70 -0
- package/src/shapes/cube.js +244 -0
- package/src/shapes/cylinder.js +254 -0
- package/src/shapes/diamond.js +48 -0
- package/src/shapes/euclidian.js +111 -0
- package/src/shapes/figure.js +115 -0
- package/src/shapes/geometry.js +220 -0
- package/src/shapes/group.js +375 -0
- package/src/shapes/heart.js +42 -0
- package/src/shapes/hexagon.js +26 -0
- package/src/shapes/image.js +192 -0
- package/src/shapes/index.js +111 -0
- package/src/shapes/line.js +29 -0
- package/src/shapes/pattern.js +90 -0
- package/src/shapes/pin.js +44 -0
- package/src/shapes/poly.js +31 -0
- package/src/shapes/prism.js +226 -0
- package/src/shapes/rect.js +35 -0
- package/src/shapes/renderable.js +333 -0
- package/src/shapes/ring.js +26 -0
- package/src/shapes/roundrect.js +95 -0
- package/src/shapes/shape.js +117 -0
- package/src/shapes/slice.js +26 -0
- package/src/shapes/sphere.js +314 -0
- package/src/shapes/sphere3d.js +537 -0
- package/src/shapes/square.js +15 -0
- package/src/shapes/star.js +99 -0
- package/src/shapes/svg.js +408 -0
- package/src/shapes/text.js +553 -0
- package/src/shapes/traceable.js +83 -0
- package/src/shapes/transform.js +357 -0
- package/src/shapes/transformable.js +172 -0
- package/src/shapes/triangle.js +26 -0
- package/src/sound/index.js +17 -0
- package/src/sound/sound.js +473 -0
- package/src/sound/synth.analyzer.js +149 -0
- package/src/sound/synth.effects.js +207 -0
- package/src/sound/synth.envelope.js +59 -0
- package/src/sound/synth.js +229 -0
- package/src/sound/synth.musical.js +160 -0
- package/src/sound/synth.noise.js +85 -0
- package/src/sound/synth.oscillators.js +293 -0
- package/src/state/index.js +10 -0
- package/src/state/state-machine.js +371 -0
- package/src/util/camera3d.js +438 -0
- package/src/util/index.js +6 -0
- package/src/util/isometric-camera.js +235 -0
- package/src/util/layout.js +317 -0
- package/src/util/position.js +147 -0
- package/src/util/tasks.js +47 -0
- package/src/util/zindex.js +287 -0
- package/src/webgl/index.js +9 -0
- package/src/webgl/shaders/sphere-shaders.js +994 -0
- package/src/webgl/webgl-renderer.js +388 -0
- package/tde.png +0 -0
- package/test/math/orbital.test.js +61 -0
- package/test/math/tensor.test.js +114 -0
- package/test/particle/emitter.test.js +204 -0
- package/test/particle/particle-system.test.js +310 -0
- package/test/particle/particle.test.js +116 -0
- package/test/particle/updaters.test.js +386 -0
- package/test/setup.js +120 -0
- package/test/shapes/euclidian.test.js +44 -0
- package/test/shapes/geometry.test.js +86 -0
- package/test/shapes/group.test.js +86 -0
- package/test/shapes/rectangle.test.js +64 -0
- package/test/shapes/transform.test.js +379 -0
- package/test/util/camera3d.test.js +428 -0
- package/test/util/scene3d.test.js +352 -0
- package/types/collision.d.ts +249 -0
- package/types/common.d.ts +155 -0
- package/types/game.d.ts +497 -0
- package/types/index.d.ts +309 -0
- package/types/io.d.ts +188 -0
- package/types/logger.d.ts +127 -0
- package/types/math.d.ts +268 -0
- package/types/mixins.d.ts +92 -0
- package/types/motion.d.ts +678 -0
- package/types/painter.d.ts +378 -0
- package/types/shapes.d.ts +864 -0
- package/types/sound.d.ts +672 -0
- package/types/state.d.ts +251 -0
- package/types/util.d.ts +253 -0
- package/vite.config.js +50 -0
- package/vitest.config.js +13 -0
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
import { GameObject, Rectangle, Circle, Group, Keys, Painter } from "../../../src/index.js";
|
|
2
|
+
import {
|
|
3
|
+
PLAYER_SPEED,
|
|
4
|
+
PLAYER_WIDTH,
|
|
5
|
+
PLAYER_HEIGHT,
|
|
6
|
+
STARPOWER_DURATION,
|
|
7
|
+
STARPOWER_SPEED_MULTIPLIER,
|
|
8
|
+
SHIELD_MAX_ENERGY,
|
|
9
|
+
SHIELD_DRAIN_RATE,
|
|
10
|
+
SHIELD_RECHARGE_RATE,
|
|
11
|
+
SHIELD_RECHARGE_DELAY,
|
|
12
|
+
} from "./constants.js";
|
|
13
|
+
|
|
14
|
+
export class Player extends GameObject {
|
|
15
|
+
constructor(game, options = {}) {
|
|
16
|
+
super(game, {
|
|
17
|
+
width: PLAYER_WIDTH,
|
|
18
|
+
height: PLAYER_HEIGHT,
|
|
19
|
+
...options,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Classic arcade-style spaceship using grouped shapes
|
|
23
|
+
this.ship = new Group({});
|
|
24
|
+
|
|
25
|
+
// Main hull (center body)
|
|
26
|
+
const hull = new Rectangle({
|
|
27
|
+
width: 16,
|
|
28
|
+
height: 12,
|
|
29
|
+
y: 2,
|
|
30
|
+
color: "#00ff00",
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Cannon (top center)
|
|
34
|
+
const cannon = new Rectangle({
|
|
35
|
+
width: 4,
|
|
36
|
+
height: 10,
|
|
37
|
+
y: -8,
|
|
38
|
+
color: "#00ff00",
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Cannon tip
|
|
42
|
+
const cannonTip = new Rectangle({
|
|
43
|
+
width: 2,
|
|
44
|
+
height: 4,
|
|
45
|
+
y: -14,
|
|
46
|
+
color: "#88ff88",
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Left wing
|
|
50
|
+
const leftWing = new Rectangle({
|
|
51
|
+
width: 10,
|
|
52
|
+
height: 6,
|
|
53
|
+
x: -13,
|
|
54
|
+
y: 5,
|
|
55
|
+
color: "#00dd00",
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Right wing
|
|
59
|
+
const rightWing = new Rectangle({
|
|
60
|
+
width: 10,
|
|
61
|
+
height: 6,
|
|
62
|
+
x: 13,
|
|
63
|
+
y: 5,
|
|
64
|
+
color: "#00dd00",
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Left wing detail
|
|
68
|
+
const leftWingTip = new Rectangle({
|
|
69
|
+
width: 4,
|
|
70
|
+
height: 4,
|
|
71
|
+
x: -19,
|
|
72
|
+
y: 6,
|
|
73
|
+
color: "#00aa00",
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Right wing detail
|
|
77
|
+
const rightWingTip = new Rectangle({
|
|
78
|
+
width: 4,
|
|
79
|
+
height: 4,
|
|
80
|
+
x: 19,
|
|
81
|
+
y: 6,
|
|
82
|
+
color: "#00aa00",
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Engine glow left
|
|
86
|
+
const engineLeft = new Rectangle({
|
|
87
|
+
width: 4,
|
|
88
|
+
height: 4,
|
|
89
|
+
x: -6,
|
|
90
|
+
y: 10,
|
|
91
|
+
color: "#ffaa00",
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Engine glow right
|
|
95
|
+
const engineRight = new Rectangle({
|
|
96
|
+
width: 4,
|
|
97
|
+
height: 4,
|
|
98
|
+
x: 6,
|
|
99
|
+
y: 10,
|
|
100
|
+
color: "#ffaa00",
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
this.ship.add(hull);
|
|
104
|
+
this.ship.add(cannon);
|
|
105
|
+
this.ship.add(cannonTip);
|
|
106
|
+
this.ship.add(leftWing);
|
|
107
|
+
this.ship.add(rightWing);
|
|
108
|
+
this.ship.add(leftWingTip);
|
|
109
|
+
this.ship.add(rightWingTip);
|
|
110
|
+
this.ship.add(engineLeft);
|
|
111
|
+
this.ship.add(engineRight);
|
|
112
|
+
|
|
113
|
+
// Store refs for animation and color changes
|
|
114
|
+
this.engineLeft = engineLeft;
|
|
115
|
+
this.engineRight = engineRight;
|
|
116
|
+
this.engineTimer = 0;
|
|
117
|
+
this.cannon = cannon;
|
|
118
|
+
this.cannonTip = cannonTip;
|
|
119
|
+
this.leftWing = leftWing;
|
|
120
|
+
this.rightWing = rightWing;
|
|
121
|
+
this.leftWingTip = leftWingTip;
|
|
122
|
+
this.rightWingTip = rightWingTip;
|
|
123
|
+
|
|
124
|
+
// Banking/tilt when moving
|
|
125
|
+
this.targetTilt = 0;
|
|
126
|
+
this.currentTilt = 0;
|
|
127
|
+
this.maxTilt = 15; // Max degrees of rotation
|
|
128
|
+
this.tiltSpeed = 8; // How fast to reach target tilt
|
|
129
|
+
|
|
130
|
+
this.canShoot = true;
|
|
131
|
+
this.shootCooldown = 0.25; // seconds (base cooldown)
|
|
132
|
+
this.shootTimer = 0;
|
|
133
|
+
|
|
134
|
+
// Star power state
|
|
135
|
+
this.starPower = false;
|
|
136
|
+
this.starPowerTimer = 0;
|
|
137
|
+
this.starPowerFlash = 0;
|
|
138
|
+
|
|
139
|
+
// Store hull ref for star power glow
|
|
140
|
+
this.hull = hull;
|
|
141
|
+
this.originalHullColor = "#00ff00";
|
|
142
|
+
|
|
143
|
+
// Upgrades - applied as player progresses
|
|
144
|
+
this.speedMultiplier = 1.0; // Movement speed multiplier
|
|
145
|
+
this.shootCooldownMultiplier = 1.0; // Lower = faster shooting
|
|
146
|
+
this.tripleShot = false; // Shoots 3 bullets in spread pattern
|
|
147
|
+
|
|
148
|
+
// Shield system (unlocked after level 9 boss)
|
|
149
|
+
this.shieldUnlocked = false;
|
|
150
|
+
this.shieldActive = false;
|
|
151
|
+
this.shieldEnergy = SHIELD_MAX_ENERGY;
|
|
152
|
+
this.shieldRechargeDelay = 0; // Time before recharge starts
|
|
153
|
+
this.shieldFlash = 0; // For visual effect
|
|
154
|
+
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Apply upgrade after completing a level
|
|
159
|
+
* Ship color progression: Green → Blue → Yellow → Red
|
|
160
|
+
*/
|
|
161
|
+
applyUpgrade(upgradeType) {
|
|
162
|
+
switch (upgradeType) {
|
|
163
|
+
case "speed1":
|
|
164
|
+
// After level 3 boss - faster movement, BLUE ship
|
|
165
|
+
this.speedMultiplier = 1.4; // 40% faster (very noticeable)
|
|
166
|
+
this.hull.color = "#0088ff";
|
|
167
|
+
this.originalHullColor = "#0088ff";
|
|
168
|
+
this.cannon.color = "#0088ff";
|
|
169
|
+
this.cannonTip.color = "#44aaff";
|
|
170
|
+
this.leftWing.color = "#0066dd";
|
|
171
|
+
this.rightWing.color = "#0066dd";
|
|
172
|
+
this.leftWingTip.color = "#0044aa";
|
|
173
|
+
this.rightWingTip.color = "#0044aa";
|
|
174
|
+
break;
|
|
175
|
+
case "firerate1":
|
|
176
|
+
// After level 4 - faster shooting, YELLOW ship
|
|
177
|
+
this.shootCooldownMultiplier = 0.6; // 40% faster shooting
|
|
178
|
+
this.hull.color = "#ffdd00";
|
|
179
|
+
this.originalHullColor = "#ffdd00";
|
|
180
|
+
this.cannon.color = "#ffdd00";
|
|
181
|
+
this.cannonTip.color = "#ffff44";
|
|
182
|
+
this.leftWing.color = "#ddbb00";
|
|
183
|
+
this.rightWing.color = "#ddbb00";
|
|
184
|
+
this.leftWingTip.color = "#aa8800";
|
|
185
|
+
this.rightWingTip.color = "#aa8800";
|
|
186
|
+
break;
|
|
187
|
+
case "speed2":
|
|
188
|
+
// After level 5 - even faster movement (keep yellow, brighter)
|
|
189
|
+
this.speedMultiplier = 1.7; // 70% faster total
|
|
190
|
+
this.hull.color = "#ffff00";
|
|
191
|
+
this.originalHullColor = "#ffff00";
|
|
192
|
+
this.cannonTip.color = "#ffffff";
|
|
193
|
+
break;
|
|
194
|
+
case "tripleshot":
|
|
195
|
+
// After level 6 boss - triple shot, RED ship (final form)
|
|
196
|
+
this.tripleShot = true;
|
|
197
|
+
this.hull.color = "#ff0000";
|
|
198
|
+
this.originalHullColor = "#ff0000";
|
|
199
|
+
this.cannon.color = "#ff0000";
|
|
200
|
+
this.cannonTip.color = "#ff4444";
|
|
201
|
+
this.leftWing.color = "#dd0000";
|
|
202
|
+
this.rightWing.color = "#dd0000";
|
|
203
|
+
this.leftWingTip.color = "#aa0000";
|
|
204
|
+
this.rightWingTip.color = "#aa0000";
|
|
205
|
+
break;
|
|
206
|
+
case "shield":
|
|
207
|
+
// After level 9 boss - unlock shield ability
|
|
208
|
+
this.shieldUnlocked = true;
|
|
209
|
+
this.shieldEnergy = SHIELD_MAX_ENERGY;
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Reset upgrades (on game restart)
|
|
216
|
+
*/
|
|
217
|
+
resetUpgrades() {
|
|
218
|
+
this.speedMultiplier = 1.0;
|
|
219
|
+
this.shootCooldownMultiplier = 1.0;
|
|
220
|
+
this.tripleShot = false;
|
|
221
|
+
|
|
222
|
+
// Reset shield
|
|
223
|
+
this.shieldUnlocked = false;
|
|
224
|
+
this.shieldActive = false;
|
|
225
|
+
this.shieldEnergy = SHIELD_MAX_ENERGY;
|
|
226
|
+
this.shieldRechargeDelay = 0;
|
|
227
|
+
|
|
228
|
+
// Reset colors to original green
|
|
229
|
+
this.hull.color = "#00ff00";
|
|
230
|
+
this.originalHullColor = "#00ff00";
|
|
231
|
+
this.cannon.color = "#00ff00";
|
|
232
|
+
this.cannonTip.color = "#88ff88";
|
|
233
|
+
this.leftWing.color = "#00dd00";
|
|
234
|
+
this.rightWing.color = "#00dd00";
|
|
235
|
+
this.leftWingTip.color = "#00aa00";
|
|
236
|
+
this.rightWingTip.color = "#00aa00";
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
update(dt) {
|
|
240
|
+
super.update(dt);
|
|
241
|
+
|
|
242
|
+
// Only allow movement during active gameplay (including boss fight)
|
|
243
|
+
const canMove = this.game.gameState === "playing" || this.game.gameState === "bossfight";
|
|
244
|
+
|
|
245
|
+
// Handle movement (arrow keys and WASD)
|
|
246
|
+
const movingLeft = canMove && (Keys.isDown(Keys.LEFT) || Keys.isDown("a") || Keys.isDown("A"));
|
|
247
|
+
const movingRight = canMove && (Keys.isDown(Keys.RIGHT) || Keys.isDown("d") || Keys.isDown("D"));
|
|
248
|
+
|
|
249
|
+
// Speed boost during star power + upgrade multiplier
|
|
250
|
+
let speed = PLAYER_SPEED * this.speedMultiplier;
|
|
251
|
+
if (this.starPower) {
|
|
252
|
+
speed *= STARPOWER_SPEED_MULTIPLIER;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (movingLeft) {
|
|
256
|
+
this.x -= speed * dt;
|
|
257
|
+
this.targetTilt = -this.maxTilt;
|
|
258
|
+
}
|
|
259
|
+
if (movingRight) {
|
|
260
|
+
this.x += speed * dt;
|
|
261
|
+
this.targetTilt = this.maxTilt;
|
|
262
|
+
}
|
|
263
|
+
if (!movingLeft && !movingRight) {
|
|
264
|
+
this.targetTilt = 0;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Smoothly interpolate current tilt toward target
|
|
268
|
+
this.currentTilt += (this.targetTilt - this.currentTilt) * this.tiltSpeed * dt;
|
|
269
|
+
|
|
270
|
+
// Clamp to screen bounds (only during gameplay)
|
|
271
|
+
if (canMove) {
|
|
272
|
+
const halfWidth = PLAYER_WIDTH / 2;
|
|
273
|
+
this.x = Math.max(halfWidth, Math.min(this.game.width - halfWidth, this.x));
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Animate engine flicker
|
|
277
|
+
this.engineTimer += dt * 20;
|
|
278
|
+
const flicker = Math.sin(this.engineTimer) > 0;
|
|
279
|
+
this.engineLeft.color = flicker ? "#ffaa00" : "#ff6600";
|
|
280
|
+
this.engineRight.color = flicker ? "#ff6600" : "#ffaa00";
|
|
281
|
+
|
|
282
|
+
// Star power timer and effects
|
|
283
|
+
if (this.starPower) {
|
|
284
|
+
this.starPowerTimer -= dt;
|
|
285
|
+
this.starPowerFlash += dt * 15;
|
|
286
|
+
|
|
287
|
+
// Rainbow/golden glow effect
|
|
288
|
+
const hue = (Math.sin(this.starPowerFlash) + 1) / 2;
|
|
289
|
+
const colors = ["#ffff00", "#ff8800", "#ffff00", "#ffffff"];
|
|
290
|
+
const colorIndex = Math.floor(this.starPowerFlash * 2) % colors.length;
|
|
291
|
+
this.hull.color = colors[colorIndex];
|
|
292
|
+
|
|
293
|
+
// End star power
|
|
294
|
+
if (this.starPowerTimer <= 0) {
|
|
295
|
+
this.starPower = false;
|
|
296
|
+
this.hull.color = this.originalHullColor;
|
|
297
|
+
this.shootCooldown = 0.25; // Reset to normal
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Shooting cooldown (faster during star power, affected by upgrade)
|
|
302
|
+
let currentCooldown = this.shootCooldown * this.shootCooldownMultiplier;
|
|
303
|
+
if (this.starPower) {
|
|
304
|
+
currentCooldown = 0.08; // Star power overrides with very fast shooting
|
|
305
|
+
}
|
|
306
|
+
if (!this.canShoot) {
|
|
307
|
+
this.shootTimer += dt;
|
|
308
|
+
if (this.shootTimer >= currentCooldown) {
|
|
309
|
+
this.canShoot = true;
|
|
310
|
+
this.shootTimer = 0;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Handle shooting (only during active gameplay, including boss fight)
|
|
315
|
+
if (Keys.isDown(Keys.SPACE) && this.canShoot && canMove) {
|
|
316
|
+
this.shoot();
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Shield system
|
|
320
|
+
if (this.shieldUnlocked && canMove) {
|
|
321
|
+
const shiftPressed = Keys.isDown(Keys.SHIFT);
|
|
322
|
+
|
|
323
|
+
if (shiftPressed && this.shieldEnergy > 0) {
|
|
324
|
+
// Activate shield
|
|
325
|
+
this.shieldActive = true;
|
|
326
|
+
this.shieldEnergy -= SHIELD_DRAIN_RATE * dt;
|
|
327
|
+
this.shieldEnergy = Math.max(0, this.shieldEnergy);
|
|
328
|
+
this.shieldRechargeDelay = SHIELD_RECHARGE_DELAY;
|
|
329
|
+
this.shieldFlash += dt * 10;
|
|
330
|
+
} else {
|
|
331
|
+
// Deactivate shield
|
|
332
|
+
this.shieldActive = false;
|
|
333
|
+
|
|
334
|
+
// Recharge after delay
|
|
335
|
+
if (this.shieldRechargeDelay > 0) {
|
|
336
|
+
this.shieldRechargeDelay -= dt;
|
|
337
|
+
} else {
|
|
338
|
+
this.shieldEnergy += SHIELD_RECHARGE_RATE * dt;
|
|
339
|
+
this.shieldEnergy = Math.min(SHIELD_MAX_ENERGY, this.shieldEnergy);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
activateStarPower() {
|
|
346
|
+
this.starPower = true;
|
|
347
|
+
this.starPowerTimer = STARPOWER_DURATION;
|
|
348
|
+
this.starPowerFlash = 0;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
shoot() {
|
|
352
|
+
if (!this.canShoot) return;
|
|
353
|
+
this.canShoot = false;
|
|
354
|
+
|
|
355
|
+
if (this.tripleShot) {
|
|
356
|
+
// Triple shot: center + two angled bullets
|
|
357
|
+
this.game.spawnPlayerBullet(this.x, this.y - PLAYER_HEIGHT, 0); // Center
|
|
358
|
+
this.game.spawnPlayerBullet(this.x - 8, this.y - PLAYER_HEIGHT, -15); // Left angle
|
|
359
|
+
this.game.spawnPlayerBullet(this.x + 8, this.y - PLAYER_HEIGHT, 15); // Right angle
|
|
360
|
+
} else {
|
|
361
|
+
this.game.spawnPlayerBullet(this.x, this.y - PLAYER_HEIGHT, 0);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
draw() {
|
|
366
|
+
super.draw();
|
|
367
|
+
|
|
368
|
+
// Render ship first
|
|
369
|
+
this.ship.x = 0;
|
|
370
|
+
this.ship.y = 0;
|
|
371
|
+
this.ship.rotation = this.currentTilt; // Apply banking tilt
|
|
372
|
+
this.ship.render();
|
|
373
|
+
|
|
374
|
+
// Draw shield arc on top if unlocked (shows energy level as arc size)
|
|
375
|
+
if (this.shieldUnlocked && this.shieldEnergy > 0) {
|
|
376
|
+
const ctx = Painter.ctx;
|
|
377
|
+
const energyPercent = this.shieldEnergy / SHIELD_MAX_ENERGY;
|
|
378
|
+
|
|
379
|
+
// Arc radius and thickness
|
|
380
|
+
const radius = 30;
|
|
381
|
+
const lineWidth = this.shieldActive ? 4 : 2;
|
|
382
|
+
|
|
383
|
+
// Arc spans from energyPercent (full = 180 degrees, empty = 0)
|
|
384
|
+
// Arc goes from left to right across front of ship
|
|
385
|
+
const arcAngle = Math.PI * energyPercent; // 0 to PI based on energy
|
|
386
|
+
const startAngle = Math.PI + (Math.PI - arcAngle) / 2; // Centered at top
|
|
387
|
+
const endAngle = startAngle + arcAngle;
|
|
388
|
+
|
|
389
|
+
// Color based on state - yellow/gold tones
|
|
390
|
+
let strokeColor, alpha;
|
|
391
|
+
let o = ctx.globalAlpha;
|
|
392
|
+
if (this.shieldActive) {
|
|
393
|
+
// Active shield - bright gold with pulse
|
|
394
|
+
const pulse = 0.6 + Math.sin(this.shieldFlash) * 0.4;
|
|
395
|
+
strokeColor = `rgba(255, 255, 255, ${pulse})`;
|
|
396
|
+
} else if (this.shieldRechargeDelay > 0) {
|
|
397
|
+
// Recharge delay - dim orange
|
|
398
|
+
strokeColor = "rgba(255, 255, 255, 1)";
|
|
399
|
+
} else {
|
|
400
|
+
// Recharging - gradually brightening yellow
|
|
401
|
+
const brightness = 0.3 + energyPercent * 0.5;
|
|
402
|
+
strokeColor = `rgba(255, 255, 255, ${brightness})`;
|
|
403
|
+
}
|
|
404
|
+
ctx.save();
|
|
405
|
+
|
|
406
|
+
// Explicitly prevent any fill
|
|
407
|
+
ctx.fillStyle = "transparent";
|
|
408
|
+
|
|
409
|
+
// Draw arc stroke only
|
|
410
|
+
ctx.beginPath();
|
|
411
|
+
ctx.arc(0, -5, radius, startAngle, endAngle);
|
|
412
|
+
ctx.strokeStyle = strokeColor;
|
|
413
|
+
ctx.lineWidth = lineWidth;
|
|
414
|
+
ctx.lineCap = "round";
|
|
415
|
+
ctx.stroke();
|
|
416
|
+
|
|
417
|
+
// Clear the path to prevent any accidental fills later
|
|
418
|
+
ctx.beginPath();
|
|
419
|
+
|
|
420
|
+
ctx.restore();
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Check if shield is currently protecting the player
|
|
426
|
+
*/
|
|
427
|
+
isShielded() {
|
|
428
|
+
return this.shieldActive && this.shieldEnergy > 0;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { GameObject, Circle, TextShape, Group } from "../../../src/index.js";
|
|
2
|
+
import { POWERUP_SIZE, POWERUP_FALL_SPEED } from "./constants.js";
|
|
3
|
+
|
|
4
|
+
export class PowerUp extends GameObject {
|
|
5
|
+
constructor(game, options = {}) {
|
|
6
|
+
super(game, {
|
|
7
|
+
width: POWERUP_SIZE,
|
|
8
|
+
height: POWERUP_SIZE,
|
|
9
|
+
...options,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
this.speed = POWERUP_FALL_SPEED;
|
|
13
|
+
this.bobTime = Math.random() * Math.PI * 2;
|
|
14
|
+
|
|
15
|
+
// Create 1-Up visual - pastel green life icon (cached for performance)
|
|
16
|
+
this.shape = new Group({ cacheRendering: true });
|
|
17
|
+
|
|
18
|
+
// Glowing background
|
|
19
|
+
const glow = new Circle(POWERUP_SIZE / 2 + 4, {
|
|
20
|
+
color: "rgba(144, 238, 144, 0.4)",
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Main body - pastel green
|
|
24
|
+
const body = new Circle(POWERUP_SIZE / 2, {
|
|
25
|
+
color: "#98fb98", // Pale green
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// "1UP" text
|
|
29
|
+
this.label = new TextShape("1UP", {
|
|
30
|
+
font: "bold 10px monospace",
|
|
31
|
+
color: "#ffffff",
|
|
32
|
+
align: "center",
|
|
33
|
+
baseline: "middle",
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
this.shape.add(glow);
|
|
37
|
+
this.shape.add(body);
|
|
38
|
+
|
|
39
|
+
// Store glow for animation
|
|
40
|
+
this.glow = glow;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
update(dt) {
|
|
44
|
+
super.update(dt);
|
|
45
|
+
|
|
46
|
+
// Fall down
|
|
47
|
+
this.y += this.speed * dt;
|
|
48
|
+
|
|
49
|
+
// Bob side to side
|
|
50
|
+
this.bobTime += dt * 3;
|
|
51
|
+
this.x += Math.sin(this.bobTime) * 30 * dt;
|
|
52
|
+
|
|
53
|
+
// Pulse glow - pastel green
|
|
54
|
+
const pulse = 0.4 + Math.sin(this.bobTime * 2) * 0.2;
|
|
55
|
+
this.glow.color = `rgba(144, 238, 144, ${pulse})`;
|
|
56
|
+
|
|
57
|
+
// Remove if off screen
|
|
58
|
+
if (this.y > this.game.height + POWERUP_SIZE) {
|
|
59
|
+
this.destroy();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
draw() {
|
|
64
|
+
if (!this.visible) return;
|
|
65
|
+
super.draw();
|
|
66
|
+
|
|
67
|
+
this.shape.x = 0;
|
|
68
|
+
this.shape.y = 0;
|
|
69
|
+
this.shape.render();
|
|
70
|
+
|
|
71
|
+
// Draw label on top
|
|
72
|
+
this.label.x = 0;
|
|
73
|
+
this.label.y = 0;
|
|
74
|
+
this.label.render();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
destroy() {
|
|
78
|
+
this.active = false;
|
|
79
|
+
this.visible = false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
getBounds() {
|
|
83
|
+
return {
|
|
84
|
+
x: this.x - POWERUP_SIZE / 2,
|
|
85
|
+
y: this.y - POWERUP_SIZE / 2,
|
|
86
|
+
width: POWERUP_SIZE,
|
|
87
|
+
height: POWERUP_SIZE,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { GameObject, Circle } from "../../../src/index.js";
|
|
2
|
+
|
|
3
|
+
export class Starfield extends GameObject {
|
|
4
|
+
constructor(game, options = {}) {
|
|
5
|
+
super(game, options);
|
|
6
|
+
this.stars = [];
|
|
7
|
+
this.initialized = false;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
initStars() {
|
|
11
|
+
if (this.initialized || this.game.width === 0) return;
|
|
12
|
+
this.initialized = true;
|
|
13
|
+
|
|
14
|
+
// Optimized star count - cap at 100 stars max
|
|
15
|
+
const area = this.game.width * this.game.height;
|
|
16
|
+
const starCount = Math.min(100, Math.floor(area / 15000)); // ~1 star per 15000 pixels, max 100
|
|
17
|
+
|
|
18
|
+
for (let i = 0; i < starCount; i++) {
|
|
19
|
+
const size = 1 + Math.random() * 1.5;
|
|
20
|
+
this.stars.push({
|
|
21
|
+
x: Math.random() * this.game.width,
|
|
22
|
+
y: Math.random() * this.game.height,
|
|
23
|
+
size: size,
|
|
24
|
+
speed: 15 + Math.random() * 30,
|
|
25
|
+
shape: new Circle(size, {
|
|
26
|
+
color: `rgba(255,255,255,${0.4 + Math.random() * 0.5})`,
|
|
27
|
+
}),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
update(dt) {
|
|
33
|
+
super.update(dt);
|
|
34
|
+
|
|
35
|
+
// Initialize stars on first update when dimensions are known
|
|
36
|
+
if (!this.initialized) {
|
|
37
|
+
this.initStars();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
for (const star of this.stars) {
|
|
41
|
+
star.y += star.speed * dt;
|
|
42
|
+
if (star.y > this.game.height) {
|
|
43
|
+
star.y = 0;
|
|
44
|
+
star.x = Math.random() * this.game.width;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
draw() {
|
|
50
|
+
super.draw();
|
|
51
|
+
|
|
52
|
+
for (const star of this.stars) {
|
|
53
|
+
star.shape.x = star.x;
|
|
54
|
+
star.shape.y = star.y;
|
|
55
|
+
star.shape.render();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { GameObject, Circle, Star, Group } from "../../../src/index.js";
|
|
2
|
+
import { POWERUP_SIZE, POWERUP_FALL_SPEED } from "./constants.js";
|
|
3
|
+
|
|
4
|
+
export class StarPowerUp extends GameObject {
|
|
5
|
+
constructor(game, options = {}) {
|
|
6
|
+
super(game, {
|
|
7
|
+
width: POWERUP_SIZE,
|
|
8
|
+
height: POWERUP_SIZE,
|
|
9
|
+
...options,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
this.speed = POWERUP_FALL_SPEED * 0.8; // Slightly slower
|
|
13
|
+
this.bobTime = Math.random() * Math.PI * 2;
|
|
14
|
+
this.spinAngle = 0;
|
|
15
|
+
|
|
16
|
+
// Create star visual using the Star shape
|
|
17
|
+
this.shape = new Group({});
|
|
18
|
+
|
|
19
|
+
// Glowing background
|
|
20
|
+
const glow = new Circle(POWERUP_SIZE / 2 + 6, {
|
|
21
|
+
color: "rgba(255, 215, 0, 0.4)",
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Main star - golden
|
|
25
|
+
this.star = new Star(POWERUP_SIZE / 2, 5, 0.5, {
|
|
26
|
+
color: "#ffd700", // Gold
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Inner highlight
|
|
30
|
+
const innerStar = new Star(POWERUP_SIZE / 4, 5, 0.5, {
|
|
31
|
+
color: "#ffec8b", // Light gold
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
this.shape.add(glow);
|
|
35
|
+
this.shape.add(this.star);
|
|
36
|
+
this.shape.add(innerStar);
|
|
37
|
+
|
|
38
|
+
// Store refs for animation
|
|
39
|
+
this.glow = glow;
|
|
40
|
+
this.innerStar = innerStar;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
update(dt) {
|
|
44
|
+
super.update(dt);
|
|
45
|
+
|
|
46
|
+
// Fall down
|
|
47
|
+
this.y += this.speed * dt;
|
|
48
|
+
|
|
49
|
+
// Bob side to side
|
|
50
|
+
this.bobTime += dt * 3;
|
|
51
|
+
this.x += Math.sin(this.bobTime) * 40 * dt;
|
|
52
|
+
|
|
53
|
+
// Spin the star
|
|
54
|
+
this.spinAngle += dt * 180; // Degrees per second
|
|
55
|
+
this.star.rotation = this.spinAngle;
|
|
56
|
+
this.innerStar.rotation = -this.spinAngle * 0.5;
|
|
57
|
+
|
|
58
|
+
// Pulse glow
|
|
59
|
+
const pulse = 0.4 + Math.sin(this.bobTime * 2) * 0.2;
|
|
60
|
+
this.glow.color = `rgba(255, 215, 0, ${pulse})`;
|
|
61
|
+
|
|
62
|
+
// Remove if off screen
|
|
63
|
+
if (this.y > this.game.height + POWERUP_SIZE) {
|
|
64
|
+
this.destroy();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
draw() {
|
|
69
|
+
if (!this.visible) return;
|
|
70
|
+
super.draw();
|
|
71
|
+
|
|
72
|
+
this.shape.x = 0;
|
|
73
|
+
this.shape.y = 0;
|
|
74
|
+
this.shape.render();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
destroy() {
|
|
78
|
+
this.active = false;
|
|
79
|
+
this.visible = false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
getBounds() {
|
|
83
|
+
return {
|
|
84
|
+
x: this.x - POWERUP_SIZE / 2,
|
|
85
|
+
y: this.y - POWERUP_SIZE / 2,
|
|
86
|
+
width: POWERUP_SIZE,
|
|
87
|
+
height: POWERUP_SIZE,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|