Add status screen pause functionality with VR controller picking
Some checks failed
Build / build (push) Failing after 24s
Some checks failed
Build / build (push) Failing after 24s
Implemented comprehensive status screen system with pause/resume and game-end states: - Added enable/disable functionality to controller and keyboard input systems - X button and inspector key always work, even when controls disabled - Created Resume/Replay/Exit VR buttons in status screen - Resume button appears on manual pause, Replay appears on game end - Implemented automatic status screen display on game end conditions: * Death: hull < 0.01 outside landing zone * Stranded: fuel < 0.01 and velocity < 1 outside landing zone * Victory: all asteroids destroyed inside landing zone - Fixed landing zone detection to use mesh intersection instead of distance - Implemented dynamic VR pointer selection using attach/detach pattern - Pointer selection only enabled when status screen is visible - Ship controls automatically disabled when status screen shows 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
d6b1744ce4
commit
31b498da7d
@ -49,6 +49,7 @@ export class ControllerInput {
|
||||
private _onCameraAdjustObservable: Observable<CameraAdjustment> =
|
||||
new Observable<CameraAdjustment>();
|
||||
private _onStatusScreenToggleObservable: Observable<void> = new Observable<void>();
|
||||
private _enabled: boolean = true;
|
||||
|
||||
constructor() {
|
||||
this._controllerObservable.add(this.handleControllerEvent.bind(this));
|
||||
@ -79,12 +80,32 @@ export class ControllerInput {
|
||||
* Get current input state (stick positions)
|
||||
*/
|
||||
public getInputState() {
|
||||
if (!this._enabled) {
|
||||
return {
|
||||
leftStick: Vector2.Zero(),
|
||||
rightStick: Vector2.Zero(),
|
||||
};
|
||||
}
|
||||
return {
|
||||
leftStick: this._leftStick.clone(),
|
||||
rightStick: this._rightStick.clone(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable controller input
|
||||
*/
|
||||
public setEnabled(enabled: boolean): void {
|
||||
this._enabled = enabled;
|
||||
if (!enabled) {
|
||||
// Reset stick values when disabled
|
||||
this._leftStick.x = 0;
|
||||
this._leftStick.y = 0;
|
||||
this._rightStick.x = 0;
|
||||
this._rightStick.y = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a VR controller to the input system
|
||||
*/
|
||||
@ -199,6 +220,16 @@ export class ControllerInput {
|
||||
* Handle controller events (thumbsticks and buttons)
|
||||
*/
|
||||
private handleControllerEvent(controllerEvent: ControllerEvent): void {
|
||||
// Don't process ship control inputs when disabled (but allow status screen toggle)
|
||||
if (!this._enabled && controllerEvent.type === "thumbstick") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._enabled && controllerEvent.type === "button" &&
|
||||
!(controllerEvent.component.id === "x-button" && controllerEvent.hand === "left")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (controllerEvent.type === "thumbstick") {
|
||||
if (controllerEvent.hand === "left") {
|
||||
this._leftStick.x = controllerEvent.axisData.x;
|
||||
@ -235,6 +266,7 @@ export class ControllerInput {
|
||||
}
|
||||
if (controllerEvent.component.id === "x-button" && controllerEvent.hand === "left") {
|
||||
// Only trigger on button press, not release
|
||||
// X button always works, even when disabled, to allow toggling status screen
|
||||
if (controllerEvent.pressed) {
|
||||
this._onStatusScreenToggleObservable.notifyObservers();
|
||||
}
|
||||
|
||||
@ -21,6 +21,7 @@ export class KeyboardInput {
|
||||
private _onCameraChangeObservable: Observable<number> = new Observable<number>();
|
||||
private _onRecordingActionObservable: Observable<RecordingAction> = new Observable<RecordingAction>();
|
||||
private _scene: Scene;
|
||||
private _enabled: boolean = true;
|
||||
|
||||
constructor(scene: Scene) {
|
||||
this._scene = scene;
|
||||
@ -51,12 +52,32 @@ export class KeyboardInput {
|
||||
* Get current input state (stick positions)
|
||||
*/
|
||||
public getInputState() {
|
||||
if (!this._enabled) {
|
||||
return {
|
||||
leftStick: Vector2.Zero(),
|
||||
rightStick: Vector2.Zero(),
|
||||
};
|
||||
}
|
||||
return {
|
||||
leftStick: this._leftStick.clone(),
|
||||
rightStick: this._rightStick.clone(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable keyboard input
|
||||
*/
|
||||
public setEnabled(enabled: boolean): void {
|
||||
this._enabled = enabled;
|
||||
if (!enabled) {
|
||||
// Reset stick values when disabled
|
||||
this._leftStick.x = 0;
|
||||
this._leftStick.y = 0;
|
||||
this._rightStick.x = 0;
|
||||
this._rightStick.y = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup keyboard and mouse event listeners
|
||||
*/
|
||||
@ -77,6 +98,28 @@ export class KeyboardInput {
|
||||
};
|
||||
|
||||
document.onkeydown = (ev) => {
|
||||
// Always allow inspector and camera toggle, even when disabled
|
||||
if (ev.key === 'i') {
|
||||
// Open Babylon Inspector
|
||||
import("@babylonjs/inspector").then((inspector) => {
|
||||
inspector.Inspector.Show(this._scene, {
|
||||
overlay: true,
|
||||
showExplorer: true,
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (ev.key === '1') {
|
||||
this._onCameraChangeObservable.notifyObservers(1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't process ship control inputs when disabled
|
||||
if (!this._enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Recording controls (with modifiers)
|
||||
/*if (ev.key === 'r' || ev.key === 'R') {
|
||||
if (ev.ctrlKey || ev.metaKey) {
|
||||
@ -96,18 +139,6 @@ export class KeyboardInput {
|
||||
}*/
|
||||
|
||||
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;
|
||||
case ' ':
|
||||
this._onShootObservable.notifyObservers();
|
||||
break;
|
||||
|
||||
30
src/main.ts
30
src/main.ts
@ -99,11 +99,21 @@ export class Main {
|
||||
this._currentLevel.getReadyObservable().add(async () => {
|
||||
setLoadingMessage("Starting game...");
|
||||
|
||||
// Get ship and set up replay observable
|
||||
const level1 = this._currentLevel as Level1;
|
||||
const ship = (level1 as any)._ship;
|
||||
|
||||
// Listen for replay requests from the ship
|
||||
if (ship) {
|
||||
ship.onReplayRequestObservable.add(() => {
|
||||
debugLog('Replay requested - reloading page');
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
|
||||
// If we entered XR before level creation, manually setup camera parenting
|
||||
// (This is needed because onInitialXRPoseSetObservable won't fire if we're already in XR)
|
||||
if (DefaultScene.XR && xrSession && DefaultScene.XR.baseExperience.state === 2) { // WebXRState.IN_XR = 2
|
||||
const level1 = this._currentLevel as Level1;
|
||||
const ship = (level1 as any)._ship;
|
||||
|
||||
if (ship && ship.transformNode) {
|
||||
debugLog('Manually parenting XR camera to ship transformNode');
|
||||
@ -315,7 +325,8 @@ export class Main {
|
||||
if (navigator.xr) {
|
||||
try {
|
||||
DefaultScene.XR = await WebXRDefaultExperience.CreateAsync(DefaultScene.MainScene, {
|
||||
disablePointerSelection: true,
|
||||
// Don't disable pointer selection - we need it for status screen buttons
|
||||
// Will detach it during gameplay and attach when status screen is shown
|
||||
disableTeleportation: true,
|
||||
disableNearInteraction: true,
|
||||
disableHandTracking: true,
|
||||
@ -323,6 +334,19 @@ export class Main {
|
||||
});
|
||||
debugLog(WebXRFeaturesManager.GetAvailableFeatures());
|
||||
debugLog("WebXR initialized successfully");
|
||||
|
||||
// Store pointer selection feature reference and detach it initially
|
||||
if (DefaultScene.XR) {
|
||||
const pointerFeature = DefaultScene.XR.baseExperience.featuresManager.getEnabledFeature(
|
||||
"xr-controller-pointer-selection"
|
||||
);
|
||||
if (pointerFeature) {
|
||||
(DefaultScene.XR as any).pointerSelectionFeature = pointerFeature;
|
||||
// Detach immediately to prevent interaction during gameplay
|
||||
pointerFeature.detach();
|
||||
debugLog("Pointer selection feature stored and detached");
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
debugLog("WebXR initialization failed, falling back to flat mode:", error);
|
||||
DefaultScene.XR = null;
|
||||
|
||||
147
src/ship.ts
147
src/ship.ts
@ -3,6 +3,7 @@ import {
|
||||
Color3,
|
||||
FreeCamera,
|
||||
Mesh,
|
||||
Observable,
|
||||
PhysicsAggregate,
|
||||
PhysicsMotionType,
|
||||
PhysicsShapeType,
|
||||
@ -52,6 +53,12 @@ export class Ship {
|
||||
private _isInLandingZone: boolean = false;
|
||||
private _isReplayMode: boolean;
|
||||
|
||||
// Observable for replay requests
|
||||
public onReplayRequestObservable: Observable<void> = new Observable<void>();
|
||||
|
||||
// Auto-show status screen flag
|
||||
private _statusScreenAutoShown: boolean = false;
|
||||
|
||||
constructor(audioEngine?: AudioEngineV2, isReplayMode: boolean = false) {
|
||||
this._audioEngine = audioEngine;
|
||||
this._isReplayMode = isReplayMode;
|
||||
@ -69,6 +76,10 @@ export class Ship {
|
||||
return this._keyboardInput;
|
||||
}
|
||||
|
||||
public get isInLandingZone(): boolean {
|
||||
return this._isInLandingZone;
|
||||
}
|
||||
|
||||
public set position(newPosition: Vector3) {
|
||||
const body = this._ship.physicsBody;
|
||||
|
||||
@ -159,7 +170,17 @@ export class Ship {
|
||||
// Wire up status screen toggle event
|
||||
this._controllerInput.onStatusScreenToggleObservable.add(() => {
|
||||
if (this._statusScreen) {
|
||||
this._statusScreen.toggle();
|
||||
if (this._statusScreen.isVisible) {
|
||||
// Hide status screen and re-enable controls
|
||||
this._statusScreen.hide();
|
||||
this._keyboardInput?.setEnabled(true);
|
||||
this._controllerInput?.setEnabled(true);
|
||||
} else {
|
||||
// Show status screen (manual pause, not game end) and disable controls
|
||||
this._statusScreen.show(false);
|
||||
this._keyboardInput?.setEnabled(false);
|
||||
this._controllerInput?.setEnabled(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -195,6 +216,9 @@ export class Ship {
|
||||
this._frameCount = 0;
|
||||
this.updatePhysics();
|
||||
}
|
||||
|
||||
// Check game end conditions every frame (but only acts once)
|
||||
this.checkGameEndConditions();
|
||||
});
|
||||
|
||||
// Setup camera
|
||||
@ -240,11 +264,101 @@ export class Ship {
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize status screen
|
||||
this._statusScreen = new StatusScreen(DefaultScene.MainScene, this._gameStats);
|
||||
// Initialize status screen with callbacks
|
||||
this._statusScreen = new StatusScreen(
|
||||
DefaultScene.MainScene,
|
||||
this._gameStats,
|
||||
() => this.handleReplayRequest(),
|
||||
() => this.handleExitVR(),
|
||||
() => this.handleResume()
|
||||
);
|
||||
this._statusScreen.initialize(this._camera);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle replay button click from status screen
|
||||
*/
|
||||
private handleReplayRequest(): void {
|
||||
debugLog('Replay button clicked - notifying observers');
|
||||
this.onReplayRequestObservable.notifyObservers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle exit VR button click from status screen
|
||||
*/
|
||||
private handleExitVR(): void {
|
||||
debugLog('Exit VR button clicked - refreshing browser');
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle resume button click from status screen
|
||||
*/
|
||||
private handleResume(): void {
|
||||
debugLog('Resume button clicked - hiding status screen and re-enabling controls');
|
||||
this._statusScreen.hide();
|
||||
this._keyboardInput?.setEnabled(true);
|
||||
this._controllerInput?.setEnabled(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check game-ending conditions and auto-show status screen
|
||||
* Conditions:
|
||||
* 1. Ship outside landing zone AND hull < 0.01 (death)
|
||||
* 2. Ship outside landing zone AND fuel < 0.01 AND velocity < 1 (stranded)
|
||||
* 3. All asteroids destroyed AND ship inside landing zone (victory)
|
||||
*/
|
||||
private checkGameEndConditions(): void {
|
||||
// Skip if already auto-shown or status screen doesn't exist
|
||||
if (this._statusScreenAutoShown || !this._statusScreen || !this._scoreboard) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip if no physics body yet
|
||||
if (!this._ship?.physicsBody) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get current ship status
|
||||
const hull = this._scoreboard.shipStatus.hull;
|
||||
const fuel = this._scoreboard.shipStatus.fuel;
|
||||
const asteroidsRemaining = this._scoreboard.remaining;
|
||||
|
||||
// Calculate total linear velocity
|
||||
const linearVelocity = this._ship.physicsBody.getLinearVelocity();
|
||||
const totalVelocity = linearVelocity.length();
|
||||
|
||||
// Check condition 1: Death by hull damage (outside landing zone)
|
||||
if (!this._isInLandingZone && hull < 0.01) {
|
||||
debugLog('Game end condition met: Hull critical outside landing zone');
|
||||
this._statusScreen.show(true);
|
||||
this._keyboardInput?.setEnabled(false);
|
||||
this._controllerInput?.setEnabled(false);
|
||||
this._statusScreenAutoShown = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check condition 2: Stranded (outside landing zone, no fuel, low velocity)
|
||||
if (!this._isInLandingZone && fuel < 0.01 && totalVelocity < 1) {
|
||||
debugLog('Game end condition met: Stranded (no fuel, low velocity)');
|
||||
this._statusScreen.show(true);
|
||||
this._keyboardInput?.setEnabled(false);
|
||||
this._controllerInput?.setEnabled(false);
|
||||
this._statusScreenAutoShown = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check condition 3: Victory (all asteroids destroyed, inside landing zone)
|
||||
if (asteroidsRemaining <= 0 && this._isInLandingZone) {
|
||||
debugLog('Game end condition met: Victory (all asteroids destroyed)');
|
||||
this._statusScreen.show(true);
|
||||
this._keyboardInput?.setEnabled(false);
|
||||
this._controllerInput?.setEnabled(false);
|
||||
this._statusScreenAutoShown = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update physics based on combined input from all input sources
|
||||
*/
|
||||
@ -302,16 +416,22 @@ export class Ship {
|
||||
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)
|
||||
// Check if ship mesh intersects with landing zone mesh
|
||||
const wasInZone = this._isInLandingZone;
|
||||
this._isInLandingZone = distance < 20;
|
||||
|
||||
// Get the meshes from the transform nodes
|
||||
const shipMesh = this._ship.getChildMeshes()[0];
|
||||
const landingMesh = this._landingAggregate.transformNode as Mesh;
|
||||
|
||||
// Use mesh intersection for accurate zone detection
|
||||
if (shipMesh && landingMesh) {
|
||||
this._isInLandingZone = shipMesh.intersectsMesh(landingMesh, false);
|
||||
} else {
|
||||
// Fallback: if meshes not available, assume not in zone
|
||||
this._isInLandingZone = false;
|
||||
}
|
||||
|
||||
// Log zone transitions
|
||||
if (this._isInLandingZone && !wasInZone) {
|
||||
debugLog("Ship entered landing zone - resupply active");
|
||||
} else if (!this._isInLandingZone && wasInZone) {
|
||||
@ -372,12 +492,11 @@ export class Ship {
|
||||
public setLandingZone(landingAggregate: PhysicsAggregate): void {
|
||||
this._landingAggregate = landingAggregate;
|
||||
|
||||
// Listen for trigger events to detect when ship enters/exits landing zone
|
||||
// Listen for trigger events for debugging (actual detection uses mesh intersection)
|
||||
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");
|
||||
debugLog("Physics trigger fired for landing zone");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import {
|
||||
AdvancedDynamicTexture,
|
||||
Button,
|
||||
Control,
|
||||
Rectangle,
|
||||
StackPanel,
|
||||
@ -14,6 +15,7 @@ import {
|
||||
Vector3
|
||||
} from "@babylonjs/core";
|
||||
import { GameStats } from "./gameStats";
|
||||
import { DefaultScene } from "./defaultScene";
|
||||
|
||||
/**
|
||||
* Status screen that displays game statistics
|
||||
@ -35,9 +37,25 @@ export class StatusScreen {
|
||||
private _accuracyText: TextBlock;
|
||||
private _fuelConsumedText: TextBlock;
|
||||
|
||||
constructor(scene: Scene, gameStats: GameStats) {
|
||||
// Buttons
|
||||
private _replayButton: Button;
|
||||
private _exitButton: Button;
|
||||
private _resumeButton: Button;
|
||||
|
||||
// Callbacks
|
||||
private _onReplayCallback: (() => void) | null = null;
|
||||
private _onExitCallback: (() => void) | null = null;
|
||||
private _onResumeCallback: (() => void) | null = null;
|
||||
|
||||
// Track whether game has ended
|
||||
private _isGameEnded: boolean = false;
|
||||
|
||||
constructor(scene: Scene, gameStats: GameStats, onReplay?: () => void, onExit?: () => void, onResume?: () => void) {
|
||||
this._scene = scene;
|
||||
this._gameStats = gameStats;
|
||||
this._onReplayCallback = onReplay || null;
|
||||
this._onExitCallback = onExit || null;
|
||||
this._onResumeCallback = onResume || null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -108,6 +126,70 @@ export class StatusScreen {
|
||||
this._fuelConsumedText = this.createStatText("Fuel Consumed: 0%");
|
||||
mainPanel.addControl(this._fuelConsumedText);
|
||||
|
||||
// Add spacing before buttons
|
||||
const spacer2 = this.createSpacer(50);
|
||||
mainPanel.addControl(spacer2);
|
||||
|
||||
// Create button bar
|
||||
const buttonBar = new StackPanel("buttonBar");
|
||||
buttonBar.isVertical = false;
|
||||
buttonBar.height = "80px";
|
||||
buttonBar.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER;
|
||||
buttonBar.spacing = 20;
|
||||
|
||||
// Create Resume button (only shown when game hasn't ended)
|
||||
this._resumeButton = Button.CreateSimpleButton("resumeButton", "RESUME GAME");
|
||||
this._resumeButton.width = "300px";
|
||||
this._resumeButton.height = "60px";
|
||||
this._resumeButton.color = "white";
|
||||
this._resumeButton.background = "#00ff88";
|
||||
this._resumeButton.cornerRadius = 10;
|
||||
this._resumeButton.thickness = 0;
|
||||
this._resumeButton.fontSize = "30px";
|
||||
this._resumeButton.fontWeight = "bold";
|
||||
this._resumeButton.onPointerClickObservable.add(() => {
|
||||
if (this._onResumeCallback) {
|
||||
this._onResumeCallback();
|
||||
}
|
||||
});
|
||||
buttonBar.addControl(this._resumeButton);
|
||||
|
||||
// Create Replay button (only shown when game has ended)
|
||||
this._replayButton = Button.CreateSimpleButton("replayButton", "REPLAY LEVEL");
|
||||
this._replayButton.width = "300px";
|
||||
this._replayButton.height = "60px";
|
||||
this._replayButton.color = "white";
|
||||
this._replayButton.background = "#00ff88";
|
||||
this._replayButton.cornerRadius = 10;
|
||||
this._replayButton.thickness = 0;
|
||||
this._replayButton.fontSize = "30px";
|
||||
this._replayButton.fontWeight = "bold";
|
||||
this._replayButton.onPointerClickObservable.add(() => {
|
||||
if (this._onReplayCallback) {
|
||||
this._onReplayCallback();
|
||||
}
|
||||
});
|
||||
buttonBar.addControl(this._replayButton);
|
||||
|
||||
// Create Exit VR button
|
||||
this._exitButton = Button.CreateSimpleButton("exitButton", "EXIT VR");
|
||||
this._exitButton.width = "300px";
|
||||
this._exitButton.height = "60px";
|
||||
this._exitButton.color = "white";
|
||||
this._exitButton.background = "#cc3333";
|
||||
this._exitButton.cornerRadius = 10;
|
||||
this._exitButton.thickness = 0;
|
||||
this._exitButton.fontSize = "30px";
|
||||
this._exitButton.fontWeight = "bold";
|
||||
this._exitButton.onPointerClickObservable.add(() => {
|
||||
if (this._onExitCallback) {
|
||||
this._onExitCallback();
|
||||
}
|
||||
});
|
||||
buttonBar.addControl(this._exitButton);
|
||||
|
||||
mainPanel.addControl(buttonBar);
|
||||
|
||||
this._texture.addControl(mainPanel);
|
||||
|
||||
// Initially hide the screen
|
||||
@ -166,13 +248,60 @@ export class StatusScreen {
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the status screen
|
||||
* Enable VR controller picking for button interaction
|
||||
*/
|
||||
public show(): void {
|
||||
private enablePointerSelection(): void {
|
||||
// Get the stored pointer selection feature
|
||||
const pointerFeature = (DefaultScene.XR as any)?.pointerSelectionFeature;
|
||||
if (pointerFeature && DefaultScene.XR?.baseExperience?.state === 2) { // WebXRState.IN_XR = 2
|
||||
try {
|
||||
// Attach the feature to enable pointer interaction
|
||||
pointerFeature.attach();
|
||||
} catch (error) {
|
||||
console.warn('Failed to attach pointer selection:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable VR controller picking
|
||||
*/
|
||||
private disablePointerSelection(): void {
|
||||
// Get the stored pointer selection feature
|
||||
const pointerFeature = (DefaultScene.XR as any)?.pointerSelectionFeature;
|
||||
if (pointerFeature) {
|
||||
try {
|
||||
// Detach the feature to disable pointer interaction
|
||||
pointerFeature.detach();
|
||||
} catch (error) {
|
||||
console.warn('Failed to detach pointer selection:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the status screen
|
||||
* @param isGameEnded - true if game has ended (death/stranded/victory), false if manually paused
|
||||
*/
|
||||
public show(isGameEnded: boolean = false): void {
|
||||
if (!this._screenMesh) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Store game ended state
|
||||
this._isGameEnded = isGameEnded;
|
||||
|
||||
// Show/hide appropriate buttons based on whether game has ended
|
||||
if (this._resumeButton) {
|
||||
this._resumeButton.isVisible = !isGameEnded;
|
||||
}
|
||||
if (this._replayButton) {
|
||||
this._replayButton.isVisible = isGameEnded;
|
||||
}
|
||||
|
||||
// Enable pointer selection for button interaction
|
||||
this.enablePointerSelection();
|
||||
|
||||
// Update statistics before showing
|
||||
this.updateStatistics();
|
||||
|
||||
@ -189,6 +318,9 @@ export class StatusScreen {
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable pointer selection when hiding
|
||||
this.disablePointerSelection();
|
||||
|
||||
this._screenMesh.setEnabled(false);
|
||||
this._isVisible = false;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user