fixed preloading and integrated vis
This commit is contained in:
@@ -29,13 +29,36 @@ export class AssetPreloaderService {
|
|||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
if (entry.type === 'image') {
|
if (entry.type === 'image') {
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
|
const loadedPromise = new Promise<void>((resolve, reject) => {
|
||||||
|
img.onload = () => {
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
img.onerror = (e) => {
|
||||||
|
console.error(`Failed to load image: ${entry.path}`, e);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
reject(e);
|
||||||
|
};
|
||||||
|
});
|
||||||
img.src = url;
|
img.src = url;
|
||||||
|
return loadedPromise.then(() => {
|
||||||
this.images.set(entry.path, img);
|
this.images.set(entry.path, img);
|
||||||
|
});
|
||||||
} else if (entry.type === 'audio') {
|
} else if (entry.type === 'audio') {
|
||||||
const audio = new Audio();
|
const audio = new Audio();
|
||||||
|
const loadedPromise = new Promise<void>((resolve, reject) => {
|
||||||
|
audio.oncanplaythrough = () => resolve();
|
||||||
|
audio.onerror = (e) => {
|
||||||
|
console.error(`Failed to load audio: ${entry.path}`, e);
|
||||||
|
resolve(); // Don't reject to avoid blocking all
|
||||||
|
};
|
||||||
|
});
|
||||||
audio.src = url;
|
audio.src = url;
|
||||||
|
return loadedPromise.then(() => {
|
||||||
this.audio.set(entry.path, audio);
|
this.audio.set(entry.path, audio);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
return Promise.resolve();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -44,21 +67,24 @@ export class AssetPreloaderService {
|
|||||||
|
|
||||||
private async loadAsset(entry: AssetEntry): Promise<Blob> {
|
private async loadAsset(entry: AssetEntry): Promise<Blob> {
|
||||||
const response = await fetch(`/assets/${entry.path}`);
|
const response = await fetch(`/assets/${entry.path}`);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to fetch asset: ${entry.path}`);
|
||||||
|
}
|
||||||
|
const contentType = response.headers.get('Content-Type') || undefined;
|
||||||
const reader = response.body?.getReader();
|
const reader = response.body?.getReader();
|
||||||
let loaded = 0;
|
let loaded = 0;
|
||||||
const chunks = [];
|
const chunks = [];
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const { done, value } = await reader!.read();
|
const { done, value } = await reader!.read();
|
||||||
if (done)
|
if (done) break;
|
||||||
break;
|
|
||||||
chunks.push(value);
|
chunks.push(value);
|
||||||
loaded += value.length;
|
loaded += value.length;
|
||||||
this.loadedBytes += value.length;
|
this.loadedBytes += value.length;
|
||||||
this._progress.next(this.loadedBytes / this.totalBytes);
|
this._progress.next(this.loadedBytes / this.totalBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Blob(chunks);
|
return new Blob(chunks, contentType ? { type: contentType } : undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
getImage(name: string): HTMLImageElement {
|
getImage(name: string): HTMLImageElement {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
<div class="main-area">
|
<div class="main-area">
|
||||||
<div class="canvas-wrapper" #canvasWrapper>
|
<div class="canvas-wrapper" #canvasWrapper>
|
||||||
<canvas #gameCanvas (click)="onCanvasClick($event)"></canvas>
|
<canvas #gameCanvas></canvas>
|
||||||
</div>
|
</div>
|
||||||
<div class="bottom-panel">
|
<div class="bottom-panel">
|
||||||
<button (click)="rewind()">⏮ Rewind</button>
|
<button (click)="rewind()">⏮ Rewind</button>
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { OptionsComponent } from '../options/options.component';
|
|||||||
import { SimMain } from './sim/SimMain';
|
import { SimMain } from './sim/SimMain';
|
||||||
import { GdRoot } from './data/GdRoot';
|
import { GdRoot } from './data/GdRoot';
|
||||||
import { SimCommand } from './sim/commands/SimCommand';
|
import { SimCommand } from './sim/commands/SimCommand';
|
||||||
|
import { VisMain } from './vis/VisMain';
|
||||||
|
import { SplashComponent } from '../splash/splash.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-game',
|
selector: 'app-game',
|
||||||
@@ -10,33 +12,30 @@ import { SimCommand } from './sim/commands/SimCommand';
|
|||||||
styleUrls: ['./game.component.css'],
|
styleUrls: ['./game.component.css'],
|
||||||
imports: [OptionsComponent],
|
imports: [OptionsComponent],
|
||||||
})
|
})
|
||||||
export class GameComponent implements OnInit {
|
export class GameComponent {
|
||||||
simMain!: SimMain;
|
simMain: SimMain = new SimMain();
|
||||||
private optionsOpen = false;
|
visMain!: VisMain;
|
||||||
private isPaused = false;
|
|
||||||
private lastFrameTime = 0;
|
|
||||||
private tileSize = 10; // Size of each tile in pixels
|
|
||||||
|
|
||||||
@ViewChild('gameCanvas') canvasRef!: ElementRef<HTMLCanvasElement>;
|
@ViewChild('gameCanvas') canvasRef!: ElementRef<HTMLCanvasElement>;
|
||||||
@ViewChild('canvasWrapper') wrapperRef!: ElementRef<HTMLDivElement>;
|
@ViewChild('canvasWrapper') wrapperRef!: ElementRef<HTMLDivElement>;
|
||||||
private logicalWidth = 800;
|
|
||||||
private logicalHeight = 600;
|
|
||||||
scaleX: number = 1;
|
scaleX: number = 1;
|
||||||
scaleY: number = 1;
|
scaleY: number = 1;
|
||||||
pixelScale: number = 1;
|
pixelScale: number = 1;
|
||||||
|
optionsOpen: boolean = false;
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngAfterViewInit() {
|
||||||
const gdRoot = await this.loadGdRoot();
|
const gdRoot = await this.loadGdRoot();
|
||||||
this.simMain = new SimMain(gdRoot);
|
this.simMain.setGdRoot(gdRoot);
|
||||||
requestAnimationFrame(this.gameLoop.bind(this));
|
this.visMain = new VisMain(this.simMain, SplashComponent.assetPreloader, this.wrapperRef.nativeElement, this.canvasRef.nativeElement);
|
||||||
|
window.addEventListener('resize', _ => this.visMain.onResized());
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
this.simMain.start(performance.now());
|
this.visMain.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
this.simMain.stop();
|
this.visMain.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
step() {
|
step() {
|
||||||
@@ -48,14 +47,13 @@ export class GameComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fastForward() {
|
fastForward() {
|
||||||
this.simMain.fastForwardToEnd();
|
this.simMain.executeToEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadGdRoot(): Promise<GdRoot> {
|
async loadGdRoot(): Promise<GdRoot> {
|
||||||
const data = await fetch('/assets/data/gdRoot.json').then((r) => r.json());
|
const data = await fetch('/assets/data/gdRoot.json').then((r) =>
|
||||||
// if (!isGdRoot(data)) {
|
r.json()
|
||||||
// throw new Error("Invalid GdRoot!");
|
);
|
||||||
// }
|
|
||||||
const gdRoot: GdRoot = data;
|
const gdRoot: GdRoot = data;
|
||||||
return gdRoot;
|
return gdRoot;
|
||||||
}
|
}
|
||||||
@@ -64,126 +62,15 @@ export class GameComponent implements OnInit {
|
|||||||
this.simMain.setGdRoot(await this.loadGdRoot());
|
this.simMain.setGdRoot(await this.loadGdRoot());
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
|
||||||
this.resizeCanvas();
|
|
||||||
requestAnimationFrame(this.update);
|
|
||||||
window.addEventListener('resize', this.resizeCanvas.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
update = (now: number) => {
|
|
||||||
if (!this.simMain) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.simMain.update(now)
|
|
||||||
|
|
||||||
const lastStepTime = this.simMain.isRunning
|
|
||||||
? this.simMain.lastStepTime
|
|
||||||
: now;
|
|
||||||
const t = this.simMain.isRunning
|
|
||||||
? Math.max(
|
|
||||||
Math.min((now - lastStepTime) / this.simMain.interval, 1),
|
|
||||||
0
|
|
||||||
)
|
|
||||||
: 1;
|
|
||||||
|
|
||||||
this.renderFrame(t);
|
|
||||||
|
|
||||||
requestAnimationFrame(this.update);
|
|
||||||
};
|
|
||||||
|
|
||||||
renderFrame(t: number) {
|
|
||||||
const ctx = this.canvasRef.nativeElement.getContext('2d');
|
|
||||||
if (!ctx) return;
|
|
||||||
|
|
||||||
const canvas = this.canvasRef.nativeElement;
|
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
resizeCanvas(): void {
|
|
||||||
const wrapper = this.wrapperRef.nativeElement;
|
|
||||||
const canvas = this.canvasRef.nativeElement;
|
|
||||||
|
|
||||||
const targetAspect = this.logicalWidth / this.logicalHeight;
|
|
||||||
const maxW = wrapper.clientWidth;
|
|
||||||
const maxH = wrapper.clientHeight;
|
|
||||||
|
|
||||||
let width = maxW;
|
|
||||||
let height = width / targetAspect;
|
|
||||||
|
|
||||||
if (height > maxH) {
|
|
||||||
height = maxH;
|
|
||||||
width = height * targetAspect;
|
|
||||||
}
|
|
||||||
|
|
||||||
canvas.width = width;
|
|
||||||
canvas.height = height;
|
|
||||||
|
|
||||||
this.scaleX = canvas.width / this.logicalWidth; // e.g. 800
|
|
||||||
this.scaleY = canvas.height / this.logicalHeight; // e.g. 600
|
|
||||||
|
|
||||||
// Optionally use the smaller of the two for uniform scaling
|
|
||||||
this.pixelScale = Math.min(this.scaleX, this.scaleY);
|
|
||||||
}
|
|
||||||
|
|
||||||
toScreen(x: number, y: number): [number, number] {
|
|
||||||
return [x * this.pixelScale, y * this.pixelScale];
|
|
||||||
}
|
|
||||||
|
|
||||||
fromScreen(px: number, py: number): [number, number] {
|
|
||||||
return [px / this.pixelScale, py / this.pixelScale];
|
|
||||||
}
|
|
||||||
|
|
||||||
onCanvasClick(event: MouseEvent) {
|
|
||||||
const canvas = this.canvasRef.nativeElement;
|
|
||||||
const rect = canvas.getBoundingClientRect();
|
|
||||||
const scaleX = canvas.width / rect.width;
|
|
||||||
const scaleY = canvas.height / rect.height;
|
|
||||||
|
|
||||||
const px = (event.clientX - rect.left) * scaleX;
|
|
||||||
const py = (event.clientY - rect.top) * scaleY;
|
|
||||||
|
|
||||||
const [gx, gy] = this.fromScreen(px, py);
|
|
||||||
}
|
|
||||||
|
|
||||||
private gameLoop(currentTime: number): void {
|
|
||||||
if (this.lastFrameTime === 0) {
|
|
||||||
this.lastFrameTime = currentTime;
|
|
||||||
}
|
|
||||||
const deltaTime = (currentTime - this.lastFrameTime) / 1000;
|
|
||||||
this.lastFrameTime = currentTime;
|
|
||||||
|
|
||||||
// Only update the game state if not paused
|
|
||||||
if (!this.isPaused) {
|
|
||||||
this.updateGame(deltaTime);
|
|
||||||
requestAnimationFrame(this.gameLoop.bind(this));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateGame(deltaTime: number): void {
|
|
||||||
// Implement your game update logic here using deltaTime
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called when opening options: pause the game
|
// Called when opening options: pause the game
|
||||||
public openOptions(): void {
|
public openOptions(): void {
|
||||||
this.optionsOpen = true;
|
this.optionsOpen = true;
|
||||||
this.pauseGame();
|
this.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called when closing options: resume the game
|
// Called when closing options: resume the game
|
||||||
public closeOptions(): void {
|
public closeOptions(): void {
|
||||||
this.optionsOpen = false;
|
this.optionsOpen = false;
|
||||||
this.resumeGame();
|
this.start();
|
||||||
}
|
|
||||||
|
|
||||||
private pauseGame(): void {
|
|
||||||
this.isPaused = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private resumeGame(): void {
|
|
||||||
this.isPaused = false;
|
|
||||||
// Reset the lastFrameTime to avoid a huge delta on resume
|
|
||||||
this.lastFrameTime = performance.now();
|
|
||||||
requestAnimationFrame(this.gameLoop.bind(this));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,21 +9,14 @@ import { SimCommand } from './commands/SimCommand';
|
|||||||
import { SimLevel } from './SimLevel';
|
import { SimLevel } from './SimLevel';
|
||||||
|
|
||||||
export class SimMain {
|
export class SimMain {
|
||||||
executeUntilStep(targetStep: number) {
|
|
||||||
throw new Error("Method not implemented.");
|
|
||||||
}
|
|
||||||
lastTime = 0;
|
|
||||||
currentStep = -1;
|
currentStep = -1;
|
||||||
isRunning = false;
|
|
||||||
lastStepTime: number = 0;
|
|
||||||
currentLevel: SimLevel | null = null;
|
currentLevel: SimLevel | null = null;
|
||||||
commandHistory: SimCommand[] = [];
|
commandHistory: SimCommand[] = [];
|
||||||
gdRoot: GdRoot = null!;
|
gdRoot: GdRoot = null!;
|
||||||
interval: number = 0;
|
interval: number = 0;
|
||||||
private actions: ISimAction[] = [];
|
private actions: ISimAction[] = [];
|
||||||
|
|
||||||
constructor(gdRoot: GdRoot) {
|
constructor() {
|
||||||
this.setGdRoot(gdRoot);
|
|
||||||
this.actions.push(new SimActionMoveEnemies());
|
this.actions.push(new SimActionMoveEnemies());
|
||||||
this.actions.push(new SimActionSpawnEnemies());
|
this.actions.push(new SimActionSpawnEnemies());
|
||||||
this.actions.push(new SimActionFireTowers());
|
this.actions.push(new SimActionFireTowers());
|
||||||
@@ -33,15 +26,16 @@ export class SimMain {
|
|||||||
setGdRoot(gdRoot: GdRoot) {
|
setGdRoot(gdRoot: GdRoot) {
|
||||||
this.gdRoot = gdRoot;
|
this.gdRoot = gdRoot;
|
||||||
this.interval = 1.0 / this.gdRoot.simulation.stepsPerSecond;
|
this.interval = 1.0 / this.gdRoot.simulation.stepsPerSecond;
|
||||||
|
this.currentLevel = new SimLevel(this.gdRoot, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
fastForwardToEnd() {
|
executeToEnd() {
|
||||||
this.fastForwardTo(
|
this.executeUntilStep(
|
||||||
this.commandHistory[this.commandHistory.length - 1].step
|
this.commandHistory[this.commandHistory.length - 1].step
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private fastForwardTo(target: number) {
|
executeUntilStep(target: number) {
|
||||||
this.currentStep = 0;
|
this.currentStep = 0;
|
||||||
while (this.currentStep < target) {
|
while (this.currentStep < target) {
|
||||||
this.step();
|
this.step();
|
||||||
@@ -62,19 +56,6 @@ export class SimMain {
|
|||||||
for (const cmd of commands) {
|
for (const cmd of commands) {
|
||||||
cmd.execute(this);
|
cmd.execute(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.lastStepTime = performance.now();
|
|
||||||
}
|
|
||||||
|
|
||||||
start(now: number) {
|
|
||||||
if (this.isRunning) return;
|
|
||||||
|
|
||||||
this.isRunning = true;
|
|
||||||
this.lastTime = now;
|
|
||||||
}
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
this.isRunning = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addCommand(command: SimCommand) {
|
addCommand(command: SimCommand) {
|
||||||
@@ -83,13 +64,4 @@ export class SimMain {
|
|||||||
);
|
);
|
||||||
this.commandHistory.push(command);
|
this.commandHistory.push(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
update = (now: number) => {
|
|
||||||
if (!this.isRunning) return;
|
|
||||||
|
|
||||||
while (now - this.lastTime >= this.interval) {
|
|
||||||
this.step();
|
|
||||||
this.lastTime += this.interval;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export class VisEnemy {
|
|||||||
ctx.scale(2, 2);
|
ctx.scale(2, 2);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
ctx.drawImage(this.assets.getImage("enemy-" + (this.simEnemy.index | 0) + ".svg"), -this.image.width / 2, -this.image.height / 2, this.image.width, this.image.height);
|
ctx.drawImage(this.assets.getImage("images/enemy-" + (this.simEnemy.index | 0) + ".svg"), -this.image.width / 2, -this.image.height / 2, this.image.width, this.image.height);
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -228,7 +228,7 @@ export class VisLevel {
|
|||||||
const pos = Vector2.lerp(positions[0], positions[1], t);
|
const pos = Vector2.lerp(positions[0], positions[1], t);
|
||||||
const width = this.screenCellWidth * simProjectile.size;
|
const width = this.screenCellWidth * simProjectile.size;
|
||||||
const height = this.screenCellHeight * simProjectile.size;
|
const height = this.screenCellHeight * simProjectile.size;
|
||||||
this.visMain.context.drawImage(this.assets.getImage("projectile.svg"),
|
this.visMain.context.drawImage(this.assets.getImage("images/projectile.svg"),
|
||||||
this.screenXOffset + pos.x * this.hexSize - width / 2,
|
this.screenXOffset + pos.x * this.hexSize - width / 2,
|
||||||
this.screenYOffset + pos.y * this.hexSize - height / 2,
|
this.screenYOffset + pos.y * this.hexSize - height / 2,
|
||||||
width,
|
width,
|
||||||
@@ -293,6 +293,6 @@ export class VisLevel {
|
|||||||
|
|
||||||
private drawCellImage(context: CanvasRenderingContext2D, cell: SimCell, name: string) {
|
private drawCellImage(context: CanvasRenderingContext2D, cell: SimCell, name: string) {
|
||||||
const coords = this.getScreenCoords(cell.hex);
|
const coords = this.getScreenCoords(cell.hex);
|
||||||
context.drawImage(this.assets.getImage(name), coords.x, coords.y, this.screenCellWidth, this.screenCellHeight);
|
context.drawImage(this.assets.getImage("images/" + name), coords.x, coords.y, this.screenCellWidth, this.screenCellHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,19 +14,18 @@ export class VisMain {
|
|||||||
private ready: boolean = false;
|
private ready: boolean = false;
|
||||||
private gap: number = 0;
|
private gap: number = 0;
|
||||||
assets: AssetPreloaderService;
|
assets: AssetPreloaderService;
|
||||||
|
wrapper: HTMLDivElement;
|
||||||
|
|
||||||
constructor(simMain: SimMain, assets: AssetPreloaderService, canvas: HTMLCanvasElement) {
|
constructor(simMain: SimMain, assets: AssetPreloaderService, wrapper: HTMLDivElement, canvas: HTMLCanvasElement) {
|
||||||
this.assets = assets;
|
this.assets = assets;
|
||||||
this.simMain = simMain;
|
this.simMain = simMain;
|
||||||
this.canvas = canvas;
|
this.canvas = canvas;
|
||||||
|
this.wrapper = wrapper;
|
||||||
this.context = this.canvas.getContext("2d")!;
|
this.context = this.canvas.getContext("2d")!;
|
||||||
this.context.globalCompositeOperation = "source-over";
|
this.context.globalCompositeOperation = "source-over";
|
||||||
this.wallPattern = this.createPattern(assets.getImage("wall.png"), 48);
|
this.wallPattern = this.createPattern(assets.getImage("images/wall.png"), 48);
|
||||||
this.visLevel = new VisLevel(this, this.simMain, this.simMain.gdRoot, assets);
|
this.visLevel = new VisLevel(this, this.simMain, this.simMain.gdRoot, assets);
|
||||||
const host = this;
|
this.start();
|
||||||
requestAnimationFrame(function step(timestamp) {
|
|
||||||
host.step(timestamp);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private createPattern(image: HTMLImageElement, size: number): CanvasPattern {
|
private createPattern(image: HTMLImageElement, size: number): CanvasPattern {
|
||||||
@@ -45,9 +44,8 @@ export class VisMain {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const host = this;
|
|
||||||
requestAnimationFrame((timestamp: number) => {
|
requestAnimationFrame((timestamp: number) => {
|
||||||
host.step(timestamp);
|
this.step(timestamp);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!this.startTimestamp) {
|
if (!this.startTimestamp) {
|
||||||
@@ -70,9 +68,8 @@ export class VisMain {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public onResized() {
|
public onResized() {
|
||||||
const gameHost = document.getElementById("game-host") as HTMLDivElement;
|
const width = this.wrapper.clientWidth;
|
||||||
const width = gameHost.clientWidth;
|
const height = this.wrapper.clientHeight;
|
||||||
const height = gameHost.clientHeight;
|
|
||||||
const ratio = window.devicePixelRatio;
|
const ratio = window.devicePixelRatio;
|
||||||
this.canvas.width = width * ratio;
|
this.canvas.width = width * ratio;
|
||||||
this.canvas.height = height * ratio;
|
this.canvas.height = height * ratio;
|
||||||
@@ -100,7 +97,14 @@ export class VisMain {
|
|||||||
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
private stop() {
|
stop() {
|
||||||
this.active = false;
|
this.active = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
this.active = true;
|
||||||
|
requestAnimationFrame((timestamp: number) => {
|
||||||
|
this.step(timestamp);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user