space-game/src/statusScreen.ts
Michael Mainguy 406cebcd96
Some checks failed
Build / build (push) Failing after 20s
Add status screen with game statistics display
Implemented a toggleable status screen that displays game statistics, activated by pressing the X button on the left VR controller.

**New File: src/gameStats.ts**
- GameStats class to track game statistics
- Tracks: game time, asteroids destroyed, hull damage taken, shots fired, shots hit, fuel consumed
- Methods: startTimer(), recordAsteroidDestroyed(), recordHullDamage(), recordShotFired(), recordShotHit(), recordFuelConsumed()
- Calculated stats: getGameTime(), getFormattedGameTime() (MM:SS), getAccuracy() (percentage)
- getStats() returns formatted statistics object
- reset() method to reset all statistics

**New File: src/statusScreen.ts**
- StatusScreen class for visual display of game statistics
- Creates a 1.5x1.0 meter plane mesh with AdvancedDynamicTexture (1024x768)
- Dark blue background (#1a1a2e) with green title (#00ff88)
- Displays 6 statistics:
  - Game Time (MM:SS format)
  - Asteroids Destroyed (count)
  - Hull Damage Taken (percentage)
  - Shots Fired (count)
  - Accuracy (percentage)
  - Fuel Consumed (percentage)
- toggle() method to show/hide screen
- Positions 2 meters in front of camera when shown
- Automatically faces camera with proper orientation
- updateStatistics() refreshes displayed values from GameStats

**Modified: src/controllerInput.ts**
- Added _onStatusScreenToggleObservable for X button events
- Added onStatusScreenToggleObservable getter
- Added X button handler in handleControllerEvent()
- Checks for "x-button" on left controller only
- Fires observable on button press (not release)

**Modified: src/ship.ts**
- Added StatusScreen and GameStats imports
- Added _statusScreen and _gameStats properties
- Initialize GameStats in constructor
- Initialize StatusScreen with camera reference after camera creation
- Wired up controller observable to toggle status screen
- Added statusScreen cleanup in dispose() method

Features:
- Toggle display with X button on left controller
- Floats 2 meters in front of user's face
- Always faces the camera
- Clean, readable statistics layout
- Currently displays placeholder data (statistics tracking integration pending)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-07 15:46:58 -06:00

233 lines
6.5 KiB
TypeScript

import {
AdvancedDynamicTexture,
Control,
Rectangle,
StackPanel,
TextBlock
} from "@babylonjs/gui";
import {
Mesh,
MeshBuilder,
Scene,
StandardMaterial,
TransformNode,
Vector3
} from "@babylonjs/core";
import { GameStats } from "./gameStats";
/**
* Status screen that displays game statistics
* Floats in front of the user and can be toggled on/off
*/
export class StatusScreen {
private _scene: Scene;
private _gameStats: GameStats;
private _screenMesh: Mesh | null = null;
private _texture: AdvancedDynamicTexture | null = null;
private _isVisible: boolean = false;
private _camera: TransformNode | null = null;
// Text blocks for statistics
private _gameTimeText: TextBlock;
private _asteroidsText: TextBlock;
private _hullDamageText: TextBlock;
private _shotsFiredText: TextBlock;
private _accuracyText: TextBlock;
private _fuelConsumedText: TextBlock;
constructor(scene: Scene, gameStats: GameStats) {
this._scene = scene;
this._gameStats = gameStats;
}
/**
* Initialize the status screen mesh and UI
*/
public initialize(camera: TransformNode): void {
this._camera = camera;
// Create a plane mesh for the status screen
this._screenMesh = MeshBuilder.CreatePlane(
"statusScreen",
{ width: 1.5, height: 1.0 },
this._scene
);
// Create material
const material = new StandardMaterial("statusScreenMaterial", this._scene);
this._screenMesh.material = material;
// Create AdvancedDynamicTexture
this._texture = AdvancedDynamicTexture.CreateForMesh(
this._screenMesh,
1024,
768
);
this._texture.background = "#1a1a2e";
// Create main container
const mainPanel = new StackPanel("mainPanel");
mainPanel.width = "100%";
mainPanel.height = "100%";
mainPanel.isVertical = true;
mainPanel.paddingTop = "40px";
mainPanel.paddingBottom = "40px";
mainPanel.paddingLeft = "60px";
mainPanel.paddingRight = "60px";
// Title
const title = this.createTitleText("GAME STATISTICS");
mainPanel.addControl(title);
// Add spacing
const spacer1 = this.createSpacer(40);
mainPanel.addControl(spacer1);
// Create statistics display
this._gameTimeText = this.createStatText("Game Time: 00:00");
mainPanel.addControl(this._gameTimeText);
this._asteroidsText = this.createStatText("Asteroids Destroyed: 0");
mainPanel.addControl(this._asteroidsText);
this._hullDamageText = this.createStatText("Hull Damage Taken: 0%");
mainPanel.addControl(this._hullDamageText);
this._shotsFiredText = this.createStatText("Shots Fired: 0");
mainPanel.addControl(this._shotsFiredText);
this._accuracyText = this.createStatText("Accuracy: 0%");
mainPanel.addControl(this._accuracyText);
this._fuelConsumedText = this.createStatText("Fuel Consumed: 0%");
mainPanel.addControl(this._fuelConsumedText);
this._texture.addControl(mainPanel);
// Initially hide the screen
this._screenMesh.setEnabled(false);
this._isVisible = false;
}
/**
* Create title text block
*/
private createTitleText(text: string): TextBlock {
const textBlock = new TextBlock();
textBlock.text = text;
textBlock.color = "#00ff88";
textBlock.fontSize = "80px";
textBlock.height = "100px";
textBlock.fontWeight = "bold";
textBlock.textHorizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER;
return textBlock;
}
/**
* Create stat text block
*/
private createStatText(text: string): TextBlock {
const textBlock = new TextBlock();
textBlock.text = text;
textBlock.color = "#ffffff";
textBlock.fontSize = "50px";
textBlock.height = "70px";
textBlock.textHorizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT;
textBlock.paddingTop = "10px";
textBlock.paddingBottom = "10px";
return textBlock;
}
/**
* Create spacer for layout
*/
private createSpacer(height: number): Rectangle {
const spacer = new Rectangle();
spacer.height = `${height}px`;
spacer.thickness = 0;
return spacer;
}
/**
* Toggle visibility of status screen
*/
public toggle(): void {
if (this._isVisible) {
this.hide();
} else {
this.show();
}
}
/**
* Show the status screen
*/
public show(): void {
if (!this._screenMesh || !this._camera) {
return;
}
// Update statistics before showing
this.updateStatistics();
// Position screen 2 meters in front of camera
const cameraForward = this._camera.forward;
const offset = cameraForward.scale(2.0);
this._screenMesh.position = this._camera.position.add(offset);
// Make screen face the camera
this._screenMesh.lookAt(this._camera.position);
// Rotate 180 degrees to face the right way
this._screenMesh.rotate(Vector3.Up(), Math.PI);
this._screenMesh.setEnabled(true);
this._isVisible = true;
}
/**
* Hide the status screen
*/
public hide(): void {
if (!this._screenMesh) {
return;
}
this._screenMesh.setEnabled(false);
this._isVisible = false;
}
/**
* Update displayed statistics
*/
public updateStatistics(): void {
const stats = this._gameStats.getStats();
this._gameTimeText.text = `Game Time: ${stats.gameTime}`;
this._asteroidsText.text = `Asteroids Destroyed: ${stats.asteroidsDestroyed}`;
this._hullDamageText.text = `Hull Damage Taken: ${stats.hullDamageTaken}%`;
this._shotsFiredText.text = `Shots Fired: ${stats.shotsFired}`;
this._accuracyText.text = `Accuracy: ${stats.accuracy}%`;
this._fuelConsumedText.text = `Fuel Consumed: ${stats.fuelConsumed}%`;
}
/**
* Check if status screen is visible
*/
public get isVisible(): boolean {
return this._isVisible;
}
/**
* Dispose of status screen resources
*/
public dispose(): void {
if (this._texture) {
this._texture.dispose();
}
if (this._screenMesh) {
this._screenMesh.dispose();
}
}
}