From 2c1c02d693af16a5e19ffc2a11e6828ae306ec85 Mon Sep 17 00:00:00 2001 From: Thomas Renger Date: Wed, 3 Jan 2024 16:03:15 +0100 Subject: [PATCH 01/17] fix: standalone --- src/app/app.component.ts | 9 ++++++--- src/app/app.module.ts | 16 ---------------- src/app/components/pixijs/pixijs.component.ts | 7 ++++--- src/main.ts | 11 +++++++---- 4 files changed, 17 insertions(+), 26 deletions(-) delete mode 100644 src/app/app.module.ts diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 34d1b11..b8b802f 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,10 +1,13 @@ import { Component, OnInit } from '@angular/core'; import { SplashScreen } from '@capacitor/splash-screen'; +import { PixijsComponent } from './components/pixijs/pixijs.component'; @Component({ - selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.scss'], + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.scss'], + standalone: true, + imports: [PixijsComponent], }) export class AppComponent implements OnInit { ngOnInit(): void { diff --git a/src/app/app.module.ts b/src/app/app.module.ts deleted file mode 100644 index 50c1210..0000000 --- a/src/app/app.module.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { NgModule } from '@angular/core'; -import { BrowserModule } from '@angular/platform-browser'; -import { AppComponent } from './app.component'; -import { PixijsComponent } from './components/pixijs/pixijs.component'; - -@NgModule({ - declarations: [ - AppComponent, - PixijsComponent, - ], - imports: [BrowserModule], - providers: [], - bootstrap: [AppComponent], -}) -export class AppModule { -} diff --git a/src/app/components/pixijs/pixijs.component.ts b/src/app/components/pixijs/pixijs.component.ts index f998dbd..9249509 100644 --- a/src/app/components/pixijs/pixijs.component.ts +++ b/src/app/components/pixijs/pixijs.component.ts @@ -2,9 +2,10 @@ import { Component, ElementRef, NgZone, OnInit } from '@angular/core'; import { GameService } from '../../services/game.service'; @Component({ - selector: 'app-pixijs', - template: '', - providers: [GameService], + selector: 'app-pixijs', + template: '', + providers: [GameService], + standalone: true, }) export class PixijsComponent implements OnInit { constructor( diff --git a/src/main.ts b/src/main.ts index d77f8ce..4716071 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,14 +1,17 @@ -import { enableProdMode } from '@angular/core'; +import { enableProdMode, importProvidersFrom } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; -import { AppModule } from './app/app.module'; + import { environment } from './environments/environment'; +import { AppComponent } from './app/app.component'; +import { BrowserModule, bootstrapApplication } from '@angular/platform-browser'; if (environment.production) { enableProdMode(); } -platformBrowserDynamic() - .bootstrapModule(AppModule, { ngZone: 'noop' }) +bootstrapApplication(AppComponent, { + providers: [importProvidersFrom(BrowserModule)] +}) // eslint-disable-next-line no-console .catch(err => console.error(err)); From 9d0ed279bba645879f1cabfaeb65aef101e449f1 Mon Sep 17 00:00:00 2001 From: Thomas Renger Date: Wed, 3 Jan 2024 16:05:35 +0100 Subject: [PATCH 02/17] chore: simplify --- src/app/app.component.ts | 10 +++++----- src/app/components/pixijs/pixijs.component.ts | 12 ++++++------ src/main.ts | 13 ++++--------- 3 files changed, 15 insertions(+), 20 deletions(-) diff --git a/src/app/app.component.ts b/src/app/app.component.ts index b8b802f..605883b 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -3,11 +3,11 @@ import { SplashScreen } from '@capacitor/splash-screen'; import { PixijsComponent } from './components/pixijs/pixijs.component'; @Component({ - selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.scss'], - standalone: true, - imports: [PixijsComponent], + selector: 'app-root', + standalone: true, + templateUrl: './app.component.html', + styleUrls: ['./app.component.scss'], + imports: [PixijsComponent], }) export class AppComponent implements OnInit { ngOnInit(): void { diff --git a/src/app/components/pixijs/pixijs.component.ts b/src/app/components/pixijs/pixijs.component.ts index 9249509..1363623 100644 --- a/src/app/components/pixijs/pixijs.component.ts +++ b/src/app/components/pixijs/pixijs.component.ts @@ -2,10 +2,10 @@ import { Component, ElementRef, NgZone, OnInit } from '@angular/core'; import { GameService } from '../../services/game.service'; @Component({ - selector: 'app-pixijs', - template: '', - providers: [GameService], - standalone: true, + selector: 'app-pixijs', + standalone: true, + template: '', + providers: [GameService], }) export class PixijsComponent implements OnInit { constructor( @@ -15,7 +15,7 @@ export class PixijsComponent implements OnInit { ) { } - ngOnInit(): Promise { - return this.ngZone.runOutsideAngular(() => this.pixiGame.init(this.elementRef)); + async ngOnInit(): Promise { + await this.ngZone.runOutsideAngular(() => this.pixiGame.init(this.elementRef)); } } diff --git a/src/main.ts b/src/main.ts index 4716071..f0438ed 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,17 +1,12 @@ -import { enableProdMode, importProvidersFrom } from '@angular/core'; -import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; - - -import { environment } from './environments/environment'; +import { enableProdMode } from '@angular/core'; +import { bootstrapApplication } from '@angular/platform-browser'; import { AppComponent } from './app/app.component'; -import { BrowserModule, bootstrapApplication } from '@angular/platform-browser'; +import { environment } from './environments/environment'; if (environment.production) { enableProdMode(); } -bootstrapApplication(AppComponent, { - providers: [importProvidersFrom(BrowserModule)] -}) +bootstrapApplication(AppComponent) // eslint-disable-next-line no-console .catch(err => console.error(err)); From c9d914c5ca580a4a048f468f986b4d46b4359e3a Mon Sep 17 00:00:00 2001 From: Thomas Renger Date: Wed, 3 Jan 2024 16:39:18 +0100 Subject: [PATCH 03/17] refactor: signal usage --- src/app/popups/your-are-dead-popup.ts | 2 +- src/app/services/game.service.ts | 46 ++++++++++++--------------- 2 files changed, 22 insertions(+), 26 deletions(-) diff --git a/src/app/popups/your-are-dead-popup.ts b/src/app/popups/your-are-dead-popup.ts index 5c94c84..b33a351 100644 --- a/src/app/popups/your-are-dead-popup.ts +++ b/src/app/popups/your-are-dead-popup.ts @@ -8,7 +8,7 @@ export class YouAreDeadPopup extends Popup { this.addText(this.panel, 'Du bist gestorben', { size: 14 }, { y: -60 }); this.addText(this.panel, 'Du bist leider unterlegen\n\t und der Kampf ist vorbei', { size: 12 }, { y: -40 }); this.addText(this.panel, 'Punkte', { size: 14 }, { y: -10 }); - this.addText(this.panel, gameService.kills.value.toString(), { size: 12 }, { y: 10 }); + this.addText(this.panel, gameService.kills().toString(), { size: 12 }, { y: 10 }); this.addButton('Schließen!', () => gameService.endGame(this), 2, this.panel); } diff --git a/src/app/services/game.service.ts b/src/app/services/game.service.ts index c361fdf..6b4412f 100644 --- a/src/app/services/game.service.ts +++ b/src/app/services/game.service.ts @@ -1,7 +1,5 @@ -import { ElementRef, Injectable } from '@angular/core'; +import { computed, effect, ElementRef, Injectable, signal } from '@angular/core'; import { Application } from 'pixi.js'; -import { BehaviorSubject, distinctUntilChanged, filter } from 'rxjs'; -import { tap } from 'rxjs/operators'; import { AppScreen, AppScreenConstructor } from '../models/pixijs/app-screen'; import { GameSprite } from '../models/pixijs/game-sprite'; import { CreditsPopup } from '../popups/credits-popup'; @@ -29,17 +27,26 @@ function handleMouseMove(event: { @Injectable() export class GameService { - readonly kills = new BehaviorSubject(0); + readonly kills = signal(0); private app!: Application; + private gameScreen!: GameScreenService; - private readonly level = new BehaviorSubject(1); + private readonly level = computed(() => Math.floor(this.kills() / 10) + 1); private currentPopup?: AppScreen; - private started = false; + private started = signal(false); constructor(private readonly storage: StorageService) { + effect(() => { + if (!this.started()) { + return; + } + + this.gameScreen.kills = this.kills(); + this.gameScreen.level = this.level(); + }); } async init(elementRef: ElementRef): Promise { @@ -58,18 +65,8 @@ export class GameService { await ship.init(); landscape.setup(); - const gameScreen = new GameScreenService(this.app); - this.setup(landscape, collectables, enemy, ship, gameScreen); - this.kills.pipe( - distinctUntilChanged(), - filter(value => !!value), - tap(value => this.level.next(Math.ceil(value / 10))), - tap(value => gameScreen.kills = value), - ).subscribe(); - this.level.pipe( - distinctUntilChanged(), - tap(value => gameScreen.level = value), - ).subscribe(); + this.gameScreen = new GameScreenService(this.app); + this.setup(landscape, collectables, enemy, ship, this.gameScreen); elementRef.nativeElement.appendChild(this.app.view); @@ -86,28 +83,27 @@ export class GameService { ): void { const app = this.app; - ship.spawn(); this.setupInteractions(ship); app.ticker.add(delta => { - if (!this.started) { + if (!this.started()) { return; } landscape.update(delta); - enemy.update(delta, this.level.value); + enemy.update(delta, this.level()); const hits = enemy.hit(ship.shots); - this.kills.next(this.kills.value + hits); + this.kills.update(value => value + hits); if (enemy.kill(ship.instance)) { ship.instance.energy -= 1; gameScreen.lifes = ship.instance.energy; if (ship.instance.energy === 0) { - void this.storage.setHighscore(this.kills.value, this.level.value); + void this.storage.setHighscore(this.kills(), this.level()); void this.presentPopup(YouAreDeadPopup); ship.instance.destroy(); - this.started = false; + this.started.set(false); } } gameScreen.lifes = ship.instance.energy; @@ -185,7 +181,7 @@ export class GameService { async start(requester: AppScreen): Promise { await this.hideAndRemoveScreen(requester); - this.started = true; + this.started.set(true); } async openCredits(requester: AppScreen): Promise { From 313d7470a0489002441e452d0021137a442cc8a8 Mon Sep 17 00:00:00 2001 From: Thomas Renger Date: Wed, 3 Jan 2024 16:43:56 +0100 Subject: [PATCH 04/17] chore: remove the unused method --- src/app/services/storage.service.ts | 37 ++++------------------------- 1 file changed, 4 insertions(+), 33 deletions(-) diff --git a/src/app/services/storage.service.ts b/src/app/services/storage.service.ts index 242e4bb..8d7d9c9 100644 --- a/src/app/services/storage.service.ts +++ b/src/app/services/storage.service.ts @@ -40,35 +40,6 @@ export class StorageService { }); } - getFromStore(storeName: string, resolver: (item: T) => boolean): Promise { - return new Promise((resolve, reject) => { - const dbRequest = indexedDB.open('data'); - dbRequest.onerror = function (): void { - resolve(undefined); - }; - - dbRequest.onupgradeneeded = function (event: IDBVersionChangeEvent): void { - (event.currentTarget as IDBOpenDBRequest).transaction?.abort(); - resolve(undefined); - }; - - dbRequest.onsuccess = function (event: Event): void { - const database = (event.currentTarget as IDBOpenDBRequest).result; - const store = database.transaction([storeName]).objectStore(storeName); - const objectRequest = store.getAll(); - - objectRequest.onerror = function (): void { - reject(Error('Error text')); - }; - - objectRequest.onsuccess = function (): void { - resolve((objectRequest.result as T[]).find(item => resolver(item))); - }; - }; - }, - ); - } - getManyFromStore(storeName: Store, resolver?: (item: T) => boolean): Promise { return new Promise((resolve, reject) => { const dbRequest = indexedDB.open('data'); @@ -98,10 +69,6 @@ export class StorageService { ); } - private migrateDatabase(database: IDBDatabase): void { - database.createObjectStore(Store.games, { keyPath: 'id' }); - } - getHighscore(): Promise<{ date: string, kills: number; level: number }[]> { return this.getManyFromStore(Store.games, () => true); } @@ -109,4 +76,8 @@ export class StorageService { setHighscore(kills: number, level: number): Promise { return this.toStore(Store.games, { id: crypto.randomUUID(), date: new Date().toISOString(), kills, level }); } + + private migrateDatabase(database: IDBDatabase): void { + database.createObjectStore(Store.games, { keyPath: 'id' }); + } } From bff876bb75c543b6a1c361d9eb5e39a31420f56c Mon Sep 17 00:00:00 2001 From: Thomas Renger Date: Thu, 4 Jan 2024 18:15:19 +0100 Subject: [PATCH 05/17] chore: reduce the size of the ship --- src/app/services/game-ship.service.ts | 12 +++++++----- src/assets/game/ship/ship_blue.json | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/app/services/game-ship.service.ts b/src/app/services/game-ship.service.ts index ba7fc96..601aa72 100644 --- a/src/app/services/game-ship.service.ts +++ b/src/app/services/game-ship.service.ts @@ -42,6 +42,8 @@ export class GameShipService { spawn(): void { this.#ship = new Ship(0, this.shipAnimation); this.#ship.animationSpeed = 0.167; + this.#ship._width = 20; + this.#ship._height = 20; this.#ship.play(); this.#ship.x = Math.floor(this.app.screen.width / 2); this.#ship.y = this.app.screen.height - 100; @@ -61,13 +63,13 @@ export class GameShipService { shot.anchor.set(0.5); if ((power === 1) || (power === 3 && i === 2)) { shot.x = this.#ship.x; - shot.y = this.#ship.y - 45; + shot.y = this.#ship.y - 22; } else if (power > 1 && i === 1) { - shot.x = this.#ship.x - 45; - shot.y = this.#ship.y - 12; + shot.x = this.#ship.x - 22; + shot.y = this.#ship.y - 6; } else { - shot.x = this.#ship.x + 45; - shot.y = this.#ship.y - 12; + shot.x = this.#ship.x + 22; + shot.y = this.#ship.y - 6; } this.#shots.push(shot); diff --git a/src/assets/game/ship/ship_blue.json b/src/assets/game/ship/ship_blue.json index e4e1591..eaa5149 100644 --- a/src/assets/game/ship/ship_blue.json +++ b/src/assets/game/ship/ship_blue.json @@ -64,6 +64,6 @@ "w": 99, "h": 180 }, - "scale": "1" + "scale": "2" } } From 8d01f3dee238a5c639e96da7d1cd350977938a92 Mon Sep 17 00:00:00 2001 From: Thomas Renger Date: Thu, 4 Jan 2024 18:28:02 +0100 Subject: [PATCH 06/17] feat: style the highscore --- package-lock.json | 6 ++++++ package.json | 1 + src/app/game-constants.ts | 2 +- src/app/popups/highscore-popup.ts | 14 ++++++++++---- 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2718492..e383fde 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "@capacitor/splash-screen": "^5.0.0", "@pixi/ui": "^0.9.0", "@types/offscreencanvas": "^2019.6.4", + "dayjs": "^1.11.10", "gsap": "^3.12.2", "pixi.js": "^7.1.1", "rxjs": "^7.5.4", @@ -7145,6 +7146,11 @@ "node": ">=4.0" } }, + "node_modules/dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", diff --git a/package.json b/package.json index bddfbc2..231c449 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "@capacitor/splash-screen": "^5.0.0", "@pixi/ui": "^0.9.0", "@types/offscreencanvas": "^2019.6.4", + "dayjs": "^1.11.10", "gsap": "^3.12.2", "pixi.js": "^7.1.1", "rxjs": "^7.5.4", diff --git a/src/app/game-constants.ts b/src/app/game-constants.ts index 7ec2990..3086fe9 100644 --- a/src/app/game-constants.ts +++ b/src/app/game-constants.ts @@ -19,7 +19,7 @@ export const GAME_CONFIG: GameConfig = { autoSpawnSpeed: 1.35, // per second }, ship: { - shotSpeed: 3, // speed in pixel + shotSpeed: 6, // speed in pixel }, powerUpConfig: [ { diff --git a/src/app/popups/highscore-popup.ts b/src/app/popups/highscore-popup.ts index 4700348..2237f76 100644 --- a/src/app/popups/highscore-popup.ts +++ b/src/app/popups/highscore-popup.ts @@ -1,3 +1,4 @@ +import dayjs from 'dayjs'; import { GameService } from '../services/game.service'; import { StorageService } from '../services/storage.service'; import { Popup } from './popup'; @@ -14,13 +15,18 @@ export class HighscorePopup extends Popup { override async show(): Promise { const highscore = await this.storage.getHighscore(); highscore.sort((a, b) => b.kills - a.kills); - for (let i = 0; i < Math.min(highscore.length, 5); i++) { + + this.addText(this.panel, 'Datum', { size: 12 }, { y: -60, x: -60 }); + this.addText(this.panel, 'Kills', { size: 12 }, { y: -60, x: 35 }); + this.addText(this.panel, 'Level', { size: 12 }, { y: -60, x: 100 }); + + for (let i = 1; i <= Math.min(highscore.length, 5); i++) { const dataSet = highscore[i]; const date = new Date(dataSet.date); - const dateString = `${date.getDate()}.${date.getMonth() + 1}.${date.getFullYear()} ${date.getHours()}:${date.getMinutes()}`; + const dateString = dayjs(date).format('DD.MM.YYYY HH:mm'); this.addText(this.panel, dateString, { size: 12 }, { y: -60 + i * 20, x: -60 }); - this.addText(this.panel, dataSet.kills.toString(), { size: 12 }, { y: -60 + i * 20, x: 25 }); - this.addText(this.panel, dataSet.level.toString(), { size: 12 }, { y: -60 + i * 20, x: 75 }); + this.addText(this.panel, dataSet.kills.toString(), { size: 12 }, { y: -60 + i * 20, x: 35 }); + this.addText(this.panel, dataSet.level.toString(), { size: 12 }, { y: -60 + i * 20, x: 100 }); } await super.show(); From 983f599534069bdccfdad42f2bbf67cbca08984c Mon Sep 17 00:00:00 2001 From: Thomas Renger Date: Thu, 4 Jan 2024 18:56:52 +0100 Subject: [PATCH 07/17] feat: adding meteors --- ...game-sprite.ts => animated-game-sprite.ts} | 11 +- src/app/models/pixijs/power-up-sprite.ts | 4 +- src/app/models/pixijs/ship.ts | 4 +- src/app/models/pixijs/simple-game-sprite.ts | 21 ++++ src/app/services/game-enemy.service.ts | 12 +-- src/app/services/game-meteor.service.ts | 95 ++++++++++++++++++ src/app/services/game-ship.service.ts | 8 +- src/app/services/game.service.ts | 10 +- src/app/utils/sprite.util.ts | 11 ++ .../game/meteors/meteorBrown_small1.png | Bin 0 -> 567 bytes 10 files changed, 151 insertions(+), 25 deletions(-) rename src/app/models/pixijs/{game-sprite.ts => animated-game-sprite.ts} (56%) create mode 100644 src/app/models/pixijs/simple-game-sprite.ts create mode 100644 src/app/services/game-meteor.service.ts create mode 100644 src/app/utils/sprite.util.ts create mode 100644 src/assets/game/meteors/meteorBrown_small1.png diff --git a/src/app/models/pixijs/game-sprite.ts b/src/app/models/pixijs/animated-game-sprite.ts similarity index 56% rename from src/app/models/pixijs/game-sprite.ts rename to src/app/models/pixijs/animated-game-sprite.ts index 8056efa..c62ce19 100644 --- a/src/app/models/pixijs/game-sprite.ts +++ b/src/app/models/pixijs/animated-game-sprite.ts @@ -1,6 +1,7 @@ import { AnimatedSprite, FrameObject, Sprite, Texture } from 'pixi.js'; +import { hit } from '../../utils/sprite.util'; -export class GameSprite extends AnimatedSprite { +export class AnimatedGameSprite extends AnimatedSprite { private readonly speed: number = 1; constructor( @@ -19,12 +20,6 @@ export class GameSprite extends AnimatedSprite { } hit(object2: Sprite): boolean { - const bounds1 = this.getBounds(); - const bounds2 = object2.getBounds(); - - return bounds1.x < bounds2.x + bounds2.width - && bounds1.x + bounds1.width > bounds2.x - && bounds1.y < bounds2.y + bounds2.height - && bounds1.y + bounds1.height > bounds2.y; + return hit(this, object2); } } diff --git a/src/app/models/pixijs/power-up-sprite.ts b/src/app/models/pixijs/power-up-sprite.ts index aaa4a36..9f8610c 100644 --- a/src/app/models/pixijs/power-up-sprite.ts +++ b/src/app/models/pixijs/power-up-sprite.ts @@ -1,6 +1,6 @@ import { FrameObject, Texture } from 'pixi.js'; import { PowerUpConfig } from '../power-up-config.model'; -import { GameSprite } from './game-sprite'; +import { AnimatedGameSprite } from './animated-game-sprite'; export enum PowerUp { speed, @@ -8,7 +8,7 @@ export enum PowerUp { shotPower, } -export class PowerUpSprite extends GameSprite { +export class PowerUpSprite extends AnimatedGameSprite { readonly config: PowerUpConfig; constructor(speed: number, textures: Texture[] | FrameObject[], config: PowerUpConfig) { diff --git a/src/app/models/pixijs/ship.ts b/src/app/models/pixijs/ship.ts index 7f38c2a..ee2b0f4 100644 --- a/src/app/models/pixijs/ship.ts +++ b/src/app/models/pixijs/ship.ts @@ -1,6 +1,6 @@ -import { GameSprite } from './game-sprite'; +import { AnimatedGameSprite } from './animated-game-sprite'; -export class Ship extends GameSprite { +export class Ship extends AnimatedGameSprite { shotPower = 1; shotSpeed = 1; diff --git a/src/app/models/pixijs/simple-game-sprite.ts b/src/app/models/pixijs/simple-game-sprite.ts new file mode 100644 index 0000000..561e897 --- /dev/null +++ b/src/app/models/pixijs/simple-game-sprite.ts @@ -0,0 +1,21 @@ +import { Sprite, Texture } from 'pixi.js'; +import { hit } from '../../utils/sprite.util'; + +export class GameSprite extends Sprite { + private readonly speed: number = 1; + + constructor(speed: number, texture: Texture) { + super(texture); + + this.speed = speed; + } + + update(delta: number): void { + this.rotation += 0.015 * delta; + this.y += delta * this.speed; + } + + hit(object2: Sprite): boolean { + return hit(this, object2); + } +} diff --git a/src/app/services/game-enemy.service.ts b/src/app/services/game-enemy.service.ts index 54af0ff..43f9c22 100644 --- a/src/app/services/game-enemy.service.ts +++ b/src/app/services/game-enemy.service.ts @@ -1,10 +1,10 @@ import { AnimatedSprite, Application, Assets, Spritesheet, Texture } from 'pixi.js'; import { GAME_CONFIG } from '../game-constants'; -import { GameSprite } from '../models/pixijs/game-sprite'; +import { AnimatedGameSprite } from '../models/pixijs/animated-game-sprite'; import { GameCollectableService } from './game-collectable.service'; export class GameEnemyService { - #enemies: GameSprite[] = []; + #enemies: AnimatedGameSprite[] = []; private elapsed = 0; private lastEnemySpawn = -1; @@ -17,7 +17,7 @@ export class GameEnemyService { ) { } - get enemies(): GameSprite[] { + get enemies(): AnimatedGameSprite[] { return [...this.#enemies]; } @@ -44,7 +44,7 @@ export class GameEnemyService { } } - hit(shots: GameSprite[]): number { + hit(shots: AnimatedGameSprite[]): number { let result = 0; for (const shot of shots) { const hitEnemy = this.enemies.find(enemy => !enemy.destroyed && shot.hit(enemy)); @@ -71,7 +71,7 @@ export class GameEnemyService { return result; } - kill(ship: GameSprite | undefined): boolean { + kill(ship: AnimatedGameSprite | undefined): boolean { if (!ship || ship.destroyed) { return false; } @@ -98,7 +98,7 @@ export class GameEnemyService { private spawn(level: number): void { const position = Math.floor(Math.random() * this.app.screen.width - 20) + 10; const animations: Record = this.enemySprite.animations; - const enemy = new GameSprite(1 + (0.25 * (level)), animations['frame']); + const enemy = new AnimatedGameSprite(1 + (0.25 * (level)), animations['frame']); enemy.animationSpeed = 0.167; enemy.play(); enemy.anchor.set(0.5); diff --git a/src/app/services/game-meteor.service.ts b/src/app/services/game-meteor.service.ts new file mode 100644 index 0000000..a2e97ed --- /dev/null +++ b/src/app/services/game-meteor.service.ts @@ -0,0 +1,95 @@ +import { AnimatedSprite, Application, Assets, Spritesheet, Texture } from 'pixi.js'; +import { GAME_CONFIG } from '../game-constants'; +import { AnimatedGameSprite } from '../models/pixijs/animated-game-sprite'; +import { GameSprite } from '../models/pixijs/simple-game-sprite'; + +export class GameMeteorService { + #meteors: GameSprite[] = []; + + private elapsed = 0; + private lastMeteorSpawn = -1; + private explosionSprite!: Spritesheet; + + constructor(private readonly app: Application) { + } + + get meteors(): GameSprite[] { + return [...this.#meteors]; + } + + async init(): Promise { + this.explosionSprite = await Assets.load('assets/game/explosion.json'); + } + + update(delta: number, level: number): void { + this.elapsed += delta; + + this.#meteors.forEach(enemy => enemy.update(delta)); + + this.#meteors = this.#meteors.filter(enemy => !enemy.destroyed); + this.#meteors + .filter(enemy => enemy.y > this.app.screen.height + 50) + .forEach(enemy => { + enemy.y = 0; + }); + + const check = Math.floor(this.elapsed); + if (((check % Math.floor(60 / (GAME_CONFIG.enemy.autoSpawnSpeed + (0.1 * (level - 1))))) === 0) + && (check !== this.lastMeteorSpawn)) { + this.lastMeteorSpawn = check; + this.spawn(level); + } + } + + hit(shots: AnimatedGameSprite[]): number { + for (const shot of shots) { + const hit = this.meteors.find(enemy => !enemy.destroyed && shot.hit(enemy)); + if (hit) { + // explode + const animations: Record = this.explosionSprite.animations; + const explosion = new AnimatedSprite(animations['explosion']); + explosion.animationSpeed = 0.167; + explosion.loop = false; + explosion.x = hit.x; + explosion.y = hit.y; + explosion.onComplete = (): void => explosion.destroy(); + this.app.stage.addChild(explosion); + shot.destroy(); + explosion.play(); + } + } + + return 0; + } + + kill(ship: AnimatedGameSprite | undefined): boolean { + if (!ship || ship.destroyed) { + return false; + } + + const hit = this.meteors.find(enemy => !enemy.destroyed && ship.hit(enemy)); + if (hit) { + const animations: Record = this.explosionSprite.animations; + const explosion = new AnimatedSprite(animations['explosion']); + explosion.animationSpeed = 0.167; + explosion.loop = false; + explosion.x = hit.x; + explosion.y = hit.y; + explosion.onComplete = (): void => explosion.destroy(); + this.app.stage.addChild(explosion); + explosion.play(); + return true; + } + return false; + } + + private spawn(level: number): void { + const position = Math.floor(Math.random() * this.app.screen.width - 20) + 10; + const meteor = new GameSprite(1 + (0.25 * (level)), Texture.from('assets/game/meteors/meteorBrown_small1.png')); + meteor.anchor.set(0.5); + meteor.x = position; + meteor.y = 10; + this.#meteors.push(meteor); + this.app.stage.addChild(meteor); + } +} diff --git a/src/app/services/game-ship.service.ts b/src/app/services/game-ship.service.ts index 601aa72..c74cf77 100644 --- a/src/app/services/game-ship.service.ts +++ b/src/app/services/game-ship.service.ts @@ -1,12 +1,12 @@ import { Application, Assets, Spritesheet, Texture } from 'pixi.js'; import { GAME_CONFIG } from '../game-constants'; -import { GameSprite } from '../models/pixijs/game-sprite'; +import { AnimatedGameSprite } from '../models/pixijs/animated-game-sprite'; import { Ship } from '../models/pixijs/ship'; export class GameShipService { autoFire = false; - #shots: GameSprite[] = []; + #shots: AnimatedGameSprite[] = []; #ship?: Ship; private elapsed = 0; @@ -35,7 +35,7 @@ export class GameShipService { return this.#ship; } - get shots(): GameSprite[] { + get shots(): AnimatedGameSprite[] { return [...this.#shots]; } @@ -57,7 +57,7 @@ export class GameShipService { const power = Math.min(this.instance.shotPower, 3); for (let i = 1; i <= power; i++) { - const shot = new GameSprite(-GAME_CONFIG.ship.shotSpeed, this.laserAnimation); + const shot = new AnimatedGameSprite(-GAME_CONFIG.ship.shotSpeed, this.laserAnimation); shot.animationSpeed = 0.167; shot.play(); shot.anchor.set(0.5); diff --git a/src/app/services/game.service.ts b/src/app/services/game.service.ts index 6b4412f..dee3a3a 100644 --- a/src/app/services/game.service.ts +++ b/src/app/services/game.service.ts @@ -1,7 +1,7 @@ import { computed, effect, ElementRef, Injectable, signal } from '@angular/core'; import { Application } from 'pixi.js'; +import { AnimatedGameSprite } from '../models/pixijs/animated-game-sprite'; import { AppScreen, AppScreenConstructor } from '../models/pixijs/app-screen'; -import { GameSprite } from '../models/pixijs/game-sprite'; import { CreditsPopup } from '../popups/credits-popup'; import { HighscorePopup } from '../popups/highscore-popup'; import { NavigationPopup } from '../popups/navigation-popup'; @@ -9,13 +9,14 @@ import { YouAreDeadPopup } from '../popups/your-are-dead-popup'; import { GameCollectableService } from './game-collectable.service'; import { GameEnemyService } from './game-enemy.service'; import { GameLandscapeService } from './game-landscape.service'; +import { GameMeteorService } from './game-meteor.service'; import { GameScreenService } from './game-screen.service'; import { GameShipService } from './game-ship.service'; import { StorageService } from './storage.service'; function handleMouseMove(event: { data: { originalEvent: PointerEvent | TouchEvent } -}, ship: GameSprite | undefined): void { +}, ship: AnimatedGameSprite | undefined): void { if (!ship || ship.destroyed) { return; } @@ -63,10 +64,11 @@ export class GameService { await enemy.init(); const ship = new GameShipService(this.app); await ship.init(); + const meteor = new GameMeteorService(this.app); landscape.setup(); this.gameScreen = new GameScreenService(this.app); - this.setup(landscape, collectables, enemy, ship, this.gameScreen); + this.setup(landscape, collectables, enemy, ship, this.gameScreen, meteor); elementRef.nativeElement.appendChild(this.app.view); @@ -80,6 +82,7 @@ export class GameService { enemy: GameEnemyService, ship: GameShipService, gameScreen: GameScreenService, + meteor: GameMeteorService, ): void { const app = this.app; @@ -93,6 +96,7 @@ export class GameService { landscape.update(delta); enemy.update(delta, this.level()); + meteor.update(delta, this.level()); const hits = enemy.hit(ship.shots); this.kills.update(value => value + hits); diff --git a/src/app/utils/sprite.util.ts b/src/app/utils/sprite.util.ts new file mode 100644 index 0000000..cea0623 --- /dev/null +++ b/src/app/utils/sprite.util.ts @@ -0,0 +1,11 @@ +import { Sprite } from 'pixi.js'; + +export function hit(spriteA: Sprite, spriteB: Sprite): boolean { + const bounds1 = spriteA.getBounds(); + const bounds2 = spriteB.getBounds(); + + return bounds1.x < bounds2.x + bounds2.width + && bounds1.x + bounds1.width > bounds2.x + && bounds1.y < bounds2.y + bounds2.height + && bounds1.y + bounds1.height > bounds2.y; +} diff --git a/src/assets/game/meteors/meteorBrown_small1.png b/src/assets/game/meteors/meteorBrown_small1.png new file mode 100644 index 0000000000000000000000000000000000000000..c37d1fba3e0809ce743b178af91db913b3d9e800 GIT binary patch literal 567 zcmV-70?7S|P)t8{ds;<>VoH;2QjTa(kZMqtZc~V4 zOlV9xYEC$^>5_sl5QGB=LIT-a z*IT(kn)iRa9dZe}{fCzLIZQi4WW|jhiNt<`4$YT!`*t7)GFU@&714u1_OV}ZZ`ZAbtB002ovPDHLk FV1mat?fd`$ literal 0 HcmV?d00001 From f8eb90c4c146b61bd1cb80a2b6ca54f9a6bc46a2 Mon Sep 17 00:00:00 2001 From: Thomas Renger Date: Thu, 4 Jan 2024 18:59:26 +0100 Subject: [PATCH 08/17] feat: more random in the setup --- src/app/models/pixijs/simple-game-sprite.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/app/models/pixijs/simple-game-sprite.ts b/src/app/models/pixijs/simple-game-sprite.ts index 561e897..879c95a 100644 --- a/src/app/models/pixijs/simple-game-sprite.ts +++ b/src/app/models/pixijs/simple-game-sprite.ts @@ -2,17 +2,20 @@ import { Sprite, Texture } from 'pixi.js'; import { hit } from '../../utils/sprite.util'; export class GameSprite extends Sprite { - private readonly speed: number = 1; + private readonly ySpeed: number; + private readonly xSpeed: number; constructor(speed: number, texture: Texture) { super(texture); - this.speed = speed; + this.ySpeed = Math.random() * speed; + this.xSpeed = Math.random() * speed / 2; } update(delta: number): void { this.rotation += 0.015 * delta; - this.y += delta * this.speed; + this.y += delta * this.ySpeed; + this.x += delta * this.xSpeed; } hit(object2: Sprite): boolean { From 8993cf8394b1b9b6ebb9e1a62893561469f2d45a Mon Sep 17 00:00:00 2001 From: Thomas Renger Date: Thu, 4 Jan 2024 19:03:52 +0100 Subject: [PATCH 09/17] feat: destroy the meteo if not needed anymore --- src/app/services/game-meteor.service.ts | 12 +++++------- src/app/services/game.service.ts | 4 +++- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/app/services/game-meteor.service.ts b/src/app/services/game-meteor.service.ts index a2e97ed..1d41b6f 100644 --- a/src/app/services/game-meteor.service.ts +++ b/src/app/services/game-meteor.service.ts @@ -24,14 +24,12 @@ export class GameMeteorService { update(delta: number, level: number): void { this.elapsed += delta; - this.#meteors.forEach(enemy => enemy.update(delta)); - - this.#meteors = this.#meteors.filter(enemy => !enemy.destroyed); this.#meteors - .filter(enemy => enemy.y > this.app.screen.height + 50) - .forEach(enemy => { - enemy.y = 0; - }); + .filter(meteor => meteor.y > this.app.screen.height + 50) + .forEach(meteor => meteor.destroy()); + this.#meteors = this.#meteors.filter(enemy => !enemy.destroyed); + + this.#meteors.forEach(enemy => enemy.update(delta)); const check = Math.floor(this.elapsed); if (((check % Math.floor(60 / (GAME_CONFIG.enemy.autoSpawnSpeed + (0.1 * (level - 1))))) === 0) diff --git a/src/app/services/game.service.ts b/src/app/services/game.service.ts index dee3a3a..c902b45 100644 --- a/src/app/services/game.service.ts +++ b/src/app/services/game.service.ts @@ -93,9 +93,11 @@ export class GameService { if (!this.started()) { return; } - + // moving landscape landscape.update(delta); + // spawn enemies enemy.update(delta, this.level()); + // spawn meteors meteor.update(delta, this.level()); const hits = enemy.hit(ship.shots); From d093b870e0676ca647035beb519b6c09e29aca94 Mon Sep 17 00:00:00 2001 From: Thomas Renger Date: Thu, 4 Jan 2024 19:08:27 +0100 Subject: [PATCH 10/17] feat: meteors can kill enemies --- src/app/services/game-enemy.service.ts | 7 +++++-- src/app/services/game.service.ts | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/app/services/game-enemy.service.ts b/src/app/services/game-enemy.service.ts index 43f9c22..e90b63c 100644 --- a/src/app/services/game-enemy.service.ts +++ b/src/app/services/game-enemy.service.ts @@ -1,6 +1,7 @@ import { AnimatedSprite, Application, Assets, Spritesheet, Texture } from 'pixi.js'; import { GAME_CONFIG } from '../game-constants'; import { AnimatedGameSprite } from '../models/pixijs/animated-game-sprite'; +import { GameSprite } from '../models/pixijs/simple-game-sprite'; import { GameCollectableService } from './game-collectable.service'; export class GameEnemyService { @@ -44,7 +45,7 @@ export class GameEnemyService { } } - hit(shots: AnimatedGameSprite[]): number { + hit(shots: AnimatedGameSprite[] | GameSprite[], destroyOnHit = true): number { let result = 0; for (const shot of shots) { const hitEnemy = this.enemies.find(enemy => !enemy.destroyed && shot.hit(enemy)); @@ -62,7 +63,9 @@ export class GameEnemyService { }; this.app.stage.addChild(explosion); hitEnemy.destroy(); - shot.destroy(); + if (destroyOnHit) { + shot.destroy(); + } explosion.play(); result++; } diff --git a/src/app/services/game.service.ts b/src/app/services/game.service.ts index c902b45..08572d4 100644 --- a/src/app/services/game.service.ts +++ b/src/app/services/game.service.ts @@ -99,7 +99,7 @@ export class GameService { enemy.update(delta, this.level()); // spawn meteors meteor.update(delta, this.level()); - + enemy.hit(meteor.meteors, false); const hits = enemy.hit(ship.shots); this.kills.update(value => value + hits); if (enemy.kill(ship.instance)) { From e71d305b3c510965b177b012ed4f17e2d31dee3b Mon Sep 17 00:00:00 2001 From: Thomas Renger Date: Thu, 4 Jan 2024 19:12:49 +0100 Subject: [PATCH 11/17] feat: shooting meteors without any effect --- src/app/services/game-enemy.service.ts | 2 +- src/app/services/game-meteor.service.ts | 2 +- src/app/services/game.service.ts | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/app/services/game-enemy.service.ts b/src/app/services/game-enemy.service.ts index e90b63c..0b60be1 100644 --- a/src/app/services/game-enemy.service.ts +++ b/src/app/services/game-enemy.service.ts @@ -47,7 +47,7 @@ export class GameEnemyService { hit(shots: AnimatedGameSprite[] | GameSprite[], destroyOnHit = true): number { let result = 0; - for (const shot of shots) { + for (const shot of shots.filter(s => !s.destroyed)) { const hitEnemy = this.enemies.find(enemy => !enemy.destroyed && shot.hit(enemy)); if (hitEnemy) { // explode diff --git a/src/app/services/game-meteor.service.ts b/src/app/services/game-meteor.service.ts index 1d41b6f..5bf3314 100644 --- a/src/app/services/game-meteor.service.ts +++ b/src/app/services/game-meteor.service.ts @@ -40,7 +40,7 @@ export class GameMeteorService { } hit(shots: AnimatedGameSprite[]): number { - for (const shot of shots) { + for (const shot of shots.filter(s => !s.destroyed)) { const hit = this.meteors.find(enemy => !enemy.destroyed && shot.hit(enemy)); if (hit) { // explode diff --git a/src/app/services/game.service.ts b/src/app/services/game.service.ts index 08572d4..0e33ee4 100644 --- a/src/app/services/game.service.ts +++ b/src/app/services/game.service.ts @@ -65,6 +65,7 @@ export class GameService { const ship = new GameShipService(this.app); await ship.init(); const meteor = new GameMeteorService(this.app); + await meteor.init(); landscape.setup(); this.gameScreen = new GameScreenService(this.app); @@ -100,6 +101,7 @@ export class GameService { // spawn meteors meteor.update(delta, this.level()); enemy.hit(meteor.meteors, false); + meteor.hit(ship.shots); const hits = enemy.hit(ship.shots); this.kills.update(value => value + hits); if (enemy.kill(ship.instance)) { From 2e3205d9064ca31e52b90a77d615e002f1a46a26 Mon Sep 17 00:00:00 2001 From: Thomas Renger Date: Thu, 4 Jan 2024 19:15:13 +0100 Subject: [PATCH 12/17] feat: the meteor hit should not spawn collectables --- src/app/services/game-enemy.service.ts | 7 +++++-- src/app/services/game.service.ts | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/app/services/game-enemy.service.ts b/src/app/services/game-enemy.service.ts index 0b60be1..2946fae 100644 --- a/src/app/services/game-enemy.service.ts +++ b/src/app/services/game-enemy.service.ts @@ -45,7 +45,7 @@ export class GameEnemyService { } } - hit(shots: AnimatedGameSprite[] | GameSprite[], destroyOnHit = true): number { + hit(shots: AnimatedGameSprite[] | GameSprite[], destroyOnHit = true, spawnCollectable = true): number { let result = 0; for (const shot of shots.filter(s => !s.destroyed)) { const hitEnemy = this.enemies.find(enemy => !enemy.destroyed && shot.hit(enemy)); @@ -58,7 +58,10 @@ export class GameEnemyService { explosion.x = hitEnemy.x; explosion.y = hitEnemy.y; explosion.onComplete = (): void => { - void this.collectables.spawn(explosion.x, explosion.y); + if (spawnCollectable) { + this.collectables.spawn(explosion.x, explosion.y); + } + explosion.destroy(); }; this.app.stage.addChild(explosion); diff --git a/src/app/services/game.service.ts b/src/app/services/game.service.ts index 0e33ee4..5349d00 100644 --- a/src/app/services/game.service.ts +++ b/src/app/services/game.service.ts @@ -100,7 +100,7 @@ export class GameService { enemy.update(delta, this.level()); // spawn meteors meteor.update(delta, this.level()); - enemy.hit(meteor.meteors, false); + enemy.hit(meteor.meteors, false, false); meteor.hit(ship.shots); const hits = enemy.hit(ship.shots); this.kills.update(value => value + hits); From da66618c8263da00c397a926153200b64712e4dc Mon Sep 17 00:00:00 2001 From: Thomas Renger Date: Thu, 4 Jan 2024 19:17:43 +0100 Subject: [PATCH 13/17] feat: spawn setup meteors --- src/app/game-constants.ts | 6 ++++++ src/app/services/game-meteor.service.ts | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/app/game-constants.ts b/src/app/game-constants.ts index 3086fe9..7a4cab8 100644 --- a/src/app/game-constants.ts +++ b/src/app/game-constants.ts @@ -9,6 +9,9 @@ interface GameConfig extends Config { enemy: { autoSpawnSpeed: number; }, + meteor: { + autoSpawnSpeed: number; + }, ship: { shotSpeed: number; } @@ -18,6 +21,9 @@ export const GAME_CONFIG: GameConfig = { enemy: { autoSpawnSpeed: 1.35, // per second }, + meteor: { + autoSpawnSpeed: 0.35, // per second + }, ship: { shotSpeed: 6, // speed in pixel }, diff --git a/src/app/services/game-meteor.service.ts b/src/app/services/game-meteor.service.ts index 5bf3314..0f59639 100644 --- a/src/app/services/game-meteor.service.ts +++ b/src/app/services/game-meteor.service.ts @@ -32,7 +32,7 @@ export class GameMeteorService { this.#meteors.forEach(enemy => enemy.update(delta)); const check = Math.floor(this.elapsed); - if (((check % Math.floor(60 / (GAME_CONFIG.enemy.autoSpawnSpeed + (0.1 * (level - 1))))) === 0) + if (((check % Math.floor(60 / (GAME_CONFIG.meteor.autoSpawnSpeed + (0.1 * (level - 1))))) === 0) && (check !== this.lastMeteorSpawn)) { this.lastMeteorSpawn = check; this.spawn(level); From 2123898f32ceddaa9d080272e914b91f2a0a2267 Mon Sep 17 00:00:00 2001 From: Thomas Renger Date: Thu, 4 Jan 2024 22:10:31 +0100 Subject: [PATCH 14/17] refactor: explosion should be implemented only once --- src/app/services/base.service.ts | 33 +++++++++++++++++++ src/app/services/game-enemy.service.ts | 43 ++++++++----------------- src/app/services/game-meteor.service.ts | 34 ++++++------------- 3 files changed, 55 insertions(+), 55 deletions(-) create mode 100644 src/app/services/base.service.ts diff --git a/src/app/services/base.service.ts b/src/app/services/base.service.ts new file mode 100644 index 0000000..d15a343 --- /dev/null +++ b/src/app/services/base.service.ts @@ -0,0 +1,33 @@ +import { AnimatedSprite, Application, Assets, Spritesheet, Texture } from 'pixi.js'; + +export abstract class BaseService { + private explosionSprite!: Spritesheet; + + protected constructor(protected readonly app: Application) { + } + + protected async init(): Promise { + this.explosionSprite = await Assets.load('assets/game/explosion.json'); + } + + protected explode( + x: number, + y: number, + oncomplete: (explosion: AnimatedSprite) => void = (): void => { + }, + ): void { + // explode + const animations: Record = this.explosionSprite.animations; + const explosion = new AnimatedSprite(animations['explosion']); + explosion.animationSpeed = 0.167; + explosion.loop = false; + explosion.x = x; + explosion.y = y; + explosion.onComplete = (): void => { + oncomplete(explosion); + explosion.destroy(); + }; + this.app.stage.addChild(explosion); + explosion.play(); + } +} diff --git a/src/app/services/game-enemy.service.ts b/src/app/services/game-enemy.service.ts index 2946fae..0f972d1 100644 --- a/src/app/services/game-enemy.service.ts +++ b/src/app/services/game-enemy.service.ts @@ -1,29 +1,32 @@ -import { AnimatedSprite, Application, Assets, Spritesheet, Texture } from 'pixi.js'; +import { Application, Assets, Spritesheet, Texture } from 'pixi.js'; import { GAME_CONFIG } from '../game-constants'; import { AnimatedGameSprite } from '../models/pixijs/animated-game-sprite'; import { GameSprite } from '../models/pixijs/simple-game-sprite'; +import { BaseService } from './base.service'; import { GameCollectableService } from './game-collectable.service'; -export class GameEnemyService { +export class GameEnemyService extends BaseService { #enemies: AnimatedGameSprite[] = []; private elapsed = 0; private lastEnemySpawn = -1; - private explosionSprite!: Spritesheet; + private enemySprite!: Spritesheet; constructor( - private readonly app: Application, + app: Application, private readonly collectables: GameCollectableService, ) { + super(app); } get enemies(): AnimatedGameSprite[] { return [...this.#enemies]; } - async init(): Promise { - this.explosionSprite = await Assets.load('assets/game/explosion.json'); + override async init(): Promise { + await super.init(); + this.enemySprite = await Assets.load('assets/game/enemy.json'); } @@ -50,26 +53,15 @@ export class GameEnemyService { for (const shot of shots.filter(s => !s.destroyed)) { const hitEnemy = this.enemies.find(enemy => !enemy.destroyed && shot.hit(enemy)); if (hitEnemy) { - // explode - const animations: Record = this.explosionSprite.animations; - const explosion = new AnimatedSprite(animations['explosion']); - explosion.animationSpeed = 0.167; - explosion.loop = false; - explosion.x = hitEnemy.x; - explosion.y = hitEnemy.y; - explosion.onComplete = (): void => { + this.explode(hitEnemy.x, hitEnemy.y, explosion => { if (spawnCollectable) { this.collectables.spawn(explosion.x, explosion.y); } - - explosion.destroy(); - }; - this.app.stage.addChild(explosion); + }); hitEnemy.destroy(); if (destroyOnHit) { shot.destroy(); } - explosion.play(); result++; } } @@ -84,18 +76,9 @@ export class GameEnemyService { const hitEnemy = this.enemies.find(enemy => !enemy.destroyed && ship.hit(enemy)); if (hitEnemy) { - const animations: Record = this.explosionSprite.animations; - const explosion = new AnimatedSprite(animations['explosion']); - explosion.animationSpeed = 0.167; - explosion.loop = false; - explosion.x = hitEnemy.x; - explosion.y = hitEnemy.y; - explosion.onComplete = (): void => { - explosion.destroy(); - }; - this.app.stage.addChild(explosion); + this.explode(hitEnemy.x, hitEnemy.y); hitEnemy.destroy(); - explosion.play(); + return true; } return false; diff --git a/src/app/services/game-meteor.service.ts b/src/app/services/game-meteor.service.ts index 0f59639..9a002c1 100644 --- a/src/app/services/game-meteor.service.ts +++ b/src/app/services/game-meteor.service.ts @@ -1,24 +1,25 @@ -import { AnimatedSprite, Application, Assets, Spritesheet, Texture } from 'pixi.js'; +import { Application, Texture } from 'pixi.js'; import { GAME_CONFIG } from '../game-constants'; import { AnimatedGameSprite } from '../models/pixijs/animated-game-sprite'; import { GameSprite } from '../models/pixijs/simple-game-sprite'; +import { BaseService } from './base.service'; -export class GameMeteorService { +export class GameMeteorService extends BaseService { #meteors: GameSprite[] = []; private elapsed = 0; private lastMeteorSpawn = -1; - private explosionSprite!: Spritesheet; - constructor(private readonly app: Application) { + constructor(app: Application) { + super(app); } get meteors(): GameSprite[] { return [...this.#meteors]; } - async init(): Promise { - this.explosionSprite = await Assets.load('assets/game/explosion.json'); + override async init(): Promise { + await super.init(); } update(delta: number, level: number): void { @@ -43,17 +44,8 @@ export class GameMeteorService { for (const shot of shots.filter(s => !s.destroyed)) { const hit = this.meteors.find(enemy => !enemy.destroyed && shot.hit(enemy)); if (hit) { - // explode - const animations: Record = this.explosionSprite.animations; - const explosion = new AnimatedSprite(animations['explosion']); - explosion.animationSpeed = 0.167; - explosion.loop = false; - explosion.x = hit.x; - explosion.y = hit.y; - explosion.onComplete = (): void => explosion.destroy(); - this.app.stage.addChild(explosion); + this.explode(hit.x, hit.y); shot.destroy(); - explosion.play(); } } @@ -67,15 +59,7 @@ export class GameMeteorService { const hit = this.meteors.find(enemy => !enemy.destroyed && ship.hit(enemy)); if (hit) { - const animations: Record = this.explosionSprite.animations; - const explosion = new AnimatedSprite(animations['explosion']); - explosion.animationSpeed = 0.167; - explosion.loop = false; - explosion.x = hit.x; - explosion.y = hit.y; - explosion.onComplete = (): void => explosion.destroy(); - this.app.stage.addChild(explosion); - explosion.play(); + this.explode(hit.x, hit.y); return true; } return false; From b7f9bb38160fde53ff63fb709f07de4764295b83 Mon Sep 17 00:00:00 2001 From: Thomas Renger Date: Thu, 4 Jan 2024 22:18:59 +0100 Subject: [PATCH 15/17] refactor: different settings per explosion --- src/app/services/base.service.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/services/base.service.ts b/src/app/services/base.service.ts index d15a343..04ee94a 100644 --- a/src/app/services/base.service.ts +++ b/src/app/services/base.service.ts @@ -19,10 +19,11 @@ export abstract class BaseService { // explode const animations: Record = this.explosionSprite.animations; const explosion = new AnimatedSprite(animations['explosion']); - explosion.animationSpeed = 0.167; + explosion.animationSpeed = Math.min(.3, Math.max(0.1, Math.random())); explosion.loop = false; explosion.x = x; explosion.y = y; + explosion.rotation = Math.random() * 360; explosion.onComplete = (): void => { oncomplete(explosion); explosion.destroy(); From 88b6a48ff3acdff3668f9fffb5f91197109bbae9 Mon Sep 17 00:00:00 2001 From: Thomas Renger Date: Thu, 4 Jan 2024 22:29:08 +0100 Subject: [PATCH 16/17] feat: different meteors --- src/app/services/game-meteor.service.ts | 13 ++++++++++++- src/assets/game/meteors/meteorBrown_small2.png | Bin 0 -> 547 bytes src/assets/game/meteors/meteorGrey_small1.png | Bin 0 -> 567 bytes src/assets/game/meteors/meteorGrey_small2.png | Bin 0 -> 547 bytes 4 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 src/assets/game/meteors/meteorBrown_small2.png create mode 100644 src/assets/game/meteors/meteorGrey_small1.png create mode 100644 src/assets/game/meteors/meteorGrey_small2.png diff --git a/src/app/services/game-meteor.service.ts b/src/app/services/game-meteor.service.ts index 9a002c1..a62611e 100644 --- a/src/app/services/game-meteor.service.ts +++ b/src/app/services/game-meteor.service.ts @@ -9,9 +9,17 @@ export class GameMeteorService extends BaseService { private elapsed = 0; private lastMeteorSpawn = -1; + private readonly textures: Texture[]; constructor(app: Application) { super(app); + + this.textures = [ + Texture.from('assets/game/meteors/meteorBrown_small1.png'), + Texture.from('assets/game/meteors/meteorBrown_small2.png'), + Texture.from('assets/game/meteors/meteorGrey_small1.png'), + Texture.from('assets/game/meteors/meteorGrey_small2.png'), + ]; } get meteors(): GameSprite[] { @@ -67,7 +75,10 @@ export class GameMeteorService extends BaseService { private spawn(level: number): void { const position = Math.floor(Math.random() * this.app.screen.width - 20) + 10; - const meteor = new GameSprite(1 + (0.25 * (level)), Texture.from('assets/game/meteors/meteorBrown_small1.png')); + const meteor = new GameSprite( + 1 + (0.25 * (level)), + this.textures[Math.floor(Math.random() * this.textures.length)], + ); meteor.anchor.set(0.5); meteor.x = position; meteor.y = 10; diff --git a/src/assets/game/meteors/meteorBrown_small2.png b/src/assets/game/meteors/meteorBrown_small2.png new file mode 100644 index 0000000000000000000000000000000000000000..aef6c8130de27620b2f7a7643402688df65caade GIT binary patch literal 547 zcmV+;0^I$HP)z_y7O_B}qgfDp?TH1X4rL5tT%cyk)){;}R3 zntmMb9O;ufqF+?aXw?9Yr6Km?QU^qdp&9u2svV+CQwOIv+P7+OlE#(koxAdSEj8wH za1zGoW!()%p9()92B(~iv86+&>Iz6GXU6zRZ)er0k&96)JDKk^Nl$yPH;Ux!!K2-Ns7pDv8kc6aT)Lk1f`ElMDPj zQf#i^GPIB7mbvoP7t9#=0-v<6$%e$U%3EC&O;dxtC6?B( lO8(2U8~f=h2*TKQ{s6nWUKtnX;wu0E002ovPDHLkV1gZJ=ZoT8Pas*|Cojhdg4pQVtVqkxQ&hLV?znVyoMrk16vl%lDar>>5h zp?Zaie20vSn4OuZuXcfkiItjpgo$*2gou-vcY=q7kd=RmkAjVonyRsLeuImboGcc^ zjsO4v4|GyaQ%|p7AK!1!kH7DKFAqNeL2=_(0003xNkl$^>5_sl5QGB=LIT-a z*IVR9n)iRa9dZe}{fCzLIZQi4WW|jhiNt<`4$YT!`*t7)GFU@(|=d14u1~|V}bi%3oZZv002ovPDHLk FV1m=~2pa$Z literal 0 HcmV?d00001 diff --git a/src/assets/game/meteors/meteorGrey_small2.png b/src/assets/game/meteors/meteorGrey_small2.png new file mode 100644 index 0000000000000000000000000000000000000000..c7c5dfd892f698890a5d2569d7cceb01b0b32263 GIT binary patch literal 547 zcmV+;0^I$HP)QGdWDLOnxBi9 zosyuYnW(RblbC;skA{+$kDQ`{jgonUiG`4rcY=qGo1ux7nsk4JnyRsLeuImboZL4^ zcK`qY5Oh*bQ*V#oUmu@OzwfUZ=yQ%@xKP$!)fWv_ChBpttL){moP<3ot03mSV#$e=aUM{MTQ*ZpI? z%bT9_`+#Jkj_4Ov!>t;?u{6YiFO?&R4NWodSM3p`nmRbW(Y{rKlQgbO@7$HwYpL-d z2Pa{SUe?{y=u_bb#Nd>XF}C#Csk#DU%9t^p=&6ceZiDrSm2ZPHQA7OcKn0!9dnh6?2Lz+1;=J`qw-c4Mbp$^Z;7Qf ltm6Oj>_%R)^8GNfoj<&4UK!isvZ??8002ovPDHLkV1g}31}Xpm literal 0 HcmV?d00001 From 0965b8525d40d38fe49d572a9534edf50848077f Mon Sep 17 00:00:00 2001 From: Thomas Renger Date: Thu, 4 Jan 2024 22:36:54 +0100 Subject: [PATCH 17/17] fix: capacitor update --- capacitor.config.ts | 2 +- ios/App/App/capacitor.config.json | 2 +- ios/App/Podfile | 13 +------------ ios/App/Podfile.lock | 12 ++++++------ 4 files changed, 9 insertions(+), 20 deletions(-) diff --git a/capacitor.config.ts b/capacitor.config.ts index 817f759..cbe1642 100644 --- a/capacitor.config.ts +++ b/capacitor.config.ts @@ -3,7 +3,7 @@ import { CapacitorConfig } from '@capacitor/cli'; const config: CapacitorConfig = { appId: 'net.bolzplatzarena.solarstriker', appName: 'Solar Striker', - webDir: 'dist/solar-striker', + webDir: 'dist/solar-striker/browser', plugins: { SplashScreen: { launchShowDuration: 3000, diff --git a/ios/App/App/capacitor.config.json b/ios/App/App/capacitor.config.json index 58feea4..09c9c23 100644 --- a/ios/App/App/capacitor.config.json +++ b/ios/App/App/capacitor.config.json @@ -1,7 +1,7 @@ { "appId": "net.bolzplatzarena.solarstriker", "appName": "Solar Striker", - "webDir": "dist/solar-striker", + "webDir": "dist/solar-striker/browser", "plugins": { "SplashScreen": { "launchShowDuration": 3000, diff --git a/ios/App/Podfile b/ios/App/Podfile index 9660302..3fbb34d 100644 --- a/ios/App/Podfile +++ b/ios/App/Podfile @@ -1,15 +1,4 @@ -def assertDeploymentTarget(installer) - installer.pods_project.targets.each do |target| - target.build_configurations.each do |config| - # ensure IPHONEOS_DEPLOYMENT_TARGET is at least 13.0 - deployment_target = config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'].to_f - should_upgrade = deployment_target < 13.0 && deployment_target != 0.0 - if should_upgrade - config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0' - end - end - end -end +require_relative '../../node_modules/@capacitor/ios/scripts/pods_helpers' platform :ios, '13.0' use_frameworks! diff --git a/ios/App/Podfile.lock b/ios/App/Podfile.lock index e23d120..66bd69a 100644 --- a/ios/App/Podfile.lock +++ b/ios/App/Podfile.lock @@ -1,7 +1,7 @@ PODS: - - Capacitor (5.3.0): + - Capacitor (5.6.0): - CapacitorCordova - - CapacitorCordova (5.3.0) + - CapacitorCordova (5.6.0) - CapacitorSplashScreen (5.0.6): - Capacitor @@ -19,10 +19,10 @@ EXTERNAL SOURCES: :path: "../../node_modules/@capacitor/splash-screen" SPEC CHECKSUMS: - Capacitor: 1ac9165943bc4f2137642d218c5ba05df811de69 - CapacitorCordova: b9374d68e63ce29e96ab5db994cf14fbefd722c9 + Capacitor: ebfc16cdb8116d04c101686b080342872da42d43 + CapacitorCordova: 931b48fcdbc9bc985fc2f16cec9f77c794a27729 CapacitorSplashScreen: 5fa2ab5e46cf5cc530cf16a51c80c7a986579ccd -PODFILE CHECKSUM: 7a859ce68b9d4ff260e7aa4163685f3f2d526323 +PODFILE CHECKSUM: f335af037f3993fa7c4aaec9ad935feb22d428fc -COCOAPODS: 1.12.1 +COCOAPODS: 1.14.3