Files
ThreadNet-Web/src/effects/spaceinvaders/index.ts
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

112 lines
3.3 KiB
TypeScript
Raw Normal View History

2021-05-18 10:29:00 +01:00
/*
2024-09-09 14:57:16 +01:00
Copyright 2024 New Vector Ltd.
Copyright 2021-2023 The Matrix.org Foundation C.I.C.
2021-05-18 10:29:00 +01:00
2024-09-09 14:57:16 +01:00
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
2021-05-18 10:29:00 +01:00
*/
import ICanvasEffect from "../ICanvasEffect";
import { arrayFastClone } from "../../utils/arrays";
export type SpaceInvadersOptions = {
/**
* The maximum number of invaders to render at a given time
*/
maxCount: number;
/**
* The amount of gravity to apply to the invaders
*/
gravity: number;
2021-06-29 13:11:58 +01:00
};
2021-05-18 10:29:00 +01:00
type Invader = {
x: number;
y: number;
xCol: number;
gravity: number;
2021-06-29 13:11:58 +01:00
};
2021-05-18 10:29:00 +01:00
export const DefaultOptions: SpaceInvadersOptions = {
maxCount: 50,
gravity: 0.005,
};
const KEY_FRAME_INTERVAL = 15; // 15ms, roughly
const GLYPH = "👾";
export default class SpaceInvaders implements ICanvasEffect {
private readonly options: SpaceInvadersOptions;
public constructor(options: { [key: string]: any }) {
2021-06-29 13:11:58 +01:00
this.options = { ...DefaultOptions, ...options };
2021-05-18 10:29:00 +01:00
}
private context: CanvasRenderingContext2D | null = null;
private particles: Array<Invader> = [];
2023-02-08 12:12:14 +00:00
private lastAnimationTime = 0;
2021-05-18 10:29:00 +01:00
2023-02-08 12:12:14 +00:00
public isRunning = false;
2021-05-18 10:29:00 +01:00
public start = async (canvas: HTMLCanvasElement, timeout = 3000): Promise<void> => {
2021-05-18 10:29:00 +01:00
if (!canvas) {
return;
}
this.context = canvas.getContext("2d");
this.particles = [];
const count = this.options.maxCount;
while (this.particles.length < count) {
this.particles.push(this.resetParticle({} as Invader, canvas.width, canvas.height));
}
this.isRunning = true;
requestAnimationFrame(this.renderLoop);
if (timeout) {
window.setTimeout(this.stop, timeout);
}
2021-06-29 13:11:58 +01:00
};
2021-05-18 10:29:00 +01:00
public stop = async (): Promise<void> => {
2021-05-18 10:29:00 +01:00
this.isRunning = false;
2021-06-29 13:11:58 +01:00
};
2021-05-18 10:29:00 +01:00
private resetParticle = (particle: Invader, width: number, height: number): Invader => {
particle.x = Math.random() * width;
particle.y = Math.random() * -height;
particle.xCol = particle.x;
particle.gravity = this.options.gravity + Math.random() * 6 + 4;
return particle;
2021-06-29 13:11:58 +01:00
};
2021-05-18 10:29:00 +01:00
private renderLoop = (): void => {
if (!this.context || !this.context.canvas) {
return;
}
if (this.particles.length === 0) {
this.context.clearRect(0, 0, this.context.canvas.width, this.context.canvas.height);
} else {
const timeDelta = Date.now() - this.lastAnimationTime;
if (timeDelta >= KEY_FRAME_INTERVAL || !this.lastAnimationTime) {
// Clear the screen first
this.context.clearRect(0, 0, this.context.canvas.width, this.context.canvas.height);
this.lastAnimationTime = Date.now();
2021-05-18 11:58:15 +01:00
this.animateAndRenderInvaders();
2021-05-18 10:29:00 +01:00
}
requestAnimationFrame(this.renderLoop);
}
};
private animateAndRenderInvaders(): void {
2021-05-18 10:29:00 +01:00
if (!this.context || !this.context.canvas) {
return;
}
this.context.font = "50px Twemoji";
for (const particle of arrayFastClone(this.particles)) {
particle.y += particle.gravity;
this.context.save();
this.context.fillText(GLYPH, particle.x, particle.y);
this.context.restore();
}
}
}