summaryrefslogtreecommitdiff
path: root/src/engine/entities
diff options
context:
space:
mode:
authorElizabeth Hunt <elizabeth.hunt@simponic.xyz>2024-03-06 14:35:04 -0700
committerElizabeth Hunt <elizabeth.hunt@simponic.xyz>2024-03-06 14:35:04 -0700
commit823620b2a6ebb7ece619991e47a37ad46542b69f (patch)
tree82a1501c5f707a1bcbc6c28bd6d0f5731cc9f618 /src/engine/entities
parentce06fa7c29ba4e3d6137f7aa74fbfe45af0e8b73 (diff)
downloadthe-abstraction-engine-823620b2a6ebb7ece619991e47a37ad46542b69f.tar.gz
the-abstraction-engine-823620b2a6ebb7ece619991e47a37ad46542b69f.zip
add particles
Diffstat (limited to 'src/engine/entities')
-rw-r--r--src/engine/entities/Curry.ts10
-rw-r--r--src/engine/entities/Entity.ts2
-rw-r--r--src/engine/entities/EntityNames.ts1
-rw-r--r--src/engine/entities/LockedDoor.ts47
-rw-r--r--src/engine/entities/Particles.ts194
-rw-r--r--src/engine/entities/index.ts2
6 files changed, 251 insertions, 5 deletions
diff --git a/src/engine/entities/Curry.ts b/src/engine/entities/Curry.ts
index 85bc7ef..bd57e19 100644
--- a/src/engine/entities/Curry.ts
+++ b/src/engine/entities/Curry.ts
@@ -1,4 +1,5 @@
import { Entity, EntityNames } from ".";
+import { Game } from "..";
import { BoundingBox, Colliding, Grid, Sprite } from "../components";
import { IMAGES, SPRITE_SPECS, SpriteSpec, Sprites } from "../config";
import { Coord2D } from "../interfaces";
@@ -13,7 +14,7 @@ export class Curry extends Entity {
this.addComponent(new Grid(gridPosition));
- this.addComponent(new Colliding());
+ this.addComponent(new Colliding(this.collisionHandler));
this.addComponent(
new BoundingBox(
@@ -42,4 +43,11 @@ export class Curry extends Entity {
),
);
}
+
+ private collisionHandler(game: Game, entity: Entity) {
+ if (entity.name === EntityNames.Player) {
+ game.removeEntity(this.id);
+ game.stop();
+ }
+ }
}
diff --git a/src/engine/entities/Entity.ts b/src/engine/entities/Entity.ts
index d5a8e6e..a9c0d4b 100644
--- a/src/engine/entities/Entity.ts
+++ b/src/engine/entities/Entity.ts
@@ -29,7 +29,7 @@ export abstract class Entity {
this.hooks.get(name)?.remove();
}
- public getComponent<T extends Component>(name: string): T {
+ public getComponent<T>(name: string): T {
if (!this.hasComponent(name)) {
throw new Error("Entity does not have component " + name);
}
diff --git a/src/engine/entities/EntityNames.ts b/src/engine/entities/EntityNames.ts
index 3f6d26f..056db9a 100644
--- a/src/engine/entities/EntityNames.ts
+++ b/src/engine/entities/EntityNames.ts
@@ -7,4 +7,5 @@ export namespace EntityNames {
export const LockedDoor = "LockedDoor";
export const Curry = "Curry";
export const FunctionApplication = "FunctionApplication";
+ export const Particles = "Particles";
}
diff --git a/src/engine/entities/LockedDoor.ts b/src/engine/entities/LockedDoor.ts
index 5e364b8..b4887d6 100644
--- a/src/engine/entities/LockedDoor.ts
+++ b/src/engine/entities/LockedDoor.ts
@@ -1,7 +1,16 @@
-import { Entity, EntityNames } from ".";
-import { BoundingBox, Colliding, Grid, Sprite } from "../components";
+import { Entity, EntityNames, Particles } from ".";
+import { Game } from "..";
+import {
+ BoundingBox,
+ Colliding,
+ Grid,
+ Sprite,
+ ComponentNames,
+} from "../components";
import { IMAGES, SPRITE_SPECS, SpriteSpec, Sprites } from "../config";
import { Coord2D } from "../interfaces";
+import { Grid as GridSystem, SystemNames } from "../systems";
+import { colors } from "../utils";
export class LockedDoor extends Entity {
private static spriteSpec: SpriteSpec = SPRITE_SPECS.get(
@@ -13,7 +22,7 @@ export class LockedDoor extends Entity {
this.addComponent(new Grid(gridPosition));
- this.addComponent(new Colliding());
+ this.addComponent(new Colliding(this.handleCollision.bind(this)));
this.addComponent(
new BoundingBox(
@@ -42,4 +51,36 @@ export class LockedDoor extends Entity {
),
);
}
+
+ private handleCollision(game: Game, entity: Entity) {
+ if (entity.name !== EntityNames.Key) {
+ return;
+ }
+
+ game.removeEntity(this.id);
+ game.removeEntity(entity.id);
+
+ const grid = this.getComponent<Grid>(ComponentNames.Grid);
+ const gridSystem = game.getSystem<GridSystem>(SystemNames.Grid);
+ const { dimension } = gridSystem;
+ const particles = new Particles({
+ center: gridSystem.gridToScreenPosition(grid.gridPosition),
+ spawnerDimensions: {
+ width: dimension.width / 2,
+ height: dimension.height / 2,
+ },
+ particleCount: 20,
+ spawnerShape: "rectangle",
+ particleShape: "rectangle",
+ particleMeanSpeed: 0.35,
+ particleSpeedVariance: 0.15,
+ particleMeanLife: 80,
+ particleMeanSize: 3,
+ particleSizeVariance: 1,
+ particleLifeVariance: 20,
+ particleColors: [colors.yellow, colors.lightYellow],
+ });
+
+ game.addEntity(particles);
+ }
}
diff --git a/src/engine/entities/Particles.ts b/src/engine/entities/Particles.ts
new file mode 100644
index 0000000..34b475c
--- /dev/null
+++ b/src/engine/entities/Particles.ts
@@ -0,0 +1,194 @@
+import { Entity, EntityNames } from ".";
+import {
+ BoundingBox,
+ Component,
+ ComponentNames,
+ Life,
+ Renderable,
+} from "../components";
+import { Coord2D, Dimension2D, DrawArgs } from "../interfaces";
+import { colors } from "../utils";
+import { normalRandom } from "../utils/random";
+
+export interface ParticleSpawnOptions {
+ spawnerDimensions: Dimension2D;
+ center: Coord2D;
+ spawnerShape: "circle" | "rectangle";
+ particleShape: "circle" | "rectangle";
+ particleCount: number;
+ particleMeanLife: number;
+ particleLifeVariance: number;
+ particleMeanSize: number;
+ particleSizeVariance: number;
+ particleMeanSpeed: number;
+ particleSpeedVariance: number;
+ particleColors: Array<string>;
+}
+
+const DEFAULT_PARTICLE_SPAWN_OPTIONS: ParticleSpawnOptions = {
+ spawnerDimensions: { width: 0, height: 0 },
+ center: { x: 0, y: 0 },
+ spawnerShape: "circle",
+ particleShape: "circle",
+ particleCount: 50,
+ particleMeanLife: 200,
+ particleLifeVariance: 50,
+ particleMeanSize: 12,
+ particleSizeVariance: 1,
+ particleMeanSpeed: 2,
+ particleSpeedVariance: 1,
+ particleColors: [colors.gray, colors.aqua, colors.lightAqua],
+};
+
+interface Particle {
+ position: Coord2D;
+ velocity: Coord2D;
+ dimension: Dimension2D;
+ color: string;
+ life: number;
+ shape: "circle" | "rectangle";
+}
+
+class ParticleRenderer extends Component implements Renderable {
+ private particles: Array<Particle>;
+ private onDeath?: () => void;
+
+ constructor(particles: Array<Particle> = [], onDeath?: () => void) {
+ super(ComponentNames.Sprite);
+
+ this.particles = particles;
+ this.onDeath = onDeath;
+ }
+
+ public update(dt: number) {
+ this.particles = this.particles.filter((particle) => {
+ particle.position.x += particle.velocity.x * dt;
+ particle.position.y += particle.velocity.y * dt;
+ particle.life -= dt;
+ return particle.life > 0;
+ });
+
+ if (this.particles.length === 0 && this.onDeath) {
+ this.onDeath();
+ }
+ }
+
+ public draw(ctx: CanvasRenderingContext2D, _drawArgs: DrawArgs) {
+ for (const particle of this.particles) {
+ ctx.fillStyle = particle.color;
+ if (particle.shape === "circle") {
+ ctx.beginPath();
+
+ ctx.ellipse(
+ particle.position.x,
+ particle.position.y,
+ particle.dimension.width / 2,
+ particle.dimension.height / 2,
+ 0,
+ 0,
+ Math.PI * 2,
+ );
+ ctx.fill();
+ } else {
+ ctx.fillRect(
+ particle.position.x - particle.dimension.width / 2,
+ particle.position.y - particle.dimension.height / 2,
+ particle.dimension.width,
+ particle.dimension.height,
+ );
+ }
+ }
+ }
+}
+
+export class Particles extends Entity {
+ constructor(options: Partial<ParticleSpawnOptions>) {
+ super(EntityNames.Particles);
+
+ const spawnOptions = {
+ ...DEFAULT_PARTICLE_SPAWN_OPTIONS,
+ ...options,
+ };
+ const particles = Array(options.particleCount)
+ .fill(0)
+ .map(() => Particles.spawnParticle(spawnOptions));
+
+ this.addComponent(new Life(true));
+ this.addComponent(
+ new ParticleRenderer(particles, () => {
+ const life = this.getComponent<Life>(ComponentNames.Life);
+ life.alive = false;
+ this.addComponent(life);
+ }),
+ );
+
+ this.addComponent(
+ new BoundingBox(
+ {
+ x: 0,
+ y: 0,
+ },
+ {
+ width: spawnOptions.spawnerDimensions.width,
+ height: spawnOptions.spawnerDimensions.height,
+ },
+ 0,
+ ),
+ );
+ }
+
+ static spawnParticle(options: ParticleSpawnOptions) {
+ const angle = Math.random() * Math.PI * 2;
+ const speed = normalRandom(
+ options.particleMeanSpeed,
+ options.particleSpeedVariance,
+ );
+ const life = normalRandom(
+ options.particleMeanLife,
+ options.particleLifeVariance,
+ );
+ const size = normalRandom(
+ options.particleMeanSize,
+ options.particleSizeVariance,
+ );
+ const color =
+ options.particleColors[
+ Math.floor(Math.random() * options.particleColors.length)
+ ];
+ const position = {
+ x: options.center.x + Math.cos(angle) * options.spawnerDimensions.width,
+ y: options.center.y + Math.sin(angle) * options.spawnerDimensions.height,
+ };
+ if (options.spawnerShape === "rectangle") {
+ // determine a random position on the edge of the spawner based on the angle
+ const halfWidth = options.spawnerDimensions.width / 2;
+ const halfHeight = options.spawnerDimensions.height / 2;
+
+ if (angle < Math.PI / 4 || angle > (Math.PI * 7) / 4) {
+ position.x += halfWidth;
+ position.y += Math.tan(angle) * halfWidth;
+ } else if (angle < (Math.PI * 3) / 4) {
+ position.y += halfHeight;
+ position.x += halfHeight / Math.tan(angle);
+ } else if (angle < (Math.PI * 5) / 4) {
+ position.x -= halfWidth;
+ position.y -= Math.tan(angle) * halfWidth;
+ } else {
+ position.y -= halfHeight;
+ position.x -= halfHeight / Math.tan(angle);
+ }
+ }
+
+ return {
+ position,
+ velocity: {
+ x: Math.cos(angle) * speed,
+ y: Math.sin(angle) * speed,
+ },
+ color,
+ life,
+ dimension: { width: size, height: size },
+ shape: options.particleShape,
+ };
+ }
+}
diff --git a/src/engine/entities/index.ts b/src/engine/entities/index.ts
index 45c95c0..cb256ec 100644
--- a/src/engine/entities/index.ts
+++ b/src/engine/entities/index.ts
@@ -7,3 +7,5 @@ export * from "./LambdaFactory";
export * from "./Key";
export * from "./LockedDoor";
export * from "./Curry";
+export * from "./FunctionApplication";
+export * from "./Particles";