Skip to content

Commit

Permalink
feat(sounds): add multiple spectators sounds for ambiance
Browse files Browse the repository at this point in the history
  • Loading branch information
mathieuher committed Feb 22, 2024
1 parent 3f038de commit 64133e8
Show file tree
Hide file tree
Showing 13 changed files with 713 additions and 652 deletions.
172 changes: 96 additions & 76 deletions src/actors/spectator-group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,80 +7,100 @@ import { Game } from '../game';
import { Resources } from '../resources';

export class SpectatorGroup extends Actor {
private engine: Engine;
private side: 'left' | 'right';
private density: number;
private sound = Config.SPECTATORS_SOUNDS[Math.round(Math.random() * (Config.SPECTATORS_SOUNDS.length - 1))];
private soundInstance?: Audio;
private shouldPlayIntenseSound = Math.random() < 0.15;

constructor(engine: Engine, position: Vector, density: number, side: 'left' | 'right') {
super({
anchor: vec(0, 0),
pos: position,
height: density * (Config.SPECTATOR_HEIGHT * 0.7),
width: Config.DISPLAY_MIN_MARGIN,
});

this.engine = engine;
this.density = density;
this.side = side;

this.listenExitViewportEvent();

}

update(): void {
if (ScreenManager.isNearScreen(this, this.scene.camera) && !this.children?.length) {
this.buildSpectators();
(this.engine as Game).soundPlayer.playSound(this.sound, 0.001, true, true);
this.soundInstance = this.sound.instances[this.sound.instanceCount() - 1];
}

if (this.soundInstance) {
this.adjustSoundVolume();
}

if (ScreenManager.isBehind(this.scene.camera.pos, this.pos.add(vec(0, this.height))) && this.shouldPlayIntenseSound) {
this.shouldPlayIntenseSound = false;
(this.engine as Game).soundPlayer.playSound(Resources.SpectatorsIntenseSound, 0.3, false, true);
}


if (this.children?.length) {
this.rotateSpectators();
}
}

private listenExitViewportEvent(): void {
this.on('exitviewport', () => this.checkForKill());
}

private checkForKill(): void {
if (ScreenManager.isBehind(this.scene.camera.pos, this.pos)) {
this.soundInstance?.stop();
this.kill();
}
}

private buildSpectators(): void {
for (let i = 1; i <= this.density; i++) {
const xPosition = Math.random() * (this.width - Config.SPECTATOR_WIDTH);
const yPosition = Math.random() * (this.height - Config.SPECTATOR_HEIGHT);
const rotation = this.side === 'left' ? 0 : 180;
this.addChild(new Spectator(vec(xPosition, yPosition), rotation));
}
}

private adjustSoundVolume(): void {
const skierYPosition = (this.scene as Race).skier!.pos.y;
const distance = Math.min(Math.abs(this.pos.y - skierYPosition), 300);
this.soundInstance!.volume = Math.max(0.001, 1 - (distance / 300)) * (this.density / 20) * 0.1;
}

private rotateSpectators(): void {
// TODO
}


private engine: Engine;
private side: 'left' | 'right';
private density: number;
private sound = Config.SPECTATORS_SOUNDS[~~(Math.random() * Config.SPECTATORS_SOUNDS.length)];
private soundInstance?: Audio;
private shouldPlayIntenseSound = Math.random() < Config.SPECTATORS_INTENSE_SOUND_PROBABILITY;
private shouldPlayBellsSound = Math.random() < Config.SPECTATORS_BELLS_SOUND_PROBABILITY;
private bellsSound = this.shouldPlayBellsSound
? Config.SPECTATORS_BELLS_SOUNDS[~~(Math.random() * Config.SPECTATORS_BELLS_SOUNDS.length)]
: null;
private bellsSoundInstance?: Audio;

constructor(engine: Engine, position: Vector, density: number, side: 'left' | 'right') {
super({
anchor: vec(0, 0),
pos: position,
height: density * (Config.SPECTATOR_HEIGHT * 0.7),
width: Config.DISPLAY_MIN_MARGIN,
});

this.engine = engine;
this.density = density;
this.side = side;

this.listenExitViewportEvent();
}

update(): void {
if (ScreenManager.isNearScreen(this, this.scene.camera) && !this.children?.length) {
this.buildSpectators();
(this.engine as Game).soundPlayer.playSound(this.sound, 0.001, true, true);
this.soundInstance = this.sound.instances[this.sound.instanceCount() - 1];
if (this.shouldPlayBellsSound) {
(this.engine as Game).soundPlayer.playSound(this.bellsSound!, 0.001, true, true);
this.bellsSoundInstance = this.bellsSound!.instances[this.bellsSound!.instanceCount() - 1];
}
}

if (this.soundInstance) {
this.adjustSoundVolume();
}

if (
ScreenManager.isBehind(this.scene.camera.pos, this.pos.add(vec(0, this.height))) &&
this.shouldPlayIntenseSound
) {
this.shouldPlayIntenseSound = false;
(this.engine as Game).soundPlayer.playSound(Resources.SpectatorsIntenseSound, 0.3, false, true);
}

if (this.children?.length) {
this.rotateSpectators();
}
}

private listenExitViewportEvent(): void {
this.on('exitviewport', () => this.checkForKill());
}

private checkForKill(): void {
if (ScreenManager.isBehind(this.scene.camera.pos, this.pos)) {
this.soundInstance?.stop();
this.bellsSoundInstance?.stop();
this.kill();
}
}

private buildSpectators(): void {
for (let i = 1; i <= this.density; i++) {
const xPosition = Math.random() * (this.width - Config.SPECTATOR_WIDTH);
const yPosition = Math.random() * (this.height - Config.SPECTATOR_HEIGHT);
const rotation = this.side === 'left' ? 0 : 180;
this.addChild(new Spectator(vec(xPosition, yPosition), rotation));
}
}

private adjustSoundVolume(): void {
const skierYPosition = (this.scene as Race).skier!.pos.y;
const distance = Math.min(
Math.abs(this.pos.y - skierYPosition),
Config.SPECTATORS_MAX_SOUND_DISTANCE,
);
this.soundInstance!.volume =
Math.max(0.001, 1 - distance / Config.SPECTATORS_MAX_SOUND_DISTANCE) *
(this.density / Config.SPECTATORS_MAX_DENSITY) *
Config.SPECTATORS_SOUND_INTENSITY;
if (this.bellsSoundInstance) {
this.bellsSoundInstance!.volume =
Math.max(0.001, 1 - distance / Config.SPECTATORS_MAX_SOUND_DISTANCE) *
Config.SPECTATORS_BELLS_SOUND_INTENSITY;
}
}

private rotateSpectators(): void {
// TODO
}
}
66 changes: 36 additions & 30 deletions src/actors/spectator.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,48 @@
import { Actor, CollisionType, Vector, toRadians, vec, CollisionStartEvent } from 'excalibur';
import { Actor, CollisionType, Vector, toRadians, vec, CollisionStartEvent, Sound } from 'excalibur';
import { Config } from '../config';
import { Skier } from './skier';
import { Game } from '../game';
import { Resources } from '../resources';

export class Spectator extends Actor {
constructor(position: Vector, rotation: number) {
super({
anchor: vec(0, 1),
pos: position,
width: Config.SPECTATOR_WIDTH,
height: Config.SPECTATOR_HEIGHT,
rotation: toRadians(rotation),
collisionType: CollisionType.Active,
});
private hitSound!: Sound;

this.useRandomSpectatorGraphic();
constructor(position: Vector, rotation: number) {
super({
anchor: vec(0, 1),
pos: position,
width: Config.SPECTATOR_WIDTH,
height: Config.SPECTATOR_HEIGHT,
rotation: toRadians(rotation),
collisionType: CollisionType.Active,
});

const randomizer = Math.random();
this.useRandomSpectatorGraphic(randomizer);
this.useRandomHitSound(randomizer);
}

}
onInitialize() {
this.on('collisionstart', evt => this.onPreCollision(evt));
}

onInitialize() {
this.on('collisionstart', evt => this.onPreCollision(evt));
}
private useRandomSpectatorGraphic(randomizer: number): void {
const spriteNumber = ~~(randomizer * Config.SPECTATOR_SPRITES.length);
this.graphics.use(Config.SPECTATOR_SPRITES[spriteNumber]);
}

private useRandomSpectatorGraphic(): void {
const spriteNumber = Math.floor(Math.random() * (Config.SPECTATOR_SPRITES.length - 1));
this.graphics.use(Config.SPECTATOR_SPRITES[spriteNumber]);
}
private useRandomHitSound(randomizer: number): void {
const soundNumber = ~~(randomizer * Config.SPECTATOR_HIT_SOUNDS.length);
this.hitSound = Config.SPECTATOR_HIT_SOUNDS[soundNumber];
}

private onPreCollision(evt: CollisionStartEvent): void {
if (evt.other instanceof Skier) {
(this.scene.engine as Game).soundPlayer.playSound(
Resources.SpectatorHitSound,
0.5,
false,
true
);
}
}
private onPreCollision(evt: CollisionStartEvent): void {
if (evt.other instanceof Skier) {
(this.scene.engine as Game).soundPlayer.playSound(
this.hitSound,
Config.SPECTATOR_HIT_SOUND_INTENSITY,
false,
true,
);
}
}
}
Loading

0 comments on commit 64133e8

Please sign in to comment.