Implemented automatic resource replenishment when the ship is inside the base landing zone. **Modified: src/starBase.ts** - Added StarBaseResult interface with baseMesh and landingAggregate properties - Changed buildStarBase() return type from AbstractMesh to StarBaseResult - Now returns both the base mesh and landing aggregate for resupply system access - Landing aggregate already configured as trigger for collision detection **Modified: src/levelDeserializer.ts** - Added PhysicsAggregate import - Updated deserialize() return type to include landingAggregate field - Changed createStartBase() to return full StarBaseResult - Updated return statement to destructure baseResult into baseMesh and landingAggregate **Modified: src/level1.ts** - Added PhysicsAggregate import and _landingAggregate property - Stored landingAggregate from deserializer result - Called ship.setLandingZone() to configure resupply system **Modified: src/ship.ts** - Added resupply system properties: _landingAggregate, _resupplyTimer, _isInLandingZone - Added setLandingZone() method to configure landing zone for resupply - Added updateResupply() method called every physics update (6 times per second) - Distance-based detection: checks if ship is within 20 units of landing zone center - Resupply rate: 0.1 per second for all resources (fuel, hull, ammo) - Automatically replenishes until resources reach 1.0 maximum - Debug logging for enter/exit landing zone events Resupply System Mechanics: - Activates when ship is within landing zone (distance < 20 units) - Replenishes at 0.1 per second (~0.01666 per update at 6 updates/second) - Repairs all three resources simultaneously: fuel, hull, ammo - Stops automatically when each resource reaches maximum (1.0) - Integrated with existing ShipStatus observable system for automatic gauge updates 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
8605946fab
commit
827dd2d359
@ -3,6 +3,7 @@ import type {AudioEngineV2} from "@babylonjs/core";
|
|||||||
import {
|
import {
|
||||||
AbstractMesh,
|
AbstractMesh,
|
||||||
Observable,
|
Observable,
|
||||||
|
PhysicsAggregate,
|
||||||
Vector3
|
Vector3
|
||||||
} from "@babylonjs/core";
|
} from "@babylonjs/core";
|
||||||
import {Ship} from "./ship";
|
import {Ship} from "./ship";
|
||||||
@ -18,6 +19,7 @@ export class Level1 implements Level {
|
|||||||
private _onReadyObservable: Observable<Level> = new Observable<Level>();
|
private _onReadyObservable: Observable<Level> = new Observable<Level>();
|
||||||
private _initialized: boolean = false;
|
private _initialized: boolean = false;
|
||||||
private _startBase: AbstractMesh | null;
|
private _startBase: AbstractMesh | null;
|
||||||
|
private _landingAggregate: PhysicsAggregate | null;
|
||||||
private _endBase: AbstractMesh;
|
private _endBase: AbstractMesh;
|
||||||
private _levelConfig: LevelConfig;
|
private _levelConfig: LevelConfig;
|
||||||
private _audioEngine: AudioEngineV2;
|
private _audioEngine: AudioEngineV2;
|
||||||
@ -103,6 +105,13 @@ export class Level1 implements Level {
|
|||||||
const entities = await this._deserializer.deserialize(this._ship.scoreboard.onScoreObservable);
|
const entities = await this._deserializer.deserialize(this._ship.scoreboard.onScoreObservable);
|
||||||
|
|
||||||
this._startBase = entities.startBase;
|
this._startBase = entities.startBase;
|
||||||
|
this._landingAggregate = entities.landingAggregate;
|
||||||
|
|
||||||
|
// Setup resupply system if landing aggregate exists
|
||||||
|
if (this._landingAggregate) {
|
||||||
|
this._ship.setLandingZone(this._landingAggregate);
|
||||||
|
}
|
||||||
|
|
||||||
// sun and planets are already created by deserializer
|
// sun and planets are already created by deserializer
|
||||||
|
|
||||||
// Initialize scoreboard with total asteroid count
|
// Initialize scoreboard with total asteroid count
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import {
|
|||||||
MeshBuilder,
|
MeshBuilder,
|
||||||
Observable,
|
Observable,
|
||||||
PBRMaterial,
|
PBRMaterial,
|
||||||
|
PhysicsAggregate,
|
||||||
Texture,
|
Texture,
|
||||||
Vector3
|
Vector3
|
||||||
} from "@babylonjs/core";
|
} from "@babylonjs/core";
|
||||||
@ -43,6 +44,7 @@ export class LevelDeserializer {
|
|||||||
*/
|
*/
|
||||||
public async deserialize(scoreObservable: Observable<ScoreEvent>): Promise<{
|
public async deserialize(scoreObservable: Observable<ScoreEvent>): Promise<{
|
||||||
startBase: AbstractMesh | null;
|
startBase: AbstractMesh | null;
|
||||||
|
landingAggregate: PhysicsAggregate | null;
|
||||||
sun: AbstractMesh;
|
sun: AbstractMesh;
|
||||||
planets: AbstractMesh[];
|
planets: AbstractMesh[];
|
||||||
asteroids: AbstractMesh[];
|
asteroids: AbstractMesh[];
|
||||||
@ -50,7 +52,7 @@ export class LevelDeserializer {
|
|||||||
debugLog('Deserializing level:', this.config.difficulty);
|
debugLog('Deserializing level:', this.config.difficulty);
|
||||||
|
|
||||||
// Create entities
|
// Create entities
|
||||||
const startBase = await this.createStartBase();
|
const baseResult = await this.createStartBase();
|
||||||
const sun = this.createSun();
|
const sun = this.createSun();
|
||||||
const planets = this.createPlanets();
|
const planets = this.createPlanets();
|
||||||
const asteroids = await this.createAsteroids(scoreObservable);
|
const asteroids = await this.createAsteroids(scoreObservable);
|
||||||
@ -63,7 +65,8 @@ export class LevelDeserializer {
|
|||||||
light2.intensity = .5;
|
light2.intensity = .5;
|
||||||
*/
|
*/
|
||||||
return {
|
return {
|
||||||
startBase,
|
startBase: baseResult.baseMesh,
|
||||||
|
landingAggregate: baseResult.landingAggregate,
|
||||||
sun,
|
sun,
|
||||||
planets,
|
planets,
|
||||||
asteroids
|
asteroids
|
||||||
@ -73,8 +76,8 @@ export class LevelDeserializer {
|
|||||||
/**
|
/**
|
||||||
* Create the start base from config
|
* Create the start base from config
|
||||||
*/
|
*/
|
||||||
private async createStartBase(): Promise<AbstractMesh> {
|
private async createStartBase() {
|
||||||
return await StarBase.buildStarBase();
|
return await StarBase.buildStarBase();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
73
src/ship.ts
73
src/ship.ts
@ -42,6 +42,11 @@ export class Ship {
|
|||||||
// Frame counter for physics updates
|
// Frame counter for physics updates
|
||||||
private _frameCount: number = 0;
|
private _frameCount: number = 0;
|
||||||
|
|
||||||
|
// Resupply system
|
||||||
|
private _landingAggregate: PhysicsAggregate | null = null;
|
||||||
|
private _resupplyTimer: number = 0;
|
||||||
|
private _isInLandingZone: boolean = false;
|
||||||
|
|
||||||
constructor(audioEngine?: AudioEngineV2) {
|
constructor(audioEngine?: AudioEngineV2) {
|
||||||
this._audioEngine = audioEngine;
|
this._audioEngine = audioEngine;
|
||||||
}
|
}
|
||||||
@ -233,6 +238,58 @@ export class Ship {
|
|||||||
forceMagnitudes.angularMagnitude
|
forceMagnitudes.angularMagnitude
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle resupply when in landing zone
|
||||||
|
this.updateResupply();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update resupply system - replenishes resources at 0.1 per second when in landing zone
|
||||||
|
*/
|
||||||
|
private updateResupply(): void {
|
||||||
|
if (!this._landingAggregate || !this._ship?.physicsBody) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if ship is still in the landing zone by checking distance
|
||||||
|
// Since it's a trigger, we need to track position
|
||||||
|
const shipPos = this._ship.physicsBody.transformNode.position;
|
||||||
|
const landingPos = this._landingAggregate.transformNode.position;
|
||||||
|
const distance = Vector3.Distance(shipPos, landingPos);
|
||||||
|
|
||||||
|
// Assume landing zone radius is approximately 20 units (adjust as needed)
|
||||||
|
const wasInZone = this._isInLandingZone;
|
||||||
|
this._isInLandingZone = distance < 20;
|
||||||
|
|
||||||
|
if (this._isInLandingZone && !wasInZone) {
|
||||||
|
debugLog("Ship entered landing zone - resupply active");
|
||||||
|
} else if (!this._isInLandingZone && wasInZone) {
|
||||||
|
debugLog("Ship exited landing zone - resupply inactive");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resupply at 0.1 per second if in zone
|
||||||
|
if (this._isInLandingZone && this._scoreboard?.shipStatus) {
|
||||||
|
// Physics update runs every 10 frames at 60fps = 6 times per second
|
||||||
|
// 0.1 per second / 6 updates per second = 0.01666... per update
|
||||||
|
const resupplyRate = 0.1 / 6;
|
||||||
|
|
||||||
|
const status = this._scoreboard.shipStatus;
|
||||||
|
|
||||||
|
// Replenish fuel
|
||||||
|
if (status.fuel < 1.0) {
|
||||||
|
status.addFuel(resupplyRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Repair hull
|
||||||
|
if (status.hull < 1.0) {
|
||||||
|
status.repairHull(resupplyRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replenish ammo
|
||||||
|
if (status.ammo < 1.0) {
|
||||||
|
status.addAmmo(resupplyRate);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -258,6 +315,22 @@ export class Ship {
|
|||||||
return this._ship;
|
return this._ship;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the landing zone for resupply
|
||||||
|
*/
|
||||||
|
public setLandingZone(landingAggregate: PhysicsAggregate): void {
|
||||||
|
this._landingAggregate = landingAggregate;
|
||||||
|
|
||||||
|
// Listen for trigger events to detect when ship enters/exits landing zone
|
||||||
|
landingAggregate.body.getCollisionObservable().add((collisionEvent) => {
|
||||||
|
// Check if the collision is with our ship
|
||||||
|
if (collisionEvent.collider === this._ship.physicsBody) {
|
||||||
|
this._isInLandingZone = true;
|
||||||
|
debugLog("Ship entered landing zone - resupply active");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a VR controller to the input system
|
* Add a VR controller to the input system
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -11,13 +11,18 @@ import {GameConfig} from "./gameConfig";
|
|||||||
import debugLog from "./debug";
|
import debugLog from "./debug";
|
||||||
import loadAsset from "./utils/loadAsset";
|
import loadAsset from "./utils/loadAsset";
|
||||||
|
|
||||||
|
export interface StarBaseResult {
|
||||||
|
baseMesh: AbstractMesh;
|
||||||
|
landingAggregate: PhysicsAggregate | null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create and load the star base mesh
|
* Create and load the star base mesh
|
||||||
* @param position - Position for the star base
|
* @param position - Position for the star base
|
||||||
* @returns Promise resolving to the loaded star base mesh
|
* @returns Promise resolving to the loaded star base mesh and landing aggregate
|
||||||
*/
|
*/
|
||||||
export default class StarBase {
|
export default class StarBase {
|
||||||
public static async buildStarBase(): Promise<AbstractMesh> {
|
public static async buildStarBase(): Promise<StarBaseResult> {
|
||||||
const config = GameConfig.getInstance();
|
const config = GameConfig.getInstance();
|
||||||
const scene = DefaultScene.MainScene;
|
const scene = DefaultScene.MainScene;
|
||||||
const importMeshes = await loadAsset('base.glb');
|
const importMeshes = await loadAsset('base.glb');
|
||||||
@ -25,7 +30,7 @@ export default class StarBase {
|
|||||||
const baseMesh = importMeshes.meshes.get('Base');
|
const baseMesh = importMeshes.meshes.get('Base');
|
||||||
const landingMesh = importMeshes.meshes.get('BaseLandingZone');
|
const landingMesh = importMeshes.meshes.get('BaseLandingZone');
|
||||||
|
|
||||||
|
let landingAgg: PhysicsAggregate | null = null;
|
||||||
|
|
||||||
if (config.physicsEnabled) {
|
if (config.physicsEnabled) {
|
||||||
const agg2 = new PhysicsAggregate(baseMesh, PhysicsShapeType.MESH, {
|
const agg2 = new PhysicsAggregate(baseMesh, PhysicsShapeType.MESH, {
|
||||||
@ -37,7 +42,7 @@ export default class StarBase {
|
|||||||
debugLog('collidedBody', collidedBody);
|
debugLog('collidedBody', collidedBody);
|
||||||
})
|
})
|
||||||
|
|
||||||
const landingAgg = new PhysicsAggregate(landingMesh, PhysicsShapeType.MESH);
|
landingAgg = new PhysicsAggregate(landingMesh, PhysicsShapeType.MESH);
|
||||||
landingAgg.body.setMotionType(PhysicsMotionType.ANIMATED);
|
landingAgg.body.setMotionType(PhysicsMotionType.ANIMATED);
|
||||||
/*landingAgg.body.getCollisionObservable().add((collidedCollidedBody) => {
|
/*landingAgg.body.getCollisionObservable().add((collidedCollidedBody) => {
|
||||||
|
|
||||||
@ -50,7 +55,10 @@ export default class StarBase {
|
|||||||
landingAgg.body.setCollisionCallbackEnabled(true);
|
landingAgg.body.setCollisionCallbackEnabled(true);
|
||||||
}
|
}
|
||||||
//importMesh.rootNodes[0].dispose();
|
//importMesh.rootNodes[0].dispose();
|
||||||
return baseMesh;
|
return {
|
||||||
|
baseMesh,
|
||||||
|
landingAggregate: landingAgg
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function clearParent (meshes: Map<string, AbstractMesh>, position?: Vector3) {
|
function clearParent (meshes: Map<string, AbstractMesh>, position?: Vector3) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user