From 5cbc2ed0ef718249e26ab5be3c03999341a06710 Mon Sep 17 00:00:00 2001 From: Mathieu Hermann Date: Sun, 29 Dec 2024 13:56:24 +0100 Subject: [PATCH] feat(decorations): add tree decoration --- .../1735474933_updated_tracks.js | 25 ++++++ src/app/common/services/track.service.ts | 2 + src/app/game/actors/decoration.ts | 81 +++++++++++++++++ src/app/game/actors/spectator.ts | 1 - src/app/game/config.ts | 7 ++ src/app/game/game.ts | 6 +- src/app/game/images/sprites/tree.png | Bin 0 -> 2197 bytes src/app/game/images/sprites/tree_shadow.png | Bin 0 -> 7815 bytes src/app/game/models/stockable-decoration.ts | 13 +++ src/app/game/models/stockable-track.ts | 6 +- src/app/game/models/track.ts | 16 +++- src/app/game/resources.ts | 3 + src/app/game/scenes/race.ts | 13 +++ src/app/game/utils/particles-builder.ts | 6 +- src/app/game/utils/track-builder.ts | 84 +++++++++++++----- 15 files changed, 233 insertions(+), 30 deletions(-) create mode 100644 backend/pb_migrations/1735474933_updated_tracks.js create mode 100644 src/app/game/actors/decoration.ts create mode 100644 src/app/game/images/sprites/tree.png create mode 100644 src/app/game/images/sprites/tree_shadow.png create mode 100644 src/app/game/models/stockable-decoration.ts diff --git a/backend/pb_migrations/1735474933_updated_tracks.js b/backend/pb_migrations/1735474933_updated_tracks.js new file mode 100644 index 0000000..2ddf12d --- /dev/null +++ b/backend/pb_migrations/1735474933_updated_tracks.js @@ -0,0 +1,25 @@ +/// +migrate((app) => { + const collection = app.findCollectionByNameOrId("pbc_327047008") + + // add field + collection.fields.addAt(5, new Field({ + "hidden": false, + "id": "json1404804573", + "maxSize": 0, + "name": "decorations", + "presentable": false, + "required": false, + "system": false, + "type": "json" + })) + + return app.save(collection) +}, (app) => { + const collection = app.findCollectionByNameOrId("pbc_327047008") + + // remove field + collection.fields.removeById("json1404804573") + + return app.save(collection) +}) diff --git a/src/app/common/services/track.service.ts b/src/app/common/services/track.service.ts index dbdda1d..daa6b8c 100644 --- a/src/app/common/services/track.service.ts +++ b/src/app/common/services/track.service.ts @@ -84,6 +84,8 @@ export class TrackService { stockableTrack.builderVersion = track['builderVersion']; // biome-ignore lint/complexity/useLiteralKeys: stockableTrack.gates = track['gates']; + // biome-ignore lint/complexity/useLiteralKeys: + stockableTrack.decorations = track['decorations'] || []; return stockableTrack.toTrack(); }); }) diff --git a/src/app/game/actors/decoration.ts b/src/app/game/actors/decoration.ts new file mode 100644 index 0000000..cec621d --- /dev/null +++ b/src/app/game/actors/decoration.ts @@ -0,0 +1,81 @@ +import { + Actor, + type Engine, + type Vector, + vec, + GraphicsGroup, + CircleCollider, + ColliderComponent, + CollisionType, + Sprite, + Color +} from 'excalibur'; +import { ScreenManager } from '../utils/screen-manager'; +import { Resources } from '../resources'; +import { Config } from '../config'; + +export class Decoration extends Actor { + constructor(position: Vector, type: 'tree', sizeRatio: number) { + const treeSize = (sizeRatio / 100) * Config.DECORATION_TREE_SIZE; + const shadowSize = (sizeRatio / 100) * Resources.TreeShadow.width; + super({ + anchor: vec(0, 0), + pos: position, + height: treeSize, + width: treeSize, + collisionType: CollisionType.Active, + z: treeSize, + color: Color.Pink + }); + + this.collider = new ColliderComponent(new CircleCollider({ radius: treeSize })); + + /* + const graphicsGroup = new GraphicsGroup({ + members: [ + { + graphic: new Sprite({ + image: Resources.TreeShadow, + destSize: { + width: shadowSize, + height: shadowSize + } + }), + useBounds: false, + offset: vec(0, - (shadowSize - treeSize)) + }, + { + graphic: new Sprite({ + image: Resources.Tree, + destSize: { + width: treeSize, + height: treeSize + } + }), + offset: vec(0, 0) + } + ] + }); + + this.graphics.use(graphicsGroup); + */ + + this.listenExitViewportEvent(); + } + + override update(): void { + if (ScreenManager.isNearScreen(this, this.scene!.camera) && !this.children?.length) { + // this.buildSpectators(); + } + } + + private listenExitViewportEvent(): void { + this.on('exitviewport', () => this.checkForKill()); + } + + private checkForKill(): void { + if (ScreenManager.isBehind(this.scene!.camera.pos, this.pos)) { + this.kill(); + } + } +} diff --git a/src/app/game/actors/spectator.ts b/src/app/game/actors/spectator.ts index 09bce5a..574ed7f 100644 --- a/src/app/game/actors/spectator.ts +++ b/src/app/game/actors/spectator.ts @@ -29,7 +29,6 @@ export class Spectator extends Actor { rotation: toRadians(rotation), collisionType: CollisionType.Active }); - this.collider = new ColliderComponent(new CircleCollider({ radius: Config.SPECTATOR_WIDTH / 2 })); this.originalPos = this.pos; diff --git a/src/app/game/config.ts b/src/app/game/config.ts index 934264a..fb13226 100644 --- a/src/app/game/config.ts +++ b/src/app/game/config.ts @@ -223,4 +223,11 @@ export class Config { Resources.SpectatorHit2Sound, Resources.SpectatorHit3Sound ]; + + static DECORATIONS_AMOUNT_MAX_AMOUNT = 100; + static DECORATIONS_SPRITES = { + tree: Resources.Tree.toSprite() + }; + + static DECORATION_TREE_SIZE = 65; } diff --git a/src/app/game/game.ts b/src/app/game/game.ts index df8cf54..dbbff80 100644 --- a/src/app/game/game.ts +++ b/src/app/game/game.ts @@ -45,6 +45,9 @@ export class Game extends Engine { Resources.Spectator4, Resources.SpectatorShadow, + Resources.Tree, + Resources.TreeShadow, + Resources.WinterSound, Resources.StartRaceSound, Resources.FinishRaceSound, @@ -82,7 +85,8 @@ export class Game extends Engine { maxFps: 60, canvasElementId: 'game', suppressConsoleBootMessage: true, - antialiasing: false + antialiasing: true, + suppressHiDPIScaling: true }); this.raceConfig = raceConfig; diff --git a/src/app/game/images/sprites/tree.png b/src/app/game/images/sprites/tree.png new file mode 100644 index 0000000000000000000000000000000000000000..e23b3274e2fe7879670dd513ee22e09525f57096 GIT binary patch literal 2197 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 J*MiJL{s$QL(X0Rf literal 0 HcmV?d00001 diff --git a/src/app/game/models/stockable-decoration.ts b/src/app/game/models/stockable-decoration.ts new file mode 100644 index 0000000..6277a52 --- /dev/null +++ b/src/app/game/models/stockable-decoration.ts @@ -0,0 +1,13 @@ +export class StockableDecoration { + public x: number; + public y: number; + public type: 'tree'; + public sizeRatio: number; + + constructor(x: number, y: number, type: 'tree', sizeRatio: number) { + this.x = x; + this.y = y; + this.type = type; + this.sizeRatio = sizeRatio; + } +} diff --git a/src/app/game/models/stockable-track.ts b/src/app/game/models/stockable-track.ts index 063bab7..9678570 100644 --- a/src/app/game/models/stockable-track.ts +++ b/src/app/game/models/stockable-track.ts @@ -1,4 +1,5 @@ import { TrackBuilder } from '../utils/track-builder'; +import type { StockableDecoration } from './stockable-decoration'; import type { StockableGate } from './stockable-gate'; import type { Track } from './track'; import type { TrackStyles } from './track-styles.enum'; @@ -10,6 +11,7 @@ export class StockableTrack { public style: TrackStyles; public date: Date; public gates: StockableGate[]; + public decorations: StockableDecoration[]; constructor( id?: string, @@ -17,7 +19,8 @@ export class StockableTrack { name?: string, style?: TrackStyles, date?: Date, - gates?: StockableGate[] + gates?: StockableGate[], + decorations?: StockableDecoration[] ) { this.id = id; this.name = name!; @@ -25,6 +28,7 @@ export class StockableTrack { this.style = style!; this.date = date!; this.gates = gates!; + this.decorations = decorations!; } public toTrack(): Track { diff --git a/src/app/game/models/track.ts b/src/app/game/models/track.ts index cb83cc8..6b5245d 100644 --- a/src/app/game/models/track.ts +++ b/src/app/game/models/track.ts @@ -1,3 +1,4 @@ +import type { StockableDecoration } from './stockable-decoration'; import type { StockableGate } from './stockable-gate'; import { StockableTrack } from './stockable-track'; import type { TrackStyles } from './track-styles.enum'; @@ -9,6 +10,7 @@ export class Track { public style: TrackStyles; public date: Date; public gates: StockableGate[]; + public decorations: StockableDecoration[]; constructor( id?: string, @@ -16,7 +18,8 @@ export class Track { name?: string, style?: TrackStyles, date?: Date, - gates?: StockableGate[] + gates?: StockableGate[], + decorations?: StockableDecoration[] ) { this.id = id; this.builderVersion = builderVersion; @@ -24,6 +27,7 @@ export class Track { this.style = style!; this.date = date!; this.gates = gates!; + this.decorations = decorations!; } public get fullName(): string { @@ -31,7 +35,15 @@ export class Track { } public toStockable(): StockableTrack { - return new StockableTrack(this.id, this.builderVersion, this.name, this.style, this.date, this.gates); + return new StockableTrack( + this.id, + this.builderVersion, + this.name, + this.style, + this.date, + this.gates, + this.decorations + ); } private static formatTrackName(name: string): string { diff --git a/src/app/game/resources.ts b/src/app/game/resources.ts index 67827f5..dff6a64 100644 --- a/src/app/game/resources.ts +++ b/src/app/game/resources.ts @@ -34,6 +34,9 @@ const Resources = { Spectator4: new ImageSource('./assets/images/sprites/spectator_4.png'), SpectatorShadow: new ImageSource('./assets/images/sprites/spectator_shadow.png'), + Tree: new ImageSource('./assets/images/sprites/tree.png'), + TreeShadow: new ImageSource('./assets/images/sprites/tree_shadow.png'), + WinterSound: new Sound('./assets/sounds/winter.mp3'), FinishRaceSound: new Sound('./assets/sounds/finish_race.mp3'), StartRaceSound: new Sound('./assets/sounds/start_race.mp3'), diff --git a/src/app/game/scenes/race.ts b/src/app/game/scenes/race.ts index 8b94bdc..441c259 100644 --- a/src/app/game/scenes/race.ts +++ b/src/app/game/scenes/race.ts @@ -19,6 +19,7 @@ import { SpectatorGroup } from '../actors/spectator-group'; import { RaceUiManager } from '../utils/race-ui-manager'; import type { RaceConfig } from '../models/race-config'; import { TrackBuilder } from '../utils/track-builder'; +import { Decoration } from '../actors/decoration'; export class Race extends Scene { public skier?: Skier; @@ -291,6 +292,18 @@ export class Race extends Scene { this.gates.push(gate); this.add(gate); } + + if (track.decorations?.length) { + for (const stockableDecoration of track.decorations) { + const decoration = new Decoration( + vec(stockableDecoration.x, stockableDecoration.y), + stockableDecoration.type, + stockableDecoration.sizeRatio + ); + + this.add(decoration); + } + } } private getSkierConfig(trackStyle: TrackStyles): SkierConfig { diff --git a/src/app/game/utils/particles-builder.ts b/src/app/game/utils/particles-builder.ts index a3f072b..63982fd 100644 --- a/src/app/game/utils/particles-builder.ts +++ b/src/app/game/utils/particles-builder.ts @@ -12,9 +12,9 @@ export class ParticlesBuilder { maxAngle: 6, opacity: 0.7, life: 1000, - maxSize: 5, - minSize: 5, - startSize: 5, + maxSize: 2, + minSize: 2, + startSize: 2, endSize: 1, beginColor: Color.fromRGB(23, 106, 170, 0.1), endColor: Color.Transparent diff --git a/src/app/game/utils/track-builder.ts b/src/app/game/utils/track-builder.ts index 55780a4..fd67368 100644 --- a/src/app/game/utils/track-builder.ts +++ b/src/app/game/utils/track-builder.ts @@ -5,6 +5,7 @@ import type { StockableTrack } from '../models/stockable-track'; import { TrackStyles } from '../models/track-styles.enum'; import type { GatesConfig } from '../models/gates-config'; import { StockableGate } from '../models/stockable-gate'; +import { StockableDecoration } from '../models/stockable-decoration'; export class TrackBuilder { /** @@ -14,33 +15,15 @@ export class TrackBuilder { * @returns new track */ public static designTrack(name: string, trackStyle: TrackStyles): Track { - const gates = []; const gatesConfig = TrackBuilder.getGatesConfig(trackStyle); const numberOfGates = TrackBuilder.getRandomGatesNumber(gatesConfig); const sectorGateNumbers = TrackBuilder.getSectorGateNumbers(numberOfGates); console.log('TrackBuilder - Designing a new track of ', numberOfGates, ' gates'); - let nextGateWidth = TrackBuilder.getRandomGateWidth(gatesConfig); - let nextGatePosition = TrackBuilder.getNextGatePosition(nextGateWidth, gatesConfig); + const gates = TrackBuilder.designGates(trackStyle, gatesConfig, numberOfGates, sectorGateNumbers); + const decorations = TrackBuilder.designDecorations(gates); - for (let index = 1; index < numberOfGates; index++) { - const gate = new StockableGate( - nextGatePosition.x, - nextGatePosition.y, - TrackBuilder.getGateColor(index, trackStyle), - nextGateWidth, - index, - false, - sectorGateNumbers.indexOf(index) + 1 - ); - gates.push(gate); - nextGateWidth = TrackBuilder.getRandomGateWidth(gatesConfig); - nextGatePosition = TrackBuilder.getNextGatePosition(nextGateWidth, gatesConfig, nextGatePosition); - } - - gates.push(TrackBuilder.generateFinalGate(nextGatePosition.y, numberOfGates + 1, gatesConfig)); - - return new Track(undefined, Config.CURRENT_BUILDER_VERSION, name, trackStyle, new Date(), gates); + return new Track(undefined, Config.CURRENT_BUILDER_VERSION, name, trackStyle, new Date(), gates, decorations); } /** @@ -56,7 +39,8 @@ export class TrackBuilder { stockableTrack.name, stockableTrack.style, stockableTrack.date, - stockableTrack.gates + stockableTrack.gates, + stockableTrack.decorations ); } @@ -73,6 +57,62 @@ export class TrackBuilder { return Config.DH_GATES_CONFIG; } + private static designGates( + trackStyle: TrackStyles, + gatesConfig: GatesConfig, + numberOfGates: number, + sectorGateNumbers: number[] + ): StockableGate[] { + const gates = []; + let nextGateWidth = TrackBuilder.getRandomGateWidth(gatesConfig); + let nextGatePosition = TrackBuilder.getNextGatePosition(nextGateWidth, gatesConfig); + + for (let index = 1; index < numberOfGates; index++) { + const gate = new StockableGate( + nextGatePosition.x, + nextGatePosition.y, + TrackBuilder.getGateColor(index, trackStyle), + nextGateWidth, + index, + false, + sectorGateNumbers.indexOf(index) + 1 + ); + gates.push(gate); + nextGateWidth = TrackBuilder.getRandomGateWidth(gatesConfig); + nextGatePosition = TrackBuilder.getNextGatePosition(nextGateWidth, gatesConfig, nextGatePosition); + } + + gates.push(TrackBuilder.generateFinalGate(nextGatePosition.y, numberOfGates + 1, gatesConfig)); + return gates; + } + + private static designDecorations(gates: StockableGate[]): StockableDecoration[] { + const amount = 50 + Math.floor(Math.random() * (Config.DECORATIONS_AMOUNT_MAX_AMOUNT - 50)); + const firstGatePosition = gates[0].y; + const distanceAvailable = gates[gates.length - 3].y - firstGatePosition; + const decorations = []; + + for (let i = 0; i <= amount; i++) { + let xPosition = Config.DISPLAY_WIDTH / 2; + const yPosition = Math.floor(Math.random() * distanceAvailable) + firstGatePosition; + const sizeRatio = 20 + Math.random() * 80; + const left = Math.random() < 0.5; + + const availableOffset = Math.max( + 0, + Config.DISPLAY_MIN_MARGIN - (sizeRatio / 100) * Config.DECORATION_TREE_SIZE + ); + const offset = Math.random() * availableOffset; + xPosition = left ? -xPosition + offset : xPosition - (Config.DISPLAY_MIN_MARGIN + offset); + + const decoration = new StockableDecoration(xPosition, yPosition, 'tree', sizeRatio); + decorations.push(decoration); + } + + decorations.sort((d1, d2) => d2.y - d1.y); + return decorations; + } + private static getRandomGatesNumber(gatesConfig: GatesConfig): number { return Math.floor(gatesConfig.minNumber + Math.random() * (gatesConfig.maxNumber - gatesConfig.minNumber)); }