Add mission brief audio playback using AudioEngineV2
All checks were successful
Build / build (push) Successful in 1m53s

- Add mission_brief_audio field to CloudLevelEntry interface
- Update missionBrief.ts to use AudioEngineV2.createSoundAsync()
  instead of legacy Sound class (fixes audio not playing)
- Pass audioEngine to MissionBrief.initialize() from Level1
- Add welcome_rookie.mp3 audio file

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Michael Mainguy 2025-12-01 07:27:42 -06:00
parent c87b85de40
commit e3422ef9f2
5 changed files with 30 additions and 2 deletions

View File

@ -431,7 +431,7 @@ export class Level1 implements Level {
log.info('[Level1] _missionBrief object:', this._missionBrief); log.info('[Level1] _missionBrief object:', this._missionBrief);
log.info('[Level1] Ship exists:', !!this._ship); log.info('[Level1] Ship exists:', !!this._ship);
log.info('[Level1] Ship ID in scene:', DefaultScene.MainScene.getNodeById('Ship') !== null); log.info('[Level1] Ship ID in scene:', DefaultScene.MainScene.getNodeById('Ship') !== null);
this._missionBrief.initialize(); this._missionBrief.initialize(this._audioEngine);
log.info('[Level1] ========== MISSION BRIEF INITIALIZATION COMPLETE =========='); log.info('[Level1] ========== MISSION BRIEF INITIALIZATION COMPLETE ==========');
log.debug('Mission brief initialized'); log.debug('Mission brief initialized');

View File

@ -28,6 +28,7 @@ export interface CloudLevelEntry {
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
reviewNotes?: string; reviewNotes?: string;
missionBriefAudio?: string;
} }
/** /**
@ -55,6 +56,7 @@ interface LevelRow {
created_at: string; created_at: string;
updated_at: string; updated_at: string;
review_notes?: string; review_notes?: string;
mission_brief_audio?: string;
} }
/** /**
@ -93,6 +95,7 @@ function rowToEntry(row: LevelRow): CloudLevelEntry {
createdAt: row.created_at, createdAt: row.created_at,
updatedAt: row.updated_at, updatedAt: row.updated_at,
reviewNotes: row.review_notes, reviewNotes: row.review_notes,
missionBriefAudio: row.mission_brief_audio,
}; };
} }

View File

@ -8,6 +8,7 @@ import {
} from "@babylonjs/gui"; } from "@babylonjs/gui";
import { DefaultScene } from "../../core/defaultScene"; import { DefaultScene } from "../../core/defaultScene";
import {MeshBuilder, Vector3, Observable, Observer} from "@babylonjs/core"; import {MeshBuilder, Vector3, Observable, Observer} from "@babylonjs/core";
import type { AudioEngineV2, StaticSound } from "@babylonjs/core";
import log from '../../core/logger'; import log from '../../core/logger';
import { LevelConfig } from "../../levels/config/levelConfig"; import { LevelConfig } from "../../levels/config/levelConfig";
import { CloudLevelEntry } from "../../services/cloudLevelService"; import { CloudLevelEntry } from "../../services/cloudLevelService";
@ -22,11 +23,14 @@ export class MissionBrief {
private _isVisible: boolean = false; private _isVisible: boolean = false;
private _onStartCallback: (() => void) | null = null; private _onStartCallback: (() => void) | null = null;
private _triggerObserver: Observer<void> | null = null; private _triggerObserver: Observer<void> | null = null;
private _audioEngine: AudioEngineV2 | null = null;
private _currentSound: StaticSound | null = null;
/** /**
* Initialize the mission brief as a fullscreen overlay * Initialize the mission brief as a fullscreen overlay
*/ */
public initialize(): void { public initialize(audioEngine?: AudioEngineV2): void {
this._audioEngine = audioEngine || null;
log.info('[MissionBrief] ========== INITIALIZE CALLED =========='); log.info('[MissionBrief] ========== INITIALIZE CALLED ==========');
const scene = DefaultScene.MainScene; const scene = DefaultScene.MainScene;
log.info('[MissionBrief] Scene exists:', !!scene); log.info('[MissionBrief] Scene exists:', !!scene);
@ -232,6 +236,21 @@ export class MissionBrief {
this._container.isVisible = true; this._container.isVisible = true;
this._isVisible = true; this._isVisible = true;
// Play mission brief audio if specified
if (directoryEntry?.missionBriefAudio && this._audioEngine) {
log.info('[MissionBrief] Playing audio:', directoryEntry.missionBriefAudio);
this._audioEngine.createSoundAsync(
"missionBriefAudio",
directoryEntry.missionBriefAudio,
{ loop: false, volume: 1.0 }
).then(sound => {
this._currentSound = sound;
sound.play();
}).catch(err => {
log.error('[MissionBrief] Failed to load audio:', err);
});
}
log.info('[MissionBrief] ========== CONTAINER NOW VISIBLE =========='); log.info('[MissionBrief] ========== CONTAINER NOW VISIBLE ==========');
log.info('[MissionBrief] Container.isVisible:', this._container.isVisible); log.info('[MissionBrief] Container.isVisible:', this._container.isVisible);
log.info('[MissionBrief] _isVisible flag:', this._isVisible); log.info('[MissionBrief] _isVisible flag:', this._isVisible);
@ -298,6 +317,10 @@ export class MissionBrief {
* Clean up resources * Clean up resources
*/ */
public dispose(): void { public dispose(): void {
if (this._currentSound) {
this._currentSound.dispose();
this._currentSound = null;
}
if (this._advancedTexture) { if (this._advancedTexture) {
this._advancedTexture.dispose(); this._advancedTexture.dispose();
this._advancedTexture = null; this._advancedTexture = null;
@ -305,6 +328,7 @@ export class MissionBrief {
this._container = null; this._container = null;
this._onStartCallback = null; this._onStartCallback = null;
this._triggerObserver = null; this._triggerObserver = null;
this._audioEngine = null;
this._isVisible = false; this._isVisible = false;
log.debug('[MissionBrief] Disposed'); log.debug('[MissionBrief] Disposed');
} }

View File

@ -138,6 +138,7 @@ create table public.levels
tags text[] default '{}'::text[], tags text[] default '{}'::text[],
config jsonb not null, config jsonb not null,
mission_brief text[] default '{}'::text[], mission_brief text[] default '{}'::text[],
mission_brief_audio text,
level_type text default 'private'::text not null level_type text default 'private'::text not null
constraint valid_level_type constraint valid_level_type
check (level_type = ANY check (level_type = ANY