@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,171 @@
|
|
|
1
|
+
import { GameObject, Rectangle, Circle, Group } from "../../../src/index.js";
|
|
2
|
+
import { ALIEN_WIDTH, ALIEN_HEIGHT } from "./constants.js";
|
|
3
|
+
|
|
4
|
+
export class Alien extends GameObject {
|
|
5
|
+
constructor(game, options = {}) {
|
|
6
|
+
super(game, {
|
|
7
|
+
width: ALIEN_WIDTH,
|
|
8
|
+
height: ALIEN_HEIGHT,
|
|
9
|
+
...options,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
this.row = options.row || 0;
|
|
13
|
+
this.col = options.col || 0;
|
|
14
|
+
this.points = options.points || 10;
|
|
15
|
+
this.animTime = Math.random() * 2; // Offset animation
|
|
16
|
+
|
|
17
|
+
// Create alien shape based on row
|
|
18
|
+
this.shape = this.createShape();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
createShape() {
|
|
22
|
+
const group = new Group({ cacheRendering: true });
|
|
23
|
+
|
|
24
|
+
// Different alien designs based on row - classic Space Invaders style
|
|
25
|
+
if (this.row === 0) {
|
|
26
|
+
// Top row - Squid/UFO type (30 points) - magenta/pink
|
|
27
|
+
this.points = 30;
|
|
28
|
+
|
|
29
|
+
// Dome head
|
|
30
|
+
const head = new Circle(8, { y: -2, color: "#ff0088" });
|
|
31
|
+
|
|
32
|
+
// Body
|
|
33
|
+
const body = new Rectangle({ width: 20, height: 8, y: 4, color: "#ff0088" });
|
|
34
|
+
|
|
35
|
+
// Eyes
|
|
36
|
+
const leftEye = new Circle(2, { x: -4, y: -3, color: "#ffffff" });
|
|
37
|
+
const rightEye = new Circle(2, { x: 4, y: -3, color: "#ffffff" });
|
|
38
|
+
const leftPupil = new Circle(1, { x: -4, y: -3, color: "#000000" });
|
|
39
|
+
const rightPupil = new Circle(1, { x: 4, y: -3, color: "#000000" });
|
|
40
|
+
|
|
41
|
+
// Tentacles
|
|
42
|
+
const tent1 = new Rectangle({ width: 3, height: 6, x: -8, y: 10, color: "#cc0066" });
|
|
43
|
+
const tent2 = new Rectangle({ width: 3, height: 8, x: -3, y: 11, color: "#cc0066" });
|
|
44
|
+
const tent3 = new Rectangle({ width: 3, height: 8, x: 3, y: 11, color: "#cc0066" });
|
|
45
|
+
const tent4 = new Rectangle({ width: 3, height: 6, x: 8, y: 10, color: "#cc0066" });
|
|
46
|
+
|
|
47
|
+
group.add(head);
|
|
48
|
+
group.add(body);
|
|
49
|
+
group.add(leftEye);
|
|
50
|
+
group.add(rightEye);
|
|
51
|
+
group.add(leftPupil);
|
|
52
|
+
group.add(rightPupil);
|
|
53
|
+
group.add(tent1);
|
|
54
|
+
group.add(tent2);
|
|
55
|
+
group.add(tent3);
|
|
56
|
+
group.add(tent4);
|
|
57
|
+
|
|
58
|
+
} else if (this.row <= 2) {
|
|
59
|
+
// Middle rows - Crab type (20 points) - CYAN/ELECTRIC BLUE (spacey)
|
|
60
|
+
this.points = 20;
|
|
61
|
+
|
|
62
|
+
// Main body
|
|
63
|
+
const body = new Rectangle({ width: 22, height: 10, color: "#00ddff" });
|
|
64
|
+
|
|
65
|
+
// Head bump
|
|
66
|
+
const headBump = new Rectangle({ width: 10, height: 6, y: -6, color: "#00ddff" });
|
|
67
|
+
|
|
68
|
+
// Eyes
|
|
69
|
+
const leftEye = new Rectangle({ width: 4, height: 4, x: -6, y: -2, color: "#003344" });
|
|
70
|
+
const rightEye = new Rectangle({ width: 4, height: 4, x: 6, y: -2, color: "#003344" });
|
|
71
|
+
|
|
72
|
+
// Claws - left
|
|
73
|
+
const leftArm = new Rectangle({ width: 4, height: 6, x: -14, y: -2, color: "#00aacc" });
|
|
74
|
+
const leftClaw = new Rectangle({ width: 6, height: 4, x: -16, y: -6, color: "#00aacc" });
|
|
75
|
+
|
|
76
|
+
// Claws - right
|
|
77
|
+
const rightArm = new Rectangle({ width: 4, height: 6, x: 14, y: -2, color: "#00aacc" });
|
|
78
|
+
const rightClaw = new Rectangle({ width: 6, height: 4, x: 16, y: -6, color: "#00aacc" });
|
|
79
|
+
|
|
80
|
+
// Legs
|
|
81
|
+
const leg1 = new Rectangle({ width: 3, height: 5, x: -8, y: 8, color: "#0088aa" });
|
|
82
|
+
const leg2 = new Rectangle({ width: 3, height: 5, x: 0, y: 8, color: "#0088aa" });
|
|
83
|
+
const leg3 = new Rectangle({ width: 3, height: 5, x: 8, y: 8, color: "#0088aa" });
|
|
84
|
+
|
|
85
|
+
group.add(body);
|
|
86
|
+
group.add(headBump);
|
|
87
|
+
group.add(leftEye);
|
|
88
|
+
group.add(rightEye);
|
|
89
|
+
group.add(leftArm);
|
|
90
|
+
group.add(leftClaw);
|
|
91
|
+
group.add(rightArm);
|
|
92
|
+
group.add(rightClaw);
|
|
93
|
+
group.add(leg1);
|
|
94
|
+
group.add(leg2);
|
|
95
|
+
group.add(leg3);
|
|
96
|
+
|
|
97
|
+
} else {
|
|
98
|
+
// Bottom rows - Octopus/Basic type (10 points) - green
|
|
99
|
+
this.points = 10;
|
|
100
|
+
|
|
101
|
+
// Round head
|
|
102
|
+
const head = new Circle(10, { y: -2, color: "#44ff44" });
|
|
103
|
+
|
|
104
|
+
// Body extension
|
|
105
|
+
const body = new Rectangle({ width: 16, height: 8, y: 6, color: "#44ff44" });
|
|
106
|
+
|
|
107
|
+
// Eyes
|
|
108
|
+
const leftEye = new Rectangle({ width: 4, height: 5, x: -4, y: -4, color: "#003300" });
|
|
109
|
+
const rightEye = new Rectangle({ width: 4, height: 5, x: 4, y: -4, color: "#003300" });
|
|
110
|
+
|
|
111
|
+
// Antennae
|
|
112
|
+
const leftAntenna = new Rectangle({ width: 2, height: 6, x: -6, y: -12, color: "#22cc22" });
|
|
113
|
+
const rightAntenna = new Rectangle({ width: 2, height: 6, x: 6, y: -12, color: "#22cc22" });
|
|
114
|
+
const leftTip = new Circle(2, { x: -6, y: -16, color: "#88ff88" });
|
|
115
|
+
const rightTip = new Circle(2, { x: 6, y: -16, color: "#88ff88" });
|
|
116
|
+
|
|
117
|
+
// Tentacle legs
|
|
118
|
+
const leg1 = new Rectangle({ width: 3, height: 6, x: -10, y: 12, color: "#22aa22" });
|
|
119
|
+
const leg2 = new Rectangle({ width: 3, height: 8, x: -4, y: 13, color: "#22aa22" });
|
|
120
|
+
const leg3 = new Rectangle({ width: 3, height: 8, x: 4, y: 13, color: "#22aa22" });
|
|
121
|
+
const leg4 = new Rectangle({ width: 3, height: 6, x: 10, y: 12, color: "#22aa22" });
|
|
122
|
+
|
|
123
|
+
group.add(head);
|
|
124
|
+
group.add(body);
|
|
125
|
+
group.add(leftEye);
|
|
126
|
+
group.add(rightEye);
|
|
127
|
+
group.add(leftAntenna);
|
|
128
|
+
group.add(rightAntenna);
|
|
129
|
+
group.add(leftTip);
|
|
130
|
+
group.add(rightTip);
|
|
131
|
+
group.add(leg1);
|
|
132
|
+
group.add(leg2);
|
|
133
|
+
group.add(leg3);
|
|
134
|
+
group.add(leg4);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return group;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
update(dt) {
|
|
141
|
+
super.update(dt);
|
|
142
|
+
this.animTime += dt;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
draw() {
|
|
146
|
+
if (!this.visible) return;
|
|
147
|
+
super.draw();
|
|
148
|
+
|
|
149
|
+
// Add subtle animation - render at origin with wobble offset
|
|
150
|
+
// Parent transform already positions us correctly
|
|
151
|
+
const wobble = Math.sin(this.animTime * 5) * 2;
|
|
152
|
+
|
|
153
|
+
this.shape.x = 0;
|
|
154
|
+
this.shape.y = wobble;
|
|
155
|
+
this.shape.render();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
destroy() {
|
|
159
|
+
this.active = false;
|
|
160
|
+
this.visible = false;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
getBounds() {
|
|
164
|
+
return {
|
|
165
|
+
x: this.x - ALIEN_WIDTH / 2,
|
|
166
|
+
y: this.y - ALIEN_HEIGHT / 2,
|
|
167
|
+
width: ALIEN_WIDTH,
|
|
168
|
+
height: ALIEN_HEIGHT,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { GameObject, Rectangle, Circle } from "../../../src/index.js";
|
|
2
|
+
|
|
3
|
+
export class Explosion extends GameObject {
|
|
4
|
+
constructor(game, options = {}) {
|
|
5
|
+
super(game, options);
|
|
6
|
+
|
|
7
|
+
this.particles = [];
|
|
8
|
+
this.lifetime = 0.4; // Shorter lifetime for better performance
|
|
9
|
+
this.age = 0;
|
|
10
|
+
this.baseColor = options.color || "#ffff00";
|
|
11
|
+
|
|
12
|
+
// Color palette for explosion - varies based on base color
|
|
13
|
+
const colors = this.baseColor === "#ffff00"
|
|
14
|
+
? ["#ffffff", "#ffff00", "#ffaa00", "#ff6600"] // Yellow explosion
|
|
15
|
+
: ["#ffffff", "#ff8888", "#ff4444", "#ff0000"]; // Red explosion
|
|
16
|
+
|
|
17
|
+
// Optimized particle count (8 circles + 3 squares = 11 total)
|
|
18
|
+
const particleCount = 8;
|
|
19
|
+
for (let i = 0; i < particleCount; i++) {
|
|
20
|
+
const angle = (i / particleCount) * Math.PI * 2 + Math.random() * 0.5;
|
|
21
|
+
const speed = 80 + Math.random() * 100;
|
|
22
|
+
const size = 2 + Math.random() * 4;
|
|
23
|
+
const color = colors[Math.floor(Math.random() * colors.length)];
|
|
24
|
+
|
|
25
|
+
this.particles.push({
|
|
26
|
+
x: 0,
|
|
27
|
+
y: 0,
|
|
28
|
+
vx: Math.cos(angle) * speed,
|
|
29
|
+
vy: Math.sin(angle) * speed,
|
|
30
|
+
size: size,
|
|
31
|
+
shape: new Circle(size, { color: color }),
|
|
32
|
+
rotSpeed: (Math.random() - 0.5) * 10,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Add some square debris
|
|
37
|
+
for (let i = 0; i < 3; i++) {
|
|
38
|
+
const angle = Math.random() * Math.PI * 2;
|
|
39
|
+
const speed = 40 + Math.random() * 60;
|
|
40
|
+
const size = 2 + Math.random() * 3;
|
|
41
|
+
const color = colors[Math.floor(Math.random() * colors.length)];
|
|
42
|
+
|
|
43
|
+
this.particles.push({
|
|
44
|
+
x: 0,
|
|
45
|
+
y: 0,
|
|
46
|
+
vx: Math.cos(angle) * speed,
|
|
47
|
+
vy: Math.sin(angle) * speed,
|
|
48
|
+
size: size,
|
|
49
|
+
shape: new Rectangle({ width: size, height: size, color: color }),
|
|
50
|
+
rotSpeed: (Math.random() - 0.5) * 15,
|
|
51
|
+
rotation: 0,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
update(dt) {
|
|
57
|
+
super.update(dt);
|
|
58
|
+
this.age += dt;
|
|
59
|
+
|
|
60
|
+
if (this.age >= this.lifetime) {
|
|
61
|
+
this.active = false;
|
|
62
|
+
this.visible = false;
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Update particles with gravity and fade
|
|
67
|
+
const progress = this.age / this.lifetime;
|
|
68
|
+
for (const p of this.particles) {
|
|
69
|
+
p.x += p.vx * dt;
|
|
70
|
+
p.y += p.vy * dt;
|
|
71
|
+
p.vy += 100 * dt; // Gravity
|
|
72
|
+
p.vx *= 0.99; // Air resistance
|
|
73
|
+
|
|
74
|
+
// Fade and shrink
|
|
75
|
+
p.shape.opacity = 1 - progress * progress;
|
|
76
|
+
|
|
77
|
+
// Rotate debris
|
|
78
|
+
if (p.rotation !== undefined) {
|
|
79
|
+
p.rotation += p.rotSpeed * dt;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
draw() {
|
|
85
|
+
if (!this.visible) return;
|
|
86
|
+
super.draw();
|
|
87
|
+
|
|
88
|
+
// Render particles relative to origin - parent transform positions us correctly
|
|
89
|
+
for (const p of this.particles) {
|
|
90
|
+
p.shape.x = p.x;
|
|
91
|
+
p.shape.y = p.y;
|
|
92
|
+
if (p.rotation !== undefined) {
|
|
93
|
+
p.shape.rotation = p.rotation * (180 / Math.PI); // Convert to degrees
|
|
94
|
+
}
|
|
95
|
+
p.shape.render();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import { GameObject, Rectangle } from "../../../src/index.js";
|
|
2
|
+
import { Alien } from "./alien.js";
|
|
3
|
+
import {
|
|
4
|
+
ALIEN_WIDTH,
|
|
5
|
+
ALIEN_HEIGHT,
|
|
6
|
+
BOSS_SCALE,
|
|
7
|
+
BOSS_BASE_HEALTH,
|
|
8
|
+
BOSS_BASE_POINTS,
|
|
9
|
+
BOSS_BASE_MISSILE_INTERVAL,
|
|
10
|
+
BOSS_BASE_MINION_INTERVAL,
|
|
11
|
+
BOSS_BASE_MAX_MINIONS,
|
|
12
|
+
BOSS_BASE_MOVE_SPEED,
|
|
13
|
+
GAUNTLET_BOSS_SCALE,
|
|
14
|
+
GAUNTLET_BOSS_HEALTH_MULTIPLIER,
|
|
15
|
+
GAUNTLET_BOSS_COLORS,
|
|
16
|
+
} from "./constants.js";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Boss alien - uses composition to scale up a regular alien shape
|
|
20
|
+
* Difficulty scales with boss type:
|
|
21
|
+
* Type 0 = Squid (pink) - Level 3 boss (easiest, missiles only)
|
|
22
|
+
* Type 1 = Octopus (green) - Level 6 boss (medium, missiles + lasers)
|
|
23
|
+
* Type 2 = Crab (cyan) - Level 9 boss (hardest, missiles + lasers + lightning)
|
|
24
|
+
*
|
|
25
|
+
* Gauntlet mode: Larger scale, different colors, more health
|
|
26
|
+
*/
|
|
27
|
+
export class Boss extends GameObject {
|
|
28
|
+
constructor(game, options = {}) {
|
|
29
|
+
// Gauntlet mode uses larger scale - must determine before super()
|
|
30
|
+
const isGauntlet = options.isGauntlet || false;
|
|
31
|
+
const scale = isGauntlet ? GAUNTLET_BOSS_SCALE : BOSS_SCALE;
|
|
32
|
+
|
|
33
|
+
const width = ALIEN_WIDTH * scale;
|
|
34
|
+
const height = ALIEN_HEIGHT * scale;
|
|
35
|
+
|
|
36
|
+
super(game, {
|
|
37
|
+
width: width,
|
|
38
|
+
height: height,
|
|
39
|
+
...options,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
this.isGauntlet = isGauntlet;
|
|
43
|
+
this.scale = scale;
|
|
44
|
+
|
|
45
|
+
// Boss type determines which alien row shape to use AND difficulty scaling
|
|
46
|
+
// 0 = octopus (row 3), 1 = squid (row 0), 2 = crab (row 1)
|
|
47
|
+
this.bossType = options.bossType || 0;
|
|
48
|
+
|
|
49
|
+
// Scale difficulty based on boss type (0, 1, 2)
|
|
50
|
+
const difficultyScale = 1 + this.bossType * 0.5; // 1.0, 1.5, 2.0
|
|
51
|
+
|
|
52
|
+
// Gauntlet bosses have extra health
|
|
53
|
+
const gauntletMultiplier = this.isGauntlet ? GAUNTLET_BOSS_HEALTH_MULTIPLIER : 1;
|
|
54
|
+
|
|
55
|
+
// Health scales up: 30, 45, 60 (or 45, 68, 90 in gauntlet)
|
|
56
|
+
this.health = Math.floor(BOSS_BASE_HEALTH * difficultyScale * gauntletMultiplier);
|
|
57
|
+
this.maxHealth = this.health;
|
|
58
|
+
|
|
59
|
+
// Points scale up: 1000, 2000, 3000 (doubled in gauntlet)
|
|
60
|
+
this.points = BOSS_BASE_POINTS * (this.bossType + 1) * (this.isGauntlet ? 2 : 1);
|
|
61
|
+
|
|
62
|
+
// Animation
|
|
63
|
+
this.animTime = 0;
|
|
64
|
+
this.hitFlashTime = 0;
|
|
65
|
+
this.isFlashing = false;
|
|
66
|
+
|
|
67
|
+
// Movement - faster for harder bosses
|
|
68
|
+
this.moveDirection = 1;
|
|
69
|
+
this.moveSpeed = BOSS_BASE_MOVE_SPEED * (1 + this.bossType * 0.3); // 35, 45.5, 56
|
|
70
|
+
|
|
71
|
+
// Attack intervals - shorter for harder bosses
|
|
72
|
+
// Non-signature abilities are 20% slower to reduce spam
|
|
73
|
+
// Signature: Boss 0 = missiles, Boss 1 = lasers, Boss 2 = lightning
|
|
74
|
+
let baseMissileInterval = BOSS_BASE_MISSILE_INTERVAL / difficultyScale;
|
|
75
|
+
if (this.bossType >= 1) {
|
|
76
|
+
// Missiles are non-signature for bosses 1 & 2, slow down 20%
|
|
77
|
+
baseMissileInterval *= 1.2;
|
|
78
|
+
}
|
|
79
|
+
this.missileInterval = baseMissileInterval;
|
|
80
|
+
|
|
81
|
+
this.minionInterval = BOSS_BASE_MINION_INTERVAL / difficultyScale;
|
|
82
|
+
this.maxMinions = BOSS_BASE_MAX_MINIONS + this.bossType * 2; // 2, 4, 6
|
|
83
|
+
|
|
84
|
+
// Boss type 1+ (Octopus, Crab) use lasers
|
|
85
|
+
this.usesLasers = this.bossType >= 1;
|
|
86
|
+
if (this.bossType === 1) {
|
|
87
|
+
// Boss 1: Lasers are signature - tuned down 10% from 1.8 to 2.0
|
|
88
|
+
this.laserInterval = 2.0;
|
|
89
|
+
} else if (this.bossType === 2) {
|
|
90
|
+
// Boss 2: Lasers are non-signature - 20% slower
|
|
91
|
+
this.laserInterval = 2.2;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Boss type 2 (Crab/final) uses lightning as signature move
|
|
95
|
+
this.usesLightning = this.bossType === 2;
|
|
96
|
+
this.lightningInterval = 2.5;
|
|
97
|
+
|
|
98
|
+
// Attack timers
|
|
99
|
+
this.missileTimer = 0;
|
|
100
|
+
this.minionTimer = 0;
|
|
101
|
+
this.laserTimer = 0;
|
|
102
|
+
this.lightningTimer = 0;
|
|
103
|
+
this.minionCount = 0; // Track active minions
|
|
104
|
+
|
|
105
|
+
// Entry animation
|
|
106
|
+
this.entering = true;
|
|
107
|
+
this.entryProgress = 0;
|
|
108
|
+
this.targetY = options.targetY || 150;
|
|
109
|
+
this.startY = -height;
|
|
110
|
+
|
|
111
|
+
// Create shape using composition - instantiate an Alien and reuse its shape
|
|
112
|
+
this.shape = this.createShape();
|
|
113
|
+
|
|
114
|
+
// Health bar
|
|
115
|
+
this.healthBarBg = new Rectangle({
|
|
116
|
+
width: width,
|
|
117
|
+
height: 8,
|
|
118
|
+
color: "#333333",
|
|
119
|
+
});
|
|
120
|
+
this.healthBarFg = new Rectangle({
|
|
121
|
+
width: width,
|
|
122
|
+
height: 8,
|
|
123
|
+
color: "#ff0000",
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
createShape() {
|
|
128
|
+
// Map boss type to the appropriate alien row
|
|
129
|
+
// Type 0 (Level 3) = Squid (pink) -> row 0
|
|
130
|
+
// Type 1 (Level 6) = Octopus (green) -> row 3
|
|
131
|
+
// Type 2 (Level 9) = Crab (cyan) -> row 1 (final boss!)
|
|
132
|
+
const rowMapping = [0, 3, 1];
|
|
133
|
+
const alienRow = rowMapping[this.bossType] ?? 0;
|
|
134
|
+
|
|
135
|
+
// Create a temporary alien to get its shape
|
|
136
|
+
const tempAlien = new Alien(this.game, { row: alienRow });
|
|
137
|
+
const shape = tempAlien.shape;
|
|
138
|
+
|
|
139
|
+
// Disable caching since boss scales up (would cause pixelation if cached at small size)
|
|
140
|
+
shape.cacheRendering = false;
|
|
141
|
+
|
|
142
|
+
// Apply scale transform (using instance scale which differs in gauntlet mode)
|
|
143
|
+
shape.transform.scale(this.scale);
|
|
144
|
+
|
|
145
|
+
// In gauntlet mode, recolor the shape with custom gauntlet colors
|
|
146
|
+
if (this.isGauntlet) {
|
|
147
|
+
const colors = GAUNTLET_BOSS_COLORS[this.bossType];
|
|
148
|
+
this.recolorShape(shape, colors);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return shape;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Recursively recolor all shapes in a group with gauntlet colors
|
|
156
|
+
*/
|
|
157
|
+
recolorShape(shape, colors) {
|
|
158
|
+
if (shape.children) {
|
|
159
|
+
// It's a group - recursively recolor children
|
|
160
|
+
for (const child of shape.children) {
|
|
161
|
+
this.recolorShape(child, colors);
|
|
162
|
+
}
|
|
163
|
+
} else if (shape.color) {
|
|
164
|
+
// It's a shape with a color - determine which tier based on brightness
|
|
165
|
+
const hex = shape.color.toLowerCase();
|
|
166
|
+
// Check if it's a dark color (eyes, etc) - keep them dark
|
|
167
|
+
if (hex.includes("00") && !hex.includes("ff") && !hex.includes("dd") && !hex.includes("cc") && !hex.includes("aa") && !hex.includes("88")) {
|
|
168
|
+
shape.color = colors.dark;
|
|
169
|
+
} else if (hex.includes("cc") || hex.includes("aa") || hex.includes("88") || hex.includes("66")) {
|
|
170
|
+
// Secondary/darker accent colors
|
|
171
|
+
shape.color = colors.secondary;
|
|
172
|
+
} else {
|
|
173
|
+
// Primary bright colors
|
|
174
|
+
shape.color = colors.primary;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
update(dt) {
|
|
180
|
+
super.update(dt);
|
|
181
|
+
this.animTime += dt;
|
|
182
|
+
|
|
183
|
+
// Entry animation - descend from top
|
|
184
|
+
if (this.entering) {
|
|
185
|
+
this.entryProgress += dt * 0.5; // 2 seconds to enter
|
|
186
|
+
if (this.entryProgress >= 1) {
|
|
187
|
+
this.entryProgress = 1;
|
|
188
|
+
this.entering = false;
|
|
189
|
+
}
|
|
190
|
+
// Ease out cubic
|
|
191
|
+
const eased = 1 - Math.pow(1 - this.entryProgress, 3);
|
|
192
|
+
this.y = this.startY + (this.targetY - this.startY) * eased;
|
|
193
|
+
return; // Don't attack while entering
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Horizontal movement - drift back and forth
|
|
197
|
+
this.x += this.moveSpeed * this.moveDirection * dt;
|
|
198
|
+
|
|
199
|
+
// Bounce off screen edges
|
|
200
|
+
const halfWidth = this.width / 2;
|
|
201
|
+
if (this.x < halfWidth + 20) {
|
|
202
|
+
this.x = halfWidth + 20;
|
|
203
|
+
this.moveDirection = 1;
|
|
204
|
+
} else if (this.x > this.game.width - halfWidth - 20) {
|
|
205
|
+
this.x = this.game.width - halfWidth - 20;
|
|
206
|
+
this.moveDirection = -1;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Attack timers
|
|
210
|
+
this.missileTimer += dt;
|
|
211
|
+
this.minionTimer += dt;
|
|
212
|
+
if (this.usesLasers) {
|
|
213
|
+
this.laserTimer += dt;
|
|
214
|
+
}
|
|
215
|
+
if (this.usesLightning) {
|
|
216
|
+
this.lightningTimer += dt;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Fire missiles at player
|
|
220
|
+
if (this.missileTimer >= this.missileInterval) {
|
|
221
|
+
this.missileTimer = 0;
|
|
222
|
+
this.fireMissile();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Fire lasers (boss type 1 special attack)
|
|
226
|
+
if (this.usesLasers && this.laserTimer >= this.laserInterval) {
|
|
227
|
+
this.laserTimer = 0;
|
|
228
|
+
this.fireLaser();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Fire lightning (boss type 2 special attack)
|
|
232
|
+
if (this.usesLightning && this.lightningTimer >= this.lightningInterval) {
|
|
233
|
+
this.lightningTimer = 0;
|
|
234
|
+
this.fireLightning();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Spawn minions
|
|
238
|
+
if (this.minionTimer >= this.minionInterval && this.minionCount < this.maxMinions) {
|
|
239
|
+
this.minionTimer = 0;
|
|
240
|
+
this.spawnMinion();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Hit flash effect
|
|
244
|
+
if (this.isFlashing) {
|
|
245
|
+
this.hitFlashTime -= dt;
|
|
246
|
+
if (this.hitFlashTime <= 0) {
|
|
247
|
+
this.isFlashing = false;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
fireMissile() {
|
|
253
|
+
// Fire a missile from boss position toward player
|
|
254
|
+
if (this.game.spawnBossMissile) {
|
|
255
|
+
this.game.spawnBossMissile(this.x, this.y + this.height / 2);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
fireLaser() {
|
|
260
|
+
// Fire a laser beam directly at player's current position - forces them to move
|
|
261
|
+
if (this.game.spawnLaserBeam) {
|
|
262
|
+
this.game.spawnLaserBeam(this.game.player.x);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
fireLightning() {
|
|
267
|
+
// Fire lightning - the ultimate "pay attention" move
|
|
268
|
+
if (this.game.spawnLightning) {
|
|
269
|
+
this.game.spawnLightning();
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
spawnMinion() {
|
|
274
|
+
// Spawn a minion that will float to a position
|
|
275
|
+
if (this.game.spawnBossMinion) {
|
|
276
|
+
const targetX = 100 + Math.random() * (this.game.width - 200);
|
|
277
|
+
const targetY = 200 + Math.random() * 150;
|
|
278
|
+
|
|
279
|
+
this.game.spawnBossMinion(this.x, this.y, targetX, targetY, this.bossType);
|
|
280
|
+
this.minionCount++;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
onMinionDestroyed() {
|
|
285
|
+
this.minionCount = Math.max(0, this.minionCount - 1);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
takeDamage() {
|
|
289
|
+
this.health--;
|
|
290
|
+
this.isFlashing = true;
|
|
291
|
+
this.hitFlashTime = 0.1;
|
|
292
|
+
|
|
293
|
+
if (this.health <= 0) {
|
|
294
|
+
this.destroy();
|
|
295
|
+
return true; // Boss defeated
|
|
296
|
+
}
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
draw() {
|
|
301
|
+
if (!this.visible) return;
|
|
302
|
+
super.draw();
|
|
303
|
+
|
|
304
|
+
// Wobble animation
|
|
305
|
+
const wobble = Math.sin(this.animTime * 3) * 3;
|
|
306
|
+
|
|
307
|
+
// Flash white when hit
|
|
308
|
+
if (this.isFlashing) {
|
|
309
|
+
this.shape.opacity = 0.5;
|
|
310
|
+
} else {
|
|
311
|
+
this.shape.opacity = 1;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Render boss shape
|
|
315
|
+
this.shape.x = 0;
|
|
316
|
+
this.shape.y = wobble;
|
|
317
|
+
this.shape.render();
|
|
318
|
+
|
|
319
|
+
// Render health bar above boss (further up to avoid overlap with shape)
|
|
320
|
+
const healthPercent = this.health / this.maxHealth;
|
|
321
|
+
this.healthBarBg.x = 0;
|
|
322
|
+
this.healthBarBg.y = -this.height / 2 - 40;
|
|
323
|
+
this.healthBarBg.render();
|
|
324
|
+
|
|
325
|
+
this.healthBarFg.width = this.width * healthPercent;
|
|
326
|
+
this.healthBarFg.x = -(this.width * (1 - healthPercent)) / 2;
|
|
327
|
+
this.healthBarFg.y = -this.height / 2 - 40;
|
|
328
|
+
|
|
329
|
+
// Color based on health
|
|
330
|
+
if (healthPercent > 0.5) {
|
|
331
|
+
this.healthBarFg.color = "#00ff00";
|
|
332
|
+
} else if (healthPercent > 0.25) {
|
|
333
|
+
this.healthBarFg.color = "#ffff00";
|
|
334
|
+
} else {
|
|
335
|
+
this.healthBarFg.color = "#ff0000";
|
|
336
|
+
}
|
|
337
|
+
this.healthBarFg.render();
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
destroy() {
|
|
341
|
+
this.active = false;
|
|
342
|
+
this.visible = false;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
getBounds() {
|
|
346
|
+
return {
|
|
347
|
+
x: this.x - this.width / 2,
|
|
348
|
+
y: this.y - this.height / 2,
|
|
349
|
+
width: this.width,
|
|
350
|
+
height: this.height,
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { GameObject, Circle } from "../../../src/index.js";
|
|
2
|
+
|
|
3
|
+
export class AbsorbEffect extends GameObject {
|
|
4
|
+
constructor(game, options = {}) {
|
|
5
|
+
super(game, options);
|
|
6
|
+
|
|
7
|
+
this.particles = [];
|
|
8
|
+
this.lifetime = 0.4; // Shorter lifetime for better performance
|
|
9
|
+
this.age = 0;
|
|
10
|
+
this.targetX = options.targetX || this.x;
|
|
11
|
+
this.targetY = options.targetY || this.y;
|
|
12
|
+
this.color = options.color || "#98fb98";
|
|
13
|
+
|
|
14
|
+
// Create particles that will fly toward target (optimized count)
|
|
15
|
+
const particleCount = 8;
|
|
16
|
+
for (let i = 0; i < particleCount; i++) {
|
|
17
|
+
const angle = (i / particleCount) * Math.PI * 2;
|
|
18
|
+
const distance = 25 + Math.random() * 15;
|
|
19
|
+
const size = 3 + Math.random() * 3;
|
|
20
|
+
|
|
21
|
+
this.particles.push({
|
|
22
|
+
x: Math.cos(angle) * distance,
|
|
23
|
+
y: Math.sin(angle) * distance,
|
|
24
|
+
size: size,
|
|
25
|
+
shape: new Circle(size, {
|
|
26
|
+
color: this.color,
|
|
27
|
+
}),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
update(dt) {
|
|
33
|
+
super.update(dt);
|
|
34
|
+
this.age += dt;
|
|
35
|
+
|
|
36
|
+
if (this.age >= this.lifetime) {
|
|
37
|
+
this.active = false;
|
|
38
|
+
this.visible = false;
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Update target to follow player
|
|
43
|
+
if (this.game.player) {
|
|
44
|
+
this.targetX = this.game.player.x;
|
|
45
|
+
this.targetY = this.game.player.y;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Move particles toward target (relative to effect origin)
|
|
49
|
+
const progress = this.age / this.lifetime;
|
|
50
|
+
const targetRelX = this.targetX - this.x;
|
|
51
|
+
const targetRelY = this.targetY - this.y;
|
|
52
|
+
|
|
53
|
+
for (const p of this.particles) {
|
|
54
|
+
// Lerp toward target
|
|
55
|
+
p.x += (targetRelX - p.x) * dt * 5;
|
|
56
|
+
p.y += (targetRelY - p.y) * dt * 5;
|
|
57
|
+
|
|
58
|
+
// Shrink and fade
|
|
59
|
+
p.shape.opacity = 1 - progress;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
draw() {
|
|
64
|
+
if (!this.visible) return;
|
|
65
|
+
super.draw();
|
|
66
|
+
|
|
67
|
+
for (const p of this.particles) {
|
|
68
|
+
p.shape.x = p.x;
|
|
69
|
+
p.shape.y = p.y;
|
|
70
|
+
p.shape.render();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|