Compare commits

..

No commits in common. "65e7c496b767324ca058ac1dd7311d2dafada8c0" and "0988805652710d3647960645839dfb9be826b49d" have entirely different histories.

7 changed files with 72 additions and 515 deletions

View File

@ -62,15 +62,6 @@ export class KeyboardInput {
document.onkeydown = (ev) => {
switch (ev.key) {
case 'i':
// Open Babylon Inspector
import("@babylonjs/inspector").then((inspector) => {
inspector.Inspector.Show(this._scene, {
overlay: true,
showExplorer: true,
});
});
break;
case '1':
this._onCameraChangeObservable.notifyObservers(1);
break;

View File

@ -256,6 +256,7 @@ export class Main {
this._audioEngine = await CreateAudioEngineAsync();
this.setupInspector();
window.setTimeout(()=>{
if (!this._started) {
this._started = true;
@ -285,6 +286,20 @@ export class Main {
DefaultScene.MainScene.collisionsEnabled = true;
}
private setupInspector() {
setLoadingMessage("Initializing Inspector...");
window.addEventListener("keydown", (ev) => {
if (ev.key == 'i') {
import ("@babylonjs/inspector").then((inspector) => {
inspector.Inspector.Show(DefaultScene.MainScene, {
overlay: true,
showExplorer: true
});
});
}
});
}
}
// Setup router

View File

@ -1,14 +1,12 @@
import {AdvancedDynamicTexture, Control, StackPanel, TextBlock, Rectangle, Container} from "@babylonjs/gui";
import {AdvancedDynamicTexture, Control, StackPanel, TextBlock} from "@babylonjs/gui";
import {DefaultScene} from "./defaultScene";
import {
Mesh,
AbstractMesh, Mesh,
MeshBuilder,
Observable,
Observable, StandardMaterial,
Vector3,
} from "@babylonjs/core";
import debugLog from './debug';
import { ShipStatus } from './shipStatus';
export type ScoreEvent = {
score: number,
message: string,
@ -23,32 +21,8 @@ export class Scoreboard {
private _active = false;
private _done = false;
public readonly onScoreObservable: Observable<ScoreEvent> = new Observable<ScoreEvent>();
// Gauge bar fill rectangles
private _fuelBar: Rectangle | null = null;
private _hullBar: Rectangle | null = null;
private _ammoBar: Rectangle | null = null;
// Ship status manager
private _shipStatus: ShipStatus;
constructor() {
this._shipStatus = new ShipStatus();
// Subscribe to status changes to automatically update gauges
this._shipStatus.onStatusChanged.add((event) => {
switch (event.statusType) {
case 'fuel':
this.updateFuelBar(event.newValue);
break;
case 'hull':
this.updateHullBar(event.newValue);
break;
case 'ammo':
this.updateAmmoBar(event.newValue);
break;
}
});
}
public get done() {
return this._done;
@ -56,60 +30,26 @@ export class Scoreboard {
public set done(value: boolean) {
this._done = value;
}
/**
* Get the ship status manager
*/
public get shipStatus(): ShipStatus {
return this._shipStatus;
}
public setRemainingCount(count: number) {
this._remaining = count;
}
public initialize(): void {
public initialize(baseMesh: Mesh) {
const scene = DefaultScene.MainScene;
const parent = scene.getNodeById('ship');
debugLog('Scoreboard parent:', parent);
debugLog('Initializing scoreboard');
let scoreboard: Mesh | null = null;
let scoreboard = null;
// Retrieve and setup screen mesh from the loaded GLB
const screen = scene.getMaterialById("Screen")?.getBindedMeshes()[0] as Mesh;
if (baseMesh) {
scoreboard = baseMesh;
if (screen) {
// Setup screen mesh: adjust pivot point and rotation
const oldParent = screen.parent;
screen.setParent(null);
screen.setPivotPoint(screen.getBoundingInfo().boundingSphere.center);
screen.setParent(oldParent);
screen.rotation.y = Math.PI;
scoreboard = screen;
scoreboard.material.dispose();
}
//scoreboard.material = new StandardMaterial("scoreboard", scene);
// Retrieve and setup gauges mesh from the loaded GLB
const gauges = scene.getMaterialById("Gauges")?.getBindedMeshes()[0] as Mesh;
if (gauges) {
// Setup gauges mesh: adjust pivot point and rotation
const oldParent = gauges.parent;
gauges.setParent(null);
gauges.setPivotPoint(gauges.getBoundingInfo().boundingSphere.center);
gauges.setParent(oldParent);
//gauges.rotation.z = Math.PI;
// Create gauges display
this.createGaugesDisplay(gauges);
}
// Fallback: create a plane if screen mesh not found
if (!scoreboard) {
console.error('Screen mesh not found, creating fallback plane');
} else {
scoreboard = MeshBuilder.CreatePlane("scoreboard", {width: 1, height: 1}, scene);
scoreboard.parent = parent;
scoreboard.parent =parent;
scoreboard.position.y = 1.05;
scoreboard.position.z = 2.1;
@ -126,7 +66,7 @@ export class Scoreboard {
const advancedTexture = AdvancedDynamicTexture.CreateForMesh(scoreboard, 512, 512);
advancedTexture.background = "black";
advancedTexture.background = "green";
advancedTexture.hasAlpha = false;
const scoreText = this.createText();
@ -182,171 +122,4 @@ export class Scoreboard {
private calculateScore() {
return Math.floor(this._score);
}
/**
* Create the gauges display with 3 bar gauges (Fuel, Hull, Ammo)
*/
private createGaugesDisplay(gaugesMesh: Mesh): void {
// Store reference to old material to dispose after new one is created
const oldMaterial = gaugesMesh.material;
// Create AdvancedDynamicTexture for the gauges mesh
// This creates a new StandardMaterial and assigns it to the mesh
const gaugesTexture = AdvancedDynamicTexture.CreateForMesh(gaugesMesh, 512, 512);
gaugesTexture.coordinatesIndex = 2;
gaugesTexture.background = "#444444";
gaugesTexture.hasAlpha = false;
// Now dispose the old material after the new one is assigned
if (oldMaterial) {
oldMaterial.dispose(true, true);
}
debugLog('Gauges texture created, material:', gaugesMesh.material?.name);
// Create a vertical stack panel for the gauges
const panel = new StackPanel('GaugesPanel');
panel.rotation = Math.PI;
panel.isVertical = true;
panel.width = "100%";
panel.height = "100%";
// Create the three gauges
this._fuelBar = this.createGaugeBar("FUEL", "#00FF00", panel);
this._hullBar = this.createGaugeBar("HULL", "#00FF00", panel);
this._ammoBar = this.createGaugeBar("AMMO", "#00FF00", panel);
gaugesTexture.addControl(panel);
let i = 0;
// Force the texture to update
//gaugesTexture.markAsDirty();
// Set initial values to full (for testing visibility)
this._shipStatus.setFuel(1);
this._shipStatus.setHull(1);
this._shipStatus.setAmmo(1);
debugLog('Gauges display created with initial test values');
}
/**
* Create a single gauge bar with label
*/
private createGaugeBar(label: string, color: string, parent: Container): Rectangle {
// Container for this gauge (label + bar)
const gaugeContainer = new StackPanel();
gaugeContainer.isVertical = true;
gaugeContainer.height = "140px";
gaugeContainer.width = "100%";
gaugeContainer.paddingBottom = "15px";
// Label text
const labelText = new TextBlock();
labelText.text = label;
labelText.color = "#FFFFFF";
labelText.fontSize = "60px";
labelText.height = "70px";
labelText.textHorizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT;
gaugeContainer.addControl(labelText);
// Bar background (border and empty space)
const barBackground = new Rectangle();
barBackground.height = "50px";
barBackground.width = "100%";
barBackground.thickness = 3;
barBackground.color = "#FFFFFF";
barBackground.background = "#333333";
barBackground.cornerRadius = 5;
// Bar fill (the actual gauge)
const barFill = new Rectangle();
barFill.height = "100%";
barFill.width = "100%"; // Will be updated dynamically
barFill.thickness = 0;
barFill.background = color;
barFill.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT;
barFill.cornerRadius = 3;
barBackground.addControl(barFill);
gaugeContainer.addControl(barBackground);
parent.addControl(gaugeContainer);
return barFill;
}
/**
* Get bar color based on value with smooth gradient
* Green (1.0) -> Yellow (0.5) -> Red (0.0)
*/
private getBarColor(value: number): string {
// Clamp value between 0 and 1
value = Math.max(0, Math.min(1, value));
let red: number, green: number;
if (value >= 0.5) {
// Interpolate from yellow (0.5) to green (1.0)
// At 0.5: RGB(255, 255, 0) yellow
// At 1.0: RGB(0, 255, 0) green
const t = (value - 0.5) * 2; // 0 to 1 range
red = Math.round(255 * (1 - t));
green = 255;
} else {
// Interpolate from red (0.0) to yellow (0.5)
// At 0.0: RGB(255, 0, 0) red
// At 0.5: RGB(255, 255, 0) yellow
const t = value * 2; // 0 to 1 range
red = 255;
green = Math.round(255 * t);
}
// Convert to hex
const redHex = red.toString(16).padStart(2, '0');
const greenHex = green.toString(16).padStart(2, '0');
return `#${redHex}${greenHex}00`;
}
/**
* Internal method to update fuel gauge bar
*/
private updateFuelBar(value: number): void {
if (this._fuelBar) {
this._fuelBar.width = `${value * 100}%`;
this._fuelBar.background = this.getBarColor(value);
}
}
/**
* Internal method to update hull gauge bar
*/
private updateHullBar(value: number): void {
if (this._hullBar) {
this._hullBar.width = `${value * 100}%`;
this._hullBar.background = this.getBarColor(value);
}
}
/**
* Internal method to update ammo gauge bar
*/
private updateAmmoBar(value: number): void {
if (this._ammoBar) {
this._ammoBar.width = `${value * 100}%`;
this._ammoBar.background = this.getBarColor(value);
}
}
/**
* Dispose of scoreboard resources
*/
public dispose(): void {
if (this._shipStatus) {
this._shipStatus.dispose();
}
this.onScoreObservable.clear();
}
}

View File

@ -145,7 +145,6 @@ export class Ship {
// Initialize physics controller
this._physics = new ShipPhysics();
this._physics.setShipStatus(this._scoreboard.shipStatus);
// Setup physics update loop (every 10 frames)
DefaultScene.MainScene.onAfterRenderObservable.add(() => {
@ -175,8 +174,21 @@ export class Ship {
centerGap: 0.5,
});
// Initialize scoreboard (it will retrieve and setup its own screen mesh)
this._scoreboard.initialize();
// Setup scoreboard on screen
console.log(data.meshes.get("Screen"));
const screen = DefaultScene.MainScene
.getMaterialById("Screen")
.getBindedMeshes()[0] as AbstractMesh;
console.log(screen);
const old = screen.parent;
screen.setParent(null);
screen.setPivotPoint(screen.getBoundingInfo().boundingSphere.center);
screen.setParent(old);
screen.rotation.y = Math.PI;
console.log(screen.rotation);
console.log(screen.scaling);
this._scoreboard.initialize(screen as any);
}
/**

View File

@ -1,6 +1,5 @@
import { PhysicsBody, TransformNode, Vector2, Vector3 } from "@babylonjs/core";
import { GameConfig } from "./gameConfig";
import { ShipStatus } from "./shipStatus";
export interface InputState {
leftStick: Vector2;
@ -17,14 +16,6 @@ export interface ForceApplicationResult {
* Reads physics parameters from GameConfig for runtime tuning
*/
export class ShipPhysics {
private _shipStatus: ShipStatus | null = null;
/**
* Set the ship status instance for fuel consumption tracking
*/
public setShipStatus(shipStatus: ShipStatus): void {
this._shipStatus = shipStatus;
}
/**
* Apply forces to the ship based on input state
* @param inputState - Current input state (stick positions)
@ -58,31 +49,24 @@ export class ShipPhysics {
if (Math.abs(leftStick.y) > 0.1) {
linearMagnitude = Math.abs(leftStick.y);
// Check if we have fuel before applying force
if (this._shipStatus && this._shipStatus.fuel > 0) {
// Only apply force if we haven't reached max velocity
if (currentSpeed < config.maxLinearVelocity) {
// Get local direction (Z-axis for forward/backward thrust)
const localDirection = new Vector3(0, 0, -leftStick.y);
// Transform to world space
const worldDirection = Vector3.TransformNormal(
localDirection,
transformNode.getWorldMatrix()
);
const force = worldDirection.scale(config.linearForceMultiplier);
// Only apply force if we haven't reached max velocity
if (currentSpeed < config.maxLinearVelocity) {
// Get local direction (Z-axis for forward/backward thrust)
const localDirection = new Vector3(0, 0, -leftStick.y);
// Transform to world space
const worldDirection = Vector3.TransformNormal(
localDirection,
transformNode.getWorldMatrix()
);
const force = worldDirection.scale(config.linearForceMultiplier);
// Calculate thrust point: center of mass + offset (0, 1, 0) in world space
const thrustPoint = Vector3.TransformCoordinates(
physicsBody.getMassProperties().centerOfMass.add(new Vector3(0, 1, 0)),
transformNode.getWorldMatrix()
);
// Calculate thrust point: center of mass + offset (0, 1, 0) in world space
const thrustPoint = Vector3.TransformCoordinates(
physicsBody.getMassProperties().centerOfMass.add(new Vector3(0, 1, 0)),
transformNode.getWorldMatrix()
);
physicsBody.applyForce(force, thrustPoint);
// Consume fuel: normalized magnitude (0-1) * 0.01 = max consumption of 0.01 per frame
const fuelConsumption = linearMagnitude * 0.005;
this._shipStatus.consumeFuel(fuelConsumption);
}
physicsBody.applyForce(force, thrustPoint);
}
}
@ -94,32 +78,24 @@ export class ShipPhysics {
// Apply angular forces if any stick has significant rotation input
if (angularMagnitude > 0.1) {
// Check if we have fuel before applying torque
if (this._shipStatus && this._shipStatus.fuel > 0) {
const currentAngularSpeed = currentAngularVelocity.length();
const currentAngularSpeed = currentAngularVelocity.length();
// Only apply torque if we haven't reached max angular velocity
if (currentAngularSpeed < config.maxAngularVelocity) {
const yaw = -leftStick.x;
const pitch = rightStick.y;
const roll = rightStick.x;
// Only apply torque if we haven't reached max angular velocity
if (currentAngularSpeed < config.maxAngularVelocity) {
const yaw = -leftStick.x;
const pitch = rightStick.y;
const roll = rightStick.x;
// Create torque in local space, then transform to world space
const localTorque = new Vector3(pitch, yaw, roll).scale(
config.angularForceMultiplier
);
const worldTorque = Vector3.TransformNormal(
localTorque,
transformNode.getWorldMatrix()
);
// Create torque in local space, then transform to world space
const localTorque = new Vector3(pitch, yaw, roll).scale(
config.angularForceMultiplier
);
const worldTorque = Vector3.TransformNormal(
localTorque,
transformNode.getWorldMatrix()
);
physicsBody.applyAngularImpulse(worldTorque);
// Consume fuel: normalized magnitude (0-3 max) / 3 * 0.01 = max consumption of 0.01 per frame
const normalizedAngularMagnitude = Math.min(angularMagnitude / 3.0, 1.0);
const fuelConsumption = normalizedAngularMagnitude * 0.005;
this._shipStatus.consumeFuel(fuelConsumption);
}
physicsBody.applyAngularImpulse(worldTorque);
}
}

View File

@ -1,210 +0,0 @@
import { Observable } from "@babylonjs/core";
/**
* Event data for ship status changes
*/
export interface ShipStatusChangeEvent {
statusType: "fuel" | "hull" | "ammo";
oldValue: number;
newValue: number;
delta: number;
}
/**
* Ship status values container
*/
export interface ShipStatusValues {
fuel: number;
hull: number;
ammo: number;
}
/**
* Manages ship status values (fuel, hull integrity, ammo)
* Provides observable events for changes and automatic clamping to 0-1 range
*/
export class ShipStatus {
private _fuel: number = 1.0;
private _hull: number = 1.0;
private _ammo: number = 1.0;
// Maximum values for each resource
private _maxFuel: number = 1.0;
private _maxHull: number = 1.0;
private _maxAmmo: number = 1.0;
// Observable for status changes
public readonly onStatusChanged: Observable<ShipStatusChangeEvent> =
new Observable<ShipStatusChangeEvent>();
/**
* Get current fuel level (0-1)
*/
public get fuel(): number {
return this._fuel;
}
/**
* Get current hull integrity (0-1)
*/
public get hull(): number {
return this._hull;
}
/**
* Get current ammo level (0-1)
*/
public get ammo(): number {
return this._ammo;
}
/**
* Get all status values
*/
public getValues(): ShipStatusValues {
return {
fuel: this._fuel,
hull: this._hull,
ammo: this._ammo,
};
}
/**
* Set fuel level directly (clamped to 0-1)
*/
public setFuel(value: number): void {
const oldValue = this._fuel;
this._fuel = Math.max(0, Math.min(this._maxFuel, value));
if (oldValue !== this._fuel) {
this.onStatusChanged.notifyObservers({
statusType: "fuel",
oldValue,
newValue: this._fuel,
delta: this._fuel - oldValue,
});
}
}
/**
* Set hull integrity directly (clamped to 0-1)
*/
public setHull(value: number): void {
const oldValue = this._hull;
this._hull = Math.max(0, Math.min(this._maxHull, value));
if (oldValue !== this._hull) {
this.onStatusChanged.notifyObservers({
statusType: "hull",
oldValue,
newValue: this._hull,
delta: this._hull - oldValue,
});
}
}
/**
* Set ammo level directly (clamped to 0-1)
*/
public setAmmo(value: number): void {
const oldValue = this._ammo;
this._ammo = Math.max(0, Math.min(this._maxAmmo, value));
if (oldValue !== this._ammo) {
this.onStatusChanged.notifyObservers({
statusType: "ammo",
oldValue,
newValue: this._ammo,
delta: this._ammo - oldValue,
});
}
}
/**
* Increment fuel by delta amount
*/
public addFuel(delta: number): void {
this.setFuel(this._fuel + delta);
}
/**
* Decrement fuel by delta amount
*/
public consumeFuel(delta: number): void {
this.setFuel(this._fuel - delta);
}
/**
* Damage hull by delta amount
*/
public damageHull(delta: number): void {
this.setHull(this._hull - delta);
}
/**
* Repair hull by delta amount
*/
public repairHull(delta: number): void {
this.setHull(this._hull + delta);
}
/**
* Increment ammo by delta amount
*/
public addAmmo(delta: number): void {
this.setAmmo(this._ammo + delta);
}
/**
* Decrement ammo by delta amount (fire weapon)
*/
public consumeAmmo(delta: number): void {
this.setAmmo(this._ammo - delta);
}
/**
* Check if fuel is depleted
*/
public isFuelEmpty(): boolean {
return this._fuel <= 0;
}
/**
* Check if hull is destroyed
*/
public isDestroyed(): boolean {
return this._hull <= 0;
}
/**
* Check if ammo is depleted
*/
public isAmmoEmpty(): boolean {
return this._ammo <= 0;
}
/**
* Reset all values to full
*/
public reset(): void {
this.setFuel(this._maxFuel);
this.setHull(this._maxHull);
this.setAmmo(this._maxAmmo);
}
/**
* Set maximum values for resources
*/
public setMaxValues(fuel?: number, hull?: number, ammo?: number): void {
if (fuel !== undefined) this._maxFuel = fuel;
if (hull !== undefined) this._maxHull = hull;
if (ammo !== undefined) this._maxAmmo = ammo;
}
/**
* Dispose observables
*/
public dispose(): void {
this.onStatusChanged.clear();
}
}