Skip to content

Commit

Permalink
Split out toaster and winddial components and make them more efficient
Browse files Browse the repository at this point in the history
  • Loading branch information
Half-Shot committed Oct 8, 2024
1 parent 7e38bd2 commit 18799ca
Show file tree
Hide file tree
Showing 11 changed files with 229 additions and 107 deletions.
2 changes: 1 addition & 1 deletion src/components/changelog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export function ChangelogModal({buildNumber, buildCommit, lastCommit}: {buildNum

const newChangesModal = useMemo(() => {
console.log(latestChanges);
let title = buildNumber ? `Build #${buildNumber}` : `Developer Build ${buildCommit}`;
const title = buildNumber ? `Build #${buildNumber}` : `Developer Build ${buildCommit}`;
return <dialog ref={modalRef}>
<h1>{title}</h1>
<p>
Expand Down
3 changes: 3 additions & 0 deletions src/components/menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ export function Menu({onNewGame}: Props) {
<li>
<button onClick={() => onStartNewGame("boneIsles")}>Bone Isles</button>
</li>
<li>
<button onClick={() => onStartNewGame("uiTest")}>UI Test</button>
</li>
</ul>
<ChangelogModal buildNumber={buildNumber} buildCommit={buildCommit} lastCommit={lastCommit}/>
</main>;
Expand Down
5 changes: 2 additions & 3 deletions src/entities/playable/playable.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { Point, Sprite, UPDATE_PRIORITY, Text, DEG_TO_RAD, Graphics } from "pixi.js";
import { PhysicsEntity } from "../phys/physicsEntity";
import { GameWorld, PIXELS_PER_METER, RapierPhysicsObject } from "../../world";
import { Coordinate, magnitude, MetersValue, mult, sub } from "../../utils";
import { GameWorld, RapierPhysicsObject } from "../../world";
import { magnitude, MetersValue, mult, sub } from "../../utils";
import { Vector2 } from "@dimforge/rapier2d-compat";
import { IPhysicalEntity, OnDamageOpts } from "../entity";
import { Explosion } from "../explosion";
import { teamGroupToColorSet, WormInstance } from "../../logic/teams";
import { applyGenericBoxStyle, DefaultTextStyle } from "../../mixins/styles";
import { Viewport } from "pixi-viewport";
Expand Down
20 changes: 10 additions & 10 deletions src/entities/playable/worm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { teamGroupToColorSet, WormInstance } from '../../logic/teams';
import { calculateMovement } from '../../movementController';
import { Viewport } from 'pixi-viewport';
import { magnitude, pointOnRadius, sub } from '../../utils';
import { GameStateOverlay } from '../../overlays/gameStateOverlay';
import { Toaster } from '../../overlays/toaster';
import { FireResultHitEnemy, FireResultHitOwnTeam, FireResultHitSelf, FireResultKilledEnemy, FireResultKilledOwnTeam, FireResultKilledSelf, FireResultMiss, templateRandomText, TurnEndTextFall, TurnStartText, WeaponTimerText, WormDeathGeneric, WormDeathSinking } from '../../text/toasts';
import { WeaponBazooka } from '../../weapons';

Expand Down Expand Up @@ -69,8 +69,8 @@ export class Worm extends PlayableEntity {
private targettingGfx: Graphics;
private facingRight = true;

static create(parent: Viewport, world: GameWorld, position: Coordinate, wormIdent: WormInstance, onFireWeapon: FireFn, gameOverlay?: GameStateOverlay) {
const ent = new Worm(position, world, parent, wormIdent, onFireWeapon, gameOverlay);
static create(parent: Viewport, world: GameWorld, position: Coordinate, wormIdent: WormInstance, onFireWeapon: FireFn, toaster?: Toaster) {
const ent = new Worm(position, world, parent, wormIdent, onFireWeapon, toaster);
world.addBody(ent, ent.physObject.collider);
parent.addChild(ent.targettingGfx);
parent.addChild(ent.sprite);
Expand Down Expand Up @@ -103,7 +103,7 @@ export class Worm extends PlayableEntity {
this.currentWeapon = weapon;
}

private constructor(position: Coordinate, world: GameWorld, parent: Viewport, wormIdent: WormInstance, private readonly onFireWeapon: FireFn, private readonly toaster?: GameStateOverlay) {
private constructor(position: Coordinate, world: GameWorld, parent: Viewport, wormIdent: WormInstance, private readonly onFireWeapon: FireFn, private readonly toaster?: Toaster) {
const sprite = new Sprite(Worm.texture);
sprite.scale.set(0.5, 0.5);
sprite.anchor.set(0.5, 0.5);
Expand All @@ -124,7 +124,7 @@ export class Worm extends PlayableEntity {

onWormSelected() {
this.state = WormState.Idle;
this.toaster?.addNewToast(templateRandomText(TurnStartText, {
this.toaster?.pushToast(templateRandomText(TurnStartText, {
WormName: this.wormIdent.name,
TeamName: this.wormIdent.team.name,
}), 3000, teamGroupToColorSet(this.wormIdent.team.group).fg);
Expand Down Expand Up @@ -189,7 +189,7 @@ export class Worm extends PlayableEntity {
break;
}
if (this.weaponTimerSecs !== oldTime) {
this.toaster?.addNewToast(templateRandomText(WeaponTimerText, {
this.toaster?.pushToast(templateRandomText(WeaponTimerText, {
Time: this.weaponTimerSecs.toString(),
}), 1250, undefined, true);
}
Expand Down Expand Up @@ -308,7 +308,7 @@ export class Worm extends PlayableEntity {
return;
}

this.toaster?.addNewToast(templateRandomText(randomTextSet, {
this.toaster?.pushToast(templateRandomText(randomTextSet, {
WormName: this.wormIdent.name,
TeamName: this.wormIdent.team.name,
}), 2000);
Expand Down Expand Up @@ -422,7 +422,7 @@ export class Worm extends PlayableEntity {
const damage = this.impactVelocity*Worm.impactDamageMultiplier;
this.health -= damage;
this.state = WormState.Inactive;
this.toaster?.addNewToast(templateRandomText(TurnEndTextFall, {
this.toaster?.pushToast(templateRandomText(TurnEndTextFall, {
WormName: this.wormIdent.name,
TeamName: this.wormIdent.team.name,
}), 2000);
Expand Down Expand Up @@ -454,14 +454,14 @@ export class Worm extends PlayableEntity {
// XXX: This might need to be dead.
this.state = WormState.Inactive;
if (this.isSinking) {
this.toaster?.addNewToast(templateRandomText(WormDeathSinking, {
this.toaster?.pushToast(templateRandomText(WormDeathSinking, {
WormName: this.wormIdent.name,
TeamName: this.wormIdent.team.name,
}), 3000);
// Sinking death
} else if (this.health === 0) {
// Generic death
this.toaster?.addNewToast(templateRandomText(WormDeathGeneric, {
this.toaster?.pushToast(templateRandomText(WormDeathGeneric, {
WormName: this.wormIdent.name,
TeamName: this.wormIdent.team.name,
}), 3000);
Expand Down
2 changes: 1 addition & 1 deletion src/entities/water.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export class Water implements IPhysicalEntity {
}

addToWorld(parent: Container, world: GameWorld) {
parent.addChildAt(this.waterMesh, parent.children.length-1);
parent.addChildAt(this.waterMesh, Math.max(0, parent.children.length-1));
world.addBody(this, this.physObject.collider);
}

Expand Down
3 changes: 3 additions & 0 deletions src/game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import grenadeIsland from './scenarios/grenadeIsland';
import borealisTribute from './scenarios/borealisTribute';
import testingGround from './scenarios/testingGround';
import boneIsles from './scenarios/boneIsles';
import uiTest from './scenarios/uiTest';
import { Viewport } from 'pixi-viewport';
import { getAssets } from "./assets";
import { GameDebugOverlay } from "./overlays/debugOverlay";
Expand Down Expand Up @@ -83,6 +84,8 @@ export class Game {
testingGround(this);
} else if (this.level === "boneIsles") {
boneIsles(this);
} else if (this.level === "uiTest") {
uiTest(this);
} else {
throw Error('Unknown level');
}
Expand Down
102 changes: 15 additions & 87 deletions src/overlays/gameStateOverlay.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,23 @@
import { ColorSource, Container, Graphics, Text, Ticker, UPDATE_PRIORITY } from "pixi.js";
import { Container, Graphics, Text, Ticker, UPDATE_PRIORITY } from "pixi.js";
import { GameState } from "../logic/gamestate";
import { applyGenericBoxStyle, DefaultTextStyle } from "../mixins/styles";
import { teamGroupToColorSet } from "../logic/teams";
import { GameWorld, MAX_WIND } from "../world";

interface Toast {
text: string;
timer: number;
color: ColorSource;
interruptable: boolean;
}

import { GameWorld } from "../world";
import { Toaster } from "./toaster";
import { WindDial } from "./windDial";

export class GameStateOverlay {
public readonly physicsSamples: number[] = [];
private readonly roundTimer: Text;
private readonly tickerFn: (dt: Ticker) => void;
private readonly toastGfx: Graphics;
private readonly gfx: Graphics;
private previousStateIteration = -1;
private visibleTeamHealth: Record<string, number> = {};
private healthChangeTensionTimer: number|null = null;
private readonly largestHealthPool: number;

private toastTime = 0;
private currentToastIsInterruptable = true;
private toaster: Toast[] = [];
private readonly toastBox: Text;
public readonly toaster: Toaster;
private readonly winddial: WindDial;

constructor(
private readonly ticker: Ticker,
Expand All @@ -36,7 +27,6 @@ export class GameStateOverlay {
private readonly screenWidth: number,
private readonly screenHeight: number,
) {
const topY = this.screenHeight / 20;
this.roundTimer = new Text({
text: '60',
style: {
Expand All @@ -45,23 +35,16 @@ export class GameStateOverlay {
align: 'center',
},
});
this.toastBox = new Text({
text: '',
style: {
...DefaultTextStyle,
fontSize: 48,
align: 'center',
},
});
this.toastBox.position.set(this.screenWidth / 2, topY);
this.toastBox.anchor.set(0.5, 0.5);

this.toaster = new Toaster(screenWidth, screenHeight);
this.winddial = new WindDial(screenWidth, screenHeight, this.gameState);

this.roundTimer.position.set((this.screenWidth / 30) + 14, ((this.screenHeight / 10) * 9) + 12);
this.gfx = new Graphics();
this.toastGfx = new Graphics();
this.stage.addChild(this.toaster.container);
this.stage.addChild(this.gfx);
this.stage.addChild(this.roundTimer);
this.stage.addChild(this.toastGfx);
this.stage.addChild(this.toastBox);
this.stage.addChild(this.winddial.container);
this.tickerFn = this.update.bind(this);
this.ticker.add(this.tickerFn, undefined, UPDATE_PRIORITY.UTILITY);
this.largestHealthPool = this.gameState.getTeams().reduceRight((value, team) => Math.max(value, team.maxHealth) , 0);
Expand All @@ -70,52 +53,17 @@ export class GameStateOverlay {
});
}

/**
* Adds some text to be displayed at the top of the screen.
* @param text The text notice.
* @param timer How long should the notice be displayed.
* @param color The colour of the text.
* @param interruptable Should the toast be interrupted by the next notice?
*/
public addNewToast(text: string, timer = 5000, color: ColorSource = '#FFFFFF', interruptable = false) {
this.toaster.splice(0,0, { text, timer, color, interruptable });
}


private update(dt: Ticker) {
this.toaster.update(dt);
this.winddial.update();
const centerX = this.screenWidth / 2;
if (this.healthChangeTensionTimer && !this.gameWorld.areEntitiesMoving()) {
this.healthChangeTensionTimer -= dt.deltaTime;
}

const shouldChangeTeamHealth = this.healthChangeTensionTimer !== null && this.healthChangeTensionTimer <= 0;

const shouldInterrupt = this.currentToastIsInterruptable && this.toaster.length;
if (!this.toastBox.text || shouldInterrupt) {
const newToast = this.toaster.pop();
if (newToast) {
this.toastGfx.clear();
this.toastBox.text = newToast.text;
this.toastBox.style.fill = newToast.color;
this.toastTime = newToast.timer;
this.currentToastIsInterruptable = newToast.interruptable;
this.toastGfx.position.set(
(this.screenWidth / 2) - (this.toastBox.width /2) - 6,
(this.screenHeight / 20) - (this.toastBox.height/2) - 4,
)
applyGenericBoxStyle(this.toastGfx).roundRect(0, 0, this.toastBox.width + 12, this.toastBox.height + 8, 4).stroke().fill();
}
}


if (this.toastBox.text) {
// Render toast
this.toastTime -= dt.deltaMS;
if (this.toastTime <= 0) {
this.toastBox.text = '';
this.toastTime = 0;
this.toastGfx.clear();
}
}

if (this.previousStateIteration === this.gameState.iteration && !shouldChangeTeamHealth) {
return;
Expand Down Expand Up @@ -149,26 +97,6 @@ export class GameStateOverlay {
// Round timer
applyGenericBoxStyle(this.gfx, currentTeamColors.fg).roundRect(this.roundTimer.x - 8, this.roundTimer.y - 8, this.roundTimer.width + 16, this.roundTimer.height + 16, 4).stroke().fill();

// Wind
const windX = ((this.screenWidth / 20) * 16) + 14;
const windY = ((this.screenHeight / 10) * 9) + 12;
applyGenericBoxStyle(this.gfx).roundRect(
windX,
windY,
200,
25,
4
).stroke().fill();

const windScale = this.gameWorld.wind / MAX_WIND;
applyGenericBoxStyle(this.gfx).roundRect(
(windScale >= 0 ? (windX + 100) : ((windX + 100) + (100*windScale))) + 2,
windY + 2,
96 * Math.abs(windScale),
21,
4
).fill({ color: windScale > 0 ? 0xEE3333 : 0x3333EE });

// For each team:
// TODO: Sort by health and group
// TODO: Evenly space.
Expand Down
80 changes: 80 additions & 0 deletions src/overlays/toaster.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { ColorSource, Graphics, Container, Ticker, Text } from "pixi.js";
import { DefaultTextStyle, applyGenericBoxStyle } from "../mixins/styles";

interface Toast {
text: string;
timer: number;
color: ColorSource;
interruptable: boolean;
}

/**
* Displays toast at the top of the screen during gameplay.
*/
export class Toaster {
private readonly gfx: Graphics;
private toastTime = 0;
private currentToastIsInterruptable = true;
private toaster: Toast[] = [];
private readonly text: Text;
public readonly container: Container;

constructor(private readonly screenWidth: number, private readonly screenHeight: number) {
const topY = this.screenHeight / 20;
this.container = new Container();
this.gfx = new Graphics();
this.text = new Text({
text: '',
style: {
...DefaultTextStyle,
fontSize: 48,
align: 'center',
},
});
this.text.position.set(this.screenWidth / 2, topY);
this.text.anchor.set(0.5, 0.5);
this.container.addChild(this.gfx, this.text);
}

public update(dt: Ticker) {
const shouldInterrupt = this.currentToastIsInterruptable && this.toaster.length;

if (!this.text.text || shouldInterrupt) {
const newToast = this.toaster.pop();
if (newToast) {
this.gfx.clear();
this.text.text = newToast.text;
this.text.style.fill = newToast.color;
this.toastTime = newToast.timer;
this.currentToastIsInterruptable = newToast.interruptable;
this.gfx.position.set(
(this.screenWidth / 2) - (this.text.width /2) - 6,
(this.screenHeight / 20) - (this.text.height/2) - 4,
)
applyGenericBoxStyle(this.gfx).roundRect(0, 0, this.text.width + 12, this.text.height + 8, 4).stroke().fill();
}
}

if (this.text.text) {
// Render toast
this.toastTime -= dt.deltaMS;
this.container.alpha = Math.min(1, this.toastTime/100);
if (this.toastTime <= 0) {
this.text.text = '';
this.toastTime = 0;
this.gfx.clear();
}
}
}

/**
* Adds some text to be displayed at the top of the screen.
* @param text The text notice.
* @param timer How long should the notice be displayed.
* @param color The colour of the text.
* @param interruptable Should the toast be interrupted by the next notice?
*/
public pushToast(text: string, timer = 5000, color: ColorSource = '#FFFFFF', interruptable = false) {
this.toaster.splice(0,0, { text, timer, color, interruptable });
}
}
Loading

0 comments on commit 18799ca

Please sign in to comment.