Compare commits
2 Commits
0988805652
...
65e7c496b7
| Author | SHA1 | Date | |
|---|---|---|---|
| 65e7c496b7 | |||
| 2e1c8ad725 |
Binary file not shown.
@ -62,6 +62,15 @@ export class KeyboardInput {
|
|||||||
|
|
||||||
document.onkeydown = (ev) => {
|
document.onkeydown = (ev) => {
|
||||||
switch (ev.key) {
|
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':
|
case '1':
|
||||||
this._onCameraChangeObservable.notifyObservers(1);
|
this._onCameraChangeObservable.notifyObservers(1);
|
||||||
break;
|
break;
|
||||||
|
|||||||
15
src/main.ts
15
src/main.ts
@ -256,7 +256,6 @@ export class Main {
|
|||||||
this._audioEngine = await CreateAudioEngineAsync();
|
this._audioEngine = await CreateAudioEngineAsync();
|
||||||
|
|
||||||
|
|
||||||
this.setupInspector();
|
|
||||||
window.setTimeout(()=>{
|
window.setTimeout(()=>{
|
||||||
if (!this._started) {
|
if (!this._started) {
|
||||||
this._started = true;
|
this._started = true;
|
||||||
@ -286,20 +285,6 @@ export class Main {
|
|||||||
|
|
||||||
DefaultScene.MainScene.collisionsEnabled = true;
|
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
|
// Setup router
|
||||||
|
|||||||
@ -1,12 +1,14 @@
|
|||||||
import {AdvancedDynamicTexture, Control, StackPanel, TextBlock} from "@babylonjs/gui";
|
import {AdvancedDynamicTexture, Control, StackPanel, TextBlock, Rectangle, Container} from "@babylonjs/gui";
|
||||||
import {DefaultScene} from "./defaultScene";
|
import {DefaultScene} from "./defaultScene";
|
||||||
import {
|
import {
|
||||||
AbstractMesh, Mesh,
|
Mesh,
|
||||||
MeshBuilder,
|
MeshBuilder,
|
||||||
Observable, StandardMaterial,
|
Observable,
|
||||||
Vector3,
|
Vector3,
|
||||||
} from "@babylonjs/core";
|
} from "@babylonjs/core";
|
||||||
import debugLog from './debug';
|
import debugLog from './debug';
|
||||||
|
import { ShipStatus } from './shipStatus';
|
||||||
|
|
||||||
export type ScoreEvent = {
|
export type ScoreEvent = {
|
||||||
score: number,
|
score: number,
|
||||||
message: string,
|
message: string,
|
||||||
@ -21,8 +23,32 @@ export class Scoreboard {
|
|||||||
private _active = false;
|
private _active = false;
|
||||||
private _done = false;
|
private _done = false;
|
||||||
public readonly onScoreObservable: Observable<ScoreEvent> = new Observable<ScoreEvent>();
|
public readonly onScoreObservable: Observable<ScoreEvent> = new Observable<ScoreEvent>();
|
||||||
constructor() {
|
|
||||||
|
|
||||||
|
// 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() {
|
public get done() {
|
||||||
return this._done;
|
return this._done;
|
||||||
@ -30,26 +56,60 @@ export class Scoreboard {
|
|||||||
public set done(value: boolean) {
|
public set done(value: boolean) {
|
||||||
this._done = value;
|
this._done = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the ship status manager
|
||||||
|
*/
|
||||||
|
public get shipStatus(): ShipStatus {
|
||||||
|
return this._shipStatus;
|
||||||
|
}
|
||||||
|
|
||||||
public setRemainingCount(count: number) {
|
public setRemainingCount(count: number) {
|
||||||
this._remaining = count;
|
this._remaining = count;
|
||||||
}
|
}
|
||||||
public initialize(baseMesh: Mesh) {
|
public initialize(): void {
|
||||||
const scene = DefaultScene.MainScene;
|
const scene = DefaultScene.MainScene;
|
||||||
|
|
||||||
const parent = scene.getNodeById('ship');
|
const parent = scene.getNodeById('ship');
|
||||||
debugLog('Scoreboard parent:', parent);
|
debugLog('Scoreboard parent:', parent);
|
||||||
debugLog('Initializing scoreboard');
|
debugLog('Initializing scoreboard');
|
||||||
let scoreboard = null;
|
let scoreboard: Mesh | null = null;
|
||||||
|
|
||||||
if (baseMesh) {
|
// Retrieve and setup screen mesh from the loaded GLB
|
||||||
scoreboard = baseMesh;
|
const screen = scene.getMaterialById("Screen")?.getBindedMeshes()[0] as Mesh;
|
||||||
|
|
||||||
|
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.dispose();
|
||||||
//scoreboard.material = new StandardMaterial("scoreboard", scene);
|
}
|
||||||
|
|
||||||
} else {
|
// 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');
|
||||||
scoreboard = MeshBuilder.CreatePlane("scoreboard", {width: 1, height: 1}, scene);
|
scoreboard = MeshBuilder.CreatePlane("scoreboard", {width: 1, height: 1}, scene);
|
||||||
scoreboard.parent =parent;
|
scoreboard.parent = parent;
|
||||||
|
|
||||||
scoreboard.position.y = 1.05;
|
scoreboard.position.y = 1.05;
|
||||||
scoreboard.position.z = 2.1;
|
scoreboard.position.z = 2.1;
|
||||||
@ -66,7 +126,7 @@ export class Scoreboard {
|
|||||||
|
|
||||||
|
|
||||||
const advancedTexture = AdvancedDynamicTexture.CreateForMesh(scoreboard, 512, 512);
|
const advancedTexture = AdvancedDynamicTexture.CreateForMesh(scoreboard, 512, 512);
|
||||||
advancedTexture.background = "green";
|
advancedTexture.background = "black";
|
||||||
advancedTexture.hasAlpha = false;
|
advancedTexture.hasAlpha = false;
|
||||||
const scoreText = this.createText();
|
const scoreText = this.createText();
|
||||||
|
|
||||||
@ -122,4 +182,171 @@ export class Scoreboard {
|
|||||||
private calculateScore() {
|
private calculateScore() {
|
||||||
return Math.floor(this._score);
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
18
src/ship.ts
18
src/ship.ts
@ -145,6 +145,7 @@ export class Ship {
|
|||||||
|
|
||||||
// Initialize physics controller
|
// Initialize physics controller
|
||||||
this._physics = new ShipPhysics();
|
this._physics = new ShipPhysics();
|
||||||
|
this._physics.setShipStatus(this._scoreboard.shipStatus);
|
||||||
|
|
||||||
// Setup physics update loop (every 10 frames)
|
// Setup physics update loop (every 10 frames)
|
||||||
DefaultScene.MainScene.onAfterRenderObservable.add(() => {
|
DefaultScene.MainScene.onAfterRenderObservable.add(() => {
|
||||||
@ -174,21 +175,8 @@ export class Ship {
|
|||||||
centerGap: 0.5,
|
centerGap: 0.5,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Setup scoreboard on screen
|
// Initialize scoreboard (it will retrieve and setup its own screen mesh)
|
||||||
console.log(data.meshes.get("Screen"));
|
this._scoreboard.initialize();
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { PhysicsBody, TransformNode, Vector2, Vector3 } from "@babylonjs/core";
|
import { PhysicsBody, TransformNode, Vector2, Vector3 } from "@babylonjs/core";
|
||||||
import { GameConfig } from "./gameConfig";
|
import { GameConfig } from "./gameConfig";
|
||||||
|
import { ShipStatus } from "./shipStatus";
|
||||||
|
|
||||||
export interface InputState {
|
export interface InputState {
|
||||||
leftStick: Vector2;
|
leftStick: Vector2;
|
||||||
@ -16,6 +17,14 @@ export interface ForceApplicationResult {
|
|||||||
* Reads physics parameters from GameConfig for runtime tuning
|
* Reads physics parameters from GameConfig for runtime tuning
|
||||||
*/
|
*/
|
||||||
export class ShipPhysics {
|
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
|
* Apply forces to the ship based on input state
|
||||||
* @param inputState - Current input state (stick positions)
|
* @param inputState - Current input state (stick positions)
|
||||||
@ -49,24 +58,31 @@ export class ShipPhysics {
|
|||||||
if (Math.abs(leftStick.y) > 0.1) {
|
if (Math.abs(leftStick.y) > 0.1) {
|
||||||
linearMagnitude = Math.abs(leftStick.y);
|
linearMagnitude = Math.abs(leftStick.y);
|
||||||
|
|
||||||
// Only apply force if we haven't reached max velocity
|
// Check if we have fuel before applying force
|
||||||
if (currentSpeed < config.maxLinearVelocity) {
|
if (this._shipStatus && this._shipStatus.fuel > 0) {
|
||||||
// Get local direction (Z-axis for forward/backward thrust)
|
// Only apply force if we haven't reached max velocity
|
||||||
const localDirection = new Vector3(0, 0, -leftStick.y);
|
if (currentSpeed < config.maxLinearVelocity) {
|
||||||
// Transform to world space
|
// Get local direction (Z-axis for forward/backward thrust)
|
||||||
const worldDirection = Vector3.TransformNormal(
|
const localDirection = new Vector3(0, 0, -leftStick.y);
|
||||||
localDirection,
|
// Transform to world space
|
||||||
transformNode.getWorldMatrix()
|
const worldDirection = Vector3.TransformNormal(
|
||||||
);
|
localDirection,
|
||||||
const force = worldDirection.scale(config.linearForceMultiplier);
|
transformNode.getWorldMatrix()
|
||||||
|
);
|
||||||
|
const force = worldDirection.scale(config.linearForceMultiplier);
|
||||||
|
|
||||||
// Calculate thrust point: center of mass + offset (0, 1, 0) in world space
|
// Calculate thrust point: center of mass + offset (0, 1, 0) in world space
|
||||||
const thrustPoint = Vector3.TransformCoordinates(
|
const thrustPoint = Vector3.TransformCoordinates(
|
||||||
physicsBody.getMassProperties().centerOfMass.add(new Vector3(0, 1, 0)),
|
physicsBody.getMassProperties().centerOfMass.add(new Vector3(0, 1, 0)),
|
||||||
transformNode.getWorldMatrix()
|
transformNode.getWorldMatrix()
|
||||||
);
|
);
|
||||||
|
|
||||||
physicsBody.applyForce(force, thrustPoint);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,24 +94,32 @@ export class ShipPhysics {
|
|||||||
|
|
||||||
// Apply angular forces if any stick has significant rotation input
|
// Apply angular forces if any stick has significant rotation input
|
||||||
if (angularMagnitude > 0.1) {
|
if (angularMagnitude > 0.1) {
|
||||||
const currentAngularSpeed = currentAngularVelocity.length();
|
// Check if we have fuel before applying torque
|
||||||
|
if (this._shipStatus && this._shipStatus.fuel > 0) {
|
||||||
|
const currentAngularSpeed = currentAngularVelocity.length();
|
||||||
|
|
||||||
// Only apply torque if we haven't reached max angular velocity
|
// Only apply torque if we haven't reached max angular velocity
|
||||||
if (currentAngularSpeed < config.maxAngularVelocity) {
|
if (currentAngularSpeed < config.maxAngularVelocity) {
|
||||||
const yaw = -leftStick.x;
|
const yaw = -leftStick.x;
|
||||||
const pitch = rightStick.y;
|
const pitch = rightStick.y;
|
||||||
const roll = rightStick.x;
|
const roll = rightStick.x;
|
||||||
|
|
||||||
// Create torque in local space, then transform to world space
|
// Create torque in local space, then transform to world space
|
||||||
const localTorque = new Vector3(pitch, yaw, roll).scale(
|
const localTorque = new Vector3(pitch, yaw, roll).scale(
|
||||||
config.angularForceMultiplier
|
config.angularForceMultiplier
|
||||||
);
|
);
|
||||||
const worldTorque = Vector3.TransformNormal(
|
const worldTorque = Vector3.TransformNormal(
|
||||||
localTorque,
|
localTorque,
|
||||||
transformNode.getWorldMatrix()
|
transformNode.getWorldMatrix()
|
||||||
);
|
);
|
||||||
|
|
||||||
physicsBody.applyAngularImpulse(worldTorque);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
210
src/shipStatus.ts
Normal file
210
src/shipStatus.ts
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user