Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Balancing #29

Merged
merged 14 commits into from
Feb 3, 2024
Prev Previous commit
Next Next commit
refactor: rockets
rengert committed Feb 2, 2024
commit e1a920ad344f1fde2e6d0d7903dc9e4097616b48
2 changes: 2 additions & 0 deletions src/app/components/pixijs/pixijs.component.ts
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@ import { GameLandscapeService } from '../../services/game-landscape.service';
import { GameMeteorService } from '../../services/game-meteor.service';
import { GameScreenService } from '../../services/game-screen.service';
import { GameShipService } from '../../services/game-ship.service';
import { GameShotService } from '../../services/game-shot.service';
import { GameService } from '../../services/game.service';

@Component({
@@ -20,6 +21,7 @@ import { GameService } from '../../services/game.service';
GameLandscapeService,
GameMeteorService,
GameScreenService,
GameShotService,
GameShipService,
ExplosionService,
GameService,
14 changes: 10 additions & 4 deletions src/app/game-constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { PowerUp } from './models/pixijs/power-up-sprite';
import { ShipType } from './models/pixijs/ship-type.enum';
import { PowerUpConfig } from './models/power-up-config.model';

interface Config {
@@ -12,9 +13,9 @@ interface GameConfig extends Config {
meteor: {
autoSpawnSpeed: number;
},
ship: {
ships: Record<ShipType, {
shotSpeed: number;
}
}>,
}

export const GAME_CONFIG: GameConfig = {
@@ -24,8 +25,13 @@ export const GAME_CONFIG: GameConfig = {
meteor: {
autoSpawnSpeed: 0.35, // per second
},
ship: {
shotSpeed: 6, // speed in pixel
ships: {
[ShipType.ship]: {
shotSpeed: 6, // speed in pixel
},
[ShipType.enemy]: {
shotSpeed: 4, // speed in pixel
},
},
powerUpConfig: [
{
2 changes: 1 addition & 1 deletion src/app/models/pixijs/animated-game-sprite.ts
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ import { AnimatedSprite, FrameObject, Sprite, Texture } from 'pixi.js';
import { hit } from '../../utils/sprite.util';

export class AnimatedGameSprite extends AnimatedSprite {
private readonly speed: number = 1;
protected readonly speed: number = 1;

targetX?: number;

10 changes: 10 additions & 0 deletions src/app/models/pixijs/rocket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { AnimatedGameSprite } from './animated-game-sprite';
import { Ship } from './ship';

export class Rocket extends AnimatedGameSprite {
reference: Ship | undefined;

override hit(object2: AnimatedGameSprite): boolean {
return this.reference !== object2 && super.hit(object2);
}
}
4 changes: 4 additions & 0 deletions src/app/models/pixijs/ship-type.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum ShipType {
ship = 'ship',
enemy = 'enemy',
}
34 changes: 34 additions & 0 deletions src/app/models/pixijs/ship.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,50 @@
import { FrameObject, Texture } from 'pixi.js';
import { GameShotService } from '../../services/game-shot.service';
import { AnimatedGameSprite } from './animated-game-sprite';
import { ShipType } from './ship-type.enum';

export class Ship extends AnimatedGameSprite {
shotPower = 1;
shotSpeed = 1;
lastShot = 0;
autoFire = false;

#energy = 10;

private elapsed = 0;

// eslint-disable-next-line max-params
constructor(
readonly type: ShipType,
private readonly shotService: GameShotService,
speed: number,
textures: Texture[] | FrameObject[],
autoUpdate?: boolean) {
super(speed, textures, autoUpdate);
}

set energy(value: number) {
this.#energy = Math.min(value, 10);
};

get energy(): number {
return this.#energy;
}

shot(): void {
this.shotService.shot(this.shotPower, this, this.speed <= 0);
}

override update(delta: number): void {
super.update(delta);

this.elapsed += delta;

const check = Math.floor(this.elapsed);
// todo: check if we want two power ups for speed
if (this.autoFire && (check % Math.floor(60 / this.shotSpeed) === 0) && (check !== this.lastShot)) {
this.lastShot = check;
this.shot();
}
}
}
16 changes: 12 additions & 4 deletions src/app/services/game-enemy.service.ts
Original file line number Diff line number Diff line change
@@ -2,9 +2,13 @@ import { Injectable } from '@angular/core';
import { Assets, Spritesheet, Texture } from 'pixi.js';
import { GAME_CONFIG } from '../game-constants';
import { AnimatedGameSprite } from '../models/pixijs/animated-game-sprite';
import { Rocket } from '../models/pixijs/rocket';
import { Ship } from '../models/pixijs/ship';
import { ShipType } from '../models/pixijs/ship-type.enum';
import { GameSprite } from '../models/pixijs/simple-game-sprite';
import { BaseService } from './base.service';
import { GameCollectableService } from './game-collectable.service';
import { GameShotService } from './game-shot.service';

@Injectable()
export class GameEnemyService extends BaseService {
@@ -15,7 +19,10 @@ export class GameEnemyService extends BaseService {

private enemySprite!: Spritesheet;

constructor(private readonly collectables: GameCollectableService) {
constructor(
private readonly collectables: GameCollectableService,
private readonly shotService: GameShotService,
) {
super();
}

@@ -45,9 +52,9 @@ export class GameEnemyService extends BaseService {
}
}

hit(shots: AnimatedGameSprite[] | GameSprite[], destroyOnHit = true, spawnCollectable = true): number {
hit(elements: Rocket[] | GameSprite[], destroyOnHit = true, spawnCollectable = true): number {
let result = 0;
for (const shot of shots.filter(s => !s.destroyed)) {
for (const shot of elements.filter(s => !s.destroyed)) {
const hitEnemy = this.enemies.find(enemy => !enemy.destroyed && shot.hit(enemy));
if (hitEnemy) {
this.explode(hitEnemy.x, hitEnemy.y, explosion => {
@@ -84,7 +91,8 @@ export class GameEnemyService extends BaseService {
private spawn(level: number): void {
const position = Math.floor(Math.random() * this.application.screen.width - 20) + 10;
const animations: Record<string, Texture[]> = this.enemySprite.animations;
const enemy = new AnimatedGameSprite(1 + (0.25 * (level)), animations['frame']);
const enemy = new Ship(ShipType.enemy, this.shotService, 1 + (0.25 * (level)), animations['frame']);
enemy.autoFire = true;
enemy.animationSpeed = 0.167;
enemy.play();
enemy.anchor.set(0.5);
69 changes: 7 additions & 62 deletions src/app/services/game-ship.service.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,20 @@
import { Injectable } from '@angular/core';
import { Assets, Spritesheet, Texture } from 'pixi.js';
import { GAME_CONFIG } from '../game-constants';
import { AnimatedGameSprite } from '../models/pixijs/animated-game-sprite';
import { Ship } from '../models/pixijs/ship';
import { ShipType } from '../models/pixijs/ship-type.enum';
import { ApplicationService } from './application.service';
import { GameShotService } from './game-shot.service';

@Injectable()
export class GameShipService {
autoFire = false;

#shots: AnimatedGameSprite[] = [];
#ship?: Ship;

private elapsed = 0;
private lastShot = 0;

private laserAnimation: Texture[] | undefined;
private shipAnimation: Texture[] | undefined;

constructor(private readonly application: ApplicationService) {
constructor(
private readonly application: ApplicationService,
private readonly gameShot: GameShotService,
) {
}

async init(): Promise<void> {
@@ -27,12 +23,6 @@ export class GameShipService {
const animations: Record<string, Texture[]> = ship.animations;
this.shipAnimation = animations['ship'];
}

if (!this.laserAnimation) {
const laser = await Assets.load<Spritesheet>('assets/game/laser.json');
const laserAnimations: Record<string, Texture[]> = laser.animations;
this.laserAnimation = laserAnimations['laser'];
}
}

get instance(): Ship {
@@ -42,12 +32,8 @@ export class GameShipService {
return this.#ship;
}

get shots(): AnimatedGameSprite[] {
return [...this.#shots];
}

spawn(): void {
this.#ship = new Ship(0, this.shipAnimation !);
this.#ship = new Ship(ShipType.ship, this.gameShot, 0, this.shipAnimation !);
this.#ship.animationSpeed = 0.167;
this.#ship._width = 20;
this.#ship._height = 20;
@@ -56,45 +42,4 @@ export class GameShipService {
this.#ship.y = this.application.screen.height - 100;
this.application.stage.addChild(this.#ship);
}

shot(): void {
if (!this.#ship || this.#ship.destroyed) {
return;
}

const power = Math.min(this.instance.shotPower, 3);
for (let i = 1; i <= power; i++) {
const shot = new AnimatedGameSprite(-GAME_CONFIG.ship.shotSpeed, this.laserAnimation !);
shot.animationSpeed = 0.167;
shot.play();
shot.anchor.set(0.5);
if ((power === 1) || (power === 3 && i === 2)) {
shot.x = this.#ship.x;
shot.y = this.#ship.y - 22;
} else if (power > 1 && i === 1) {
shot.x = this.#ship.x - 22;
shot.y = this.#ship.y - 6;
} else {
shot.x = this.#ship.x + 22;
shot.y = this.#ship.y - 6;
}

this.#shots.push(shot);
this.application.stage.addChild(shot);
}
}

update(delta: number): void {
this.elapsed += delta;

const check = Math.floor(this.elapsed);
// todo: check if we want two power ups for speed
if (this.autoFire && (check % Math.floor(60 / this.instance.shotSpeed) === 0) && (check !== this.lastShot)) {
this.lastShot = check;
this.shot();
}

this.#shots.filter(shot => !shot.destroyed && shot.y < 0).forEach(shot => shot.destroy());
this.#shots = this.#shots.filter(shot => !shot.destroyed);
}
}
58 changes: 58 additions & 0 deletions src/app/services/game-shot.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Injectable } from '@angular/core';
import { Assets, Spritesheet, Texture } from 'pixi.js';
import { GAME_CONFIG } from '../game-constants';
import { Rocket } from '../models/pixijs/rocket';
import { Ship } from '../models/pixijs/ship';
import { ApplicationService } from './application.service';

@Injectable()
export class GameShotService {
#shots: Rocket[] = [];

private laserAnimation: Texture[] | undefined;

constructor(private readonly application: ApplicationService) {
}

get shots(): Rocket[] {
return [...this.#shots];
}

async init(): Promise<void> {
if (!this.laserAnimation) {
const laser = await Assets.load<Spritesheet>('assets/game/laser.json');
const laserAnimations: Record<string, Texture[]> = laser.animations;
this.laserAnimation = laserAnimations['laser'];
}
}

shot(power: number, ship: Ship, up: boolean): void {
const { x, y } = ship;
for (let i = 1; i <= power; i++) {
const shot = new Rocket(up ? -GAME_CONFIG.ships[ship.type].shotSpeed : GAME_CONFIG.ships[ship.type].shotSpeed, this.laserAnimation !);
shot.reference = ship;
shot.animationSpeed = 0.167;
shot.play();
shot.rotation = up ? 0 : Math.PI;
shot.anchor.set(0.5);
if ((power === 1) || (power === 3 && i === 2)) {
shot.x = x;
shot.y = y - 22;
} else if (power > 1 && i === 1) {
shot.x = x - 22;
shot.y = y - 6;
} else {
shot.x = x + 22;
shot.y = y - 6;
}

this.#shots.push(shot);
this.application.stage.addChild(shot);
}
}

update(): void {
this.#shots.filter(shot => !shot.destroyed && shot.y < 0).forEach(shot => shot.destroy());
this.#shots = this.#shots.filter(shot => !shot.destroyed);
}
}
13 changes: 8 additions & 5 deletions src/app/services/game.service.ts
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@ 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 { GameShotService } from './game-shot.service';
import { StorageService } from './storage.service';

function handleMouseMove(event: {
@@ -34,6 +35,7 @@ export class GameService {
private readonly ship = inject(GameShipService);
private readonly meteor = inject(GameMeteorService);
private readonly gameScreen = inject(GameScreenService);
private readonly shotService = inject(GameShotService);

readonly kills = signal(0);

@@ -62,6 +64,7 @@ export class GameService {
await this.collectables.init();
await this.enemy.init();
await this.ship.init();
await this.shotService.init();
this.landscape.setup();
this.gameScreen.init();

@@ -86,8 +89,8 @@ export class GameService {
// spawn meteors
this.meteor.update(delta, this.level());
this.enemy.hit(this.meteor.meteors, false, false);
this.meteor.hit(this.ship.shots);
const hits = this.enemy.hit(this.ship.shots);
this.meteor.hit(this.shotService.shots);
const hits = this.enemy.hit(this.shotService.shots);
this.kills.update(value => value + hits);
if (
this.enemy.kill(this.ship.instance)
@@ -106,15 +109,15 @@ export class GameService {

this.gameScreen.lifes = this.ship.instance.energy;
this.collectables.collect(this.ship.instance);
this.ship.update(delta);
this.shotService.update();
});
}

private setupInteractions(ship: GameShipService): void {
this.application.stage.eventMode = 'dynamic';
this.application.stage.hitArea = this.application.screen;
this.application.stage.on('pointerdown', () => ship.autoFire = true);
this.application.stage.on('pointerup', () => ship.autoFire = false);
this.application.stage.on('pointerdown', () => ship.instance.autoFire = true);
this.application.stage.on('pointerup', () => ship.instance.autoFire = false);
this.application.stage.on(
'pointermove',
(event: unknown) => handleMouseMove(