Add centralized input control management and mission brief improvements
All checks were successful
Build / build (push) Successful in 1m31s
All checks were successful
Build / build (push) Successful in 1m31s
- Create InputControlManager singleton for centralized ship controls and pointer selection management - Last-wins behavior for state changes - Mutually exclusive ship controls and VR pointer selection - Observable events for state changes with requester tracking - Enables debugging and prevents conflicts between UI components - Refactor Ship class to use InputControlManager - Remove disableControls() and enableControls() methods - Register input systems with InputControlManager on initialization - Simplify control state management throughout ship lifecycle - Update StatusScreen to use InputControlManager - Remove manual pointer selection enable/disable methods - Delegate control management to InputControlManager - Automatic laser pointer enabling when screen shows - Update Level1 mission brief to use InputControlManager - Consistent control management for mission brief display - Proper pointer selection during mission brief interaction - Fix controller input trigger blocking bug - Triggers now properly blocked when controls disabled - Prevents shooting when status screen or mission brief is visible - Only X-button (status screen toggle) allowed when disabled - Add START MISSION button to mission brief - Replace "Pull trigger to start" text with clickable button - Green styled button matching StatusScreen design - Works with VR laser pointer interaction - Trigger pull still works as fallback 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
eccf101b73
commit
1422c5b926
13
package-lock.json
generated
13
package-lock.json
generated
@ -18,6 +18,7 @@
|
||||
"@babylonjs/procedural-textures": "8.36.1",
|
||||
"@babylonjs/serializers": "8.36.1",
|
||||
"@newrelic/browser-agent": "^1.302.0",
|
||||
"loglevel": "^1.9.2",
|
||||
"openai": "4.52.3",
|
||||
"svelte-spa-router": "^4.0.1"
|
||||
},
|
||||
@ -1431,6 +1432,18 @@
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"node_modules/loglevel": {
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz",
|
||||
"integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/loglevel"
|
||||
}
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.30.21",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||
|
||||
@ -26,6 +26,7 @@
|
||||
"@babylonjs/serializers": "8.36.1",
|
||||
"@newrelic/browser-agent": "^1.302.0",
|
||||
"openai": "4.52.3",
|
||||
"loglevel": "^1.9.2",
|
||||
"svelte-spa-router": "^4.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@ -18,6 +18,7 @@ import {PhysicsRecorder} from "../replay/recording/physicsRecorder";
|
||||
import {getAnalytics} from "../analytics";
|
||||
import {MissionBrief} from "../ui/hud/missionBrief";
|
||||
import {LevelRegistry, LevelDirectoryEntry} from "./storage/levelRegistry";
|
||||
import { InputControlManager } from "../ship/input/inputControlManager";
|
||||
|
||||
export class Level1 implements Level {
|
||||
private _ship: Ship;
|
||||
@ -162,12 +163,13 @@ export class Level1 implements Level {
|
||||
|
||||
// Disable ship controls while mission brief is showing
|
||||
debugLog('[Level1] Disabling ship controls for mission brief');
|
||||
this._ship.disableControls();
|
||||
const inputManager = InputControlManager.getInstance();
|
||||
inputManager.disableShipControls("MissionBrief");
|
||||
|
||||
// Show mission brief with trigger observable
|
||||
this._missionBrief.show(this._levelConfig, directoryEntry, this._ship.onMissionBriefTriggerObservable, () => {
|
||||
debugLog('[Level1] Mission brief dismissed - enabling controls and starting game');
|
||||
this._ship.enableControls();
|
||||
inputManager.enableShipControls("MissionBrief");
|
||||
this.startGameplay();
|
||||
});
|
||||
}
|
||||
@ -343,10 +345,10 @@ export class Level1 implements Level {
|
||||
// Load background music before marking as ready
|
||||
if (this._audioEngine) {
|
||||
setLoadingMessage("Loading background music...");
|
||||
/*this._backgroundMusic = await this._audioEngine.createSoundAsync("background", "/song1.mp3", {
|
||||
this._backgroundMusic = await this._audioEngine.createSoundAsync("background", "/song1.mp3", {
|
||||
loop: true,
|
||||
volume: 0.5
|
||||
});*/
|
||||
});
|
||||
debugLog('Background music loaded successfully');
|
||||
}
|
||||
|
||||
|
||||
12
src/main.ts
12
src/main.ts
@ -42,6 +42,7 @@ import App from './components/layouts/App.svelte';
|
||||
import { BrowserAgent } from '@newrelic/browser-agent/loaders/browser-agent'
|
||||
import { AnalyticsService } from './analytics/analyticsService';
|
||||
import { NewRelicAdapter } from './analytics/adapters/newRelicAdapter';
|
||||
import { InputControlManager } from './ship/input/inputControlManager';
|
||||
|
||||
// Populate using values from NerdGraph
|
||||
const options = {
|
||||
@ -524,16 +525,19 @@ export class Main {
|
||||
debugLog(WebXRFeaturesManager.GetAvailableFeatures());
|
||||
debugLog("WebXR initialized successfully");
|
||||
|
||||
// Store pointer selection feature reference and detach it initially
|
||||
// Register pointer selection feature with InputControlManager
|
||||
if (DefaultScene.XR) {
|
||||
const pointerFeature = DefaultScene.XR.baseExperience.featuresManager.getEnabledFeature(
|
||||
"xr-controller-pointer-selection"
|
||||
);
|
||||
if (pointerFeature) {
|
||||
// Store for backward compatibility (can be removed later if not needed)
|
||||
(DefaultScene.XR as any).pointerSelectionFeature = pointerFeature;
|
||||
// Detach immediately to prevent interaction during gameplay
|
||||
pointerFeature.detach();
|
||||
debugLog("Pointer selection feature stored and detached");
|
||||
|
||||
// Register with InputControlManager
|
||||
const inputManager = InputControlManager.getInstance();
|
||||
inputManager.registerPointerFeature(pointerFeature);
|
||||
debugLog("Pointer selection feature registered with InputControlManager");
|
||||
}
|
||||
|
||||
// Hide Discord widget when entering VR, show when exiting
|
||||
|
||||
@ -274,10 +274,14 @@ export class ControllerInput {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._enabled && controllerEvent.type === "button" &&
|
||||
!(controllerEvent.component.id === "x-button" && controllerEvent.hand === "left") &&
|
||||
controllerEvent.component.type !== "trigger") {
|
||||
return;
|
||||
if (!this._enabled && controllerEvent.type === "button") {
|
||||
// Only allow X-button on left controller (for status screen toggle)
|
||||
if (controllerEvent.component.id === "x-button" && controllerEvent.hand === "left") {
|
||||
// Allow this through
|
||||
} else {
|
||||
// Block all other buttons including triggers
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (controllerEvent.type === "thumbstick") {
|
||||
@ -295,6 +299,9 @@ export class ControllerInput {
|
||||
|
||||
if (controllerEvent.type === "button") {
|
||||
if (controllerEvent.component.type === "trigger") {
|
||||
if (!this._enabled) {
|
||||
return;
|
||||
}
|
||||
if (controllerEvent.value > 0.9 && !this._shooting) {
|
||||
this._shooting = true;
|
||||
this._onShootObservable.notifyObservers();
|
||||
|
||||
208
src/ship/input/inputControlManager.ts
Normal file
208
src/ship/input/inputControlManager.ts
Normal file
@ -0,0 +1,208 @@
|
||||
import { Observable } from "@babylonjs/core";
|
||||
import { KeyboardInput } from "./keyboardInput";
|
||||
import { ControllerInput } from "./controllerInput";
|
||||
import debugLog from "../../core/debug";
|
||||
|
||||
/**
|
||||
* State change event emitted when ship controls or pointer selection state changes
|
||||
*/
|
||||
export interface InputControlStateChange {
|
||||
shipControlsEnabled: boolean;
|
||||
pointerSelectionEnabled: boolean;
|
||||
requester: string; // e.g., "StatusScreen", "MissionBrief", "Level1"
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Centralized manager for ship controls and pointer selection
|
||||
* Ensures ship controls and pointer selection are mutually exclusive
|
||||
* Emits events when state changes for debugging and analytics
|
||||
*
|
||||
* Design principles:
|
||||
* - Last-wins behavior: Most recent state change takes precedence
|
||||
* - Mutually exclusive: Ship controls and pointer selection are inverses
|
||||
* - Event-driven: Emits observables when state changes
|
||||
* - Centralized: Single source of truth via singleton pattern
|
||||
*/
|
||||
export class InputControlManager {
|
||||
private static _instance: InputControlManager | null = null;
|
||||
|
||||
private _shipControlsEnabled: boolean = true;
|
||||
private _pointerSelectionEnabled: boolean = false;
|
||||
|
||||
// Observable for state changes
|
||||
private _onStateChangedObservable: Observable<InputControlStateChange> = new Observable();
|
||||
|
||||
// References to systems we control
|
||||
private _keyboardInput: KeyboardInput | null = null;
|
||||
private _controllerInput: ControllerInput | null = null;
|
||||
private _xrPointerFeature: any = null;
|
||||
|
||||
/**
|
||||
* Private constructor for singleton pattern
|
||||
*/
|
||||
private constructor() {
|
||||
debugLog('[InputControlManager] Instance created');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get singleton instance
|
||||
*/
|
||||
public static getInstance(): InputControlManager {
|
||||
if (!InputControlManager._instance) {
|
||||
InputControlManager._instance = new InputControlManager();
|
||||
}
|
||||
return InputControlManager._instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register input systems (called by Ship during initialization)
|
||||
*/
|
||||
public registerInputSystems(keyboard: KeyboardInput | null, controller: ControllerInput | null): void {
|
||||
debugLog('[InputControlManager] Registering input systems', { keyboard: !!keyboard, controller: !!controller });
|
||||
this._keyboardInput = keyboard;
|
||||
this._controllerInput = controller;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register XR pointer feature (called by main.ts during XR setup)
|
||||
*/
|
||||
public registerPointerFeature(pointerFeature: any): void {
|
||||
debugLog('[InputControlManager] Registering XR pointer feature');
|
||||
this._xrPointerFeature = pointerFeature;
|
||||
|
||||
// Apply current state to the newly registered pointer feature
|
||||
this.updatePointerFeature();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable ship controls, disable pointer selection
|
||||
*/
|
||||
public enableShipControls(requester: string): void {
|
||||
debugLog(`[InputControlManager] Enabling ship controls (requester: ${requester})`);
|
||||
|
||||
// Update state
|
||||
this._shipControlsEnabled = true;
|
||||
this._pointerSelectionEnabled = false;
|
||||
|
||||
// Apply to input systems
|
||||
if (this._keyboardInput) {
|
||||
this._keyboardInput.setEnabled(true);
|
||||
}
|
||||
if (this._controllerInput) {
|
||||
this._controllerInput.setEnabled(true);
|
||||
}
|
||||
|
||||
// Disable pointer selection
|
||||
this.updatePointerFeature();
|
||||
|
||||
// Emit state change event
|
||||
this.emitStateChange(requester);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable ship controls, enable pointer selection
|
||||
*/
|
||||
public disableShipControls(requester: string): void {
|
||||
debugLog(`[InputControlManager] Disabling ship controls (requester: ${requester})`);
|
||||
|
||||
// Update state
|
||||
this._shipControlsEnabled = false;
|
||||
this._pointerSelectionEnabled = true;
|
||||
|
||||
// Apply to input systems
|
||||
if (this._keyboardInput) {
|
||||
this._keyboardInput.setEnabled(false);
|
||||
}
|
||||
if (this._controllerInput) {
|
||||
this._controllerInput.setEnabled(false);
|
||||
}
|
||||
|
||||
// Enable pointer selection
|
||||
this.updatePointerFeature();
|
||||
|
||||
// Emit state change event
|
||||
this.emitStateChange(requester);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update XR pointer feature state based on current settings
|
||||
*/
|
||||
private updatePointerFeature(): void {
|
||||
if (!this._xrPointerFeature) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (this._pointerSelectionEnabled) {
|
||||
// Enable pointer selection (attach feature)
|
||||
this._xrPointerFeature.attach();
|
||||
debugLog('[InputControlManager] Pointer selection enabled');
|
||||
} else {
|
||||
// Disable pointer selection (detach feature)
|
||||
this._xrPointerFeature.detach();
|
||||
debugLog('[InputControlManager] Pointer selection disabled');
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[InputControlManager] Failed to update pointer feature:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit state change event
|
||||
*/
|
||||
private emitStateChange(requester: string): void {
|
||||
const stateChange: InputControlStateChange = {
|
||||
shipControlsEnabled: this._shipControlsEnabled,
|
||||
pointerSelectionEnabled: this._pointerSelectionEnabled,
|
||||
requester: requester,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
|
||||
this._onStateChangedObservable.notifyObservers(stateChange);
|
||||
|
||||
debugLog('[InputControlManager] State changed:', stateChange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current ship controls enabled state
|
||||
*/
|
||||
public get shipControlsEnabled(): boolean {
|
||||
return this._shipControlsEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current pointer selection enabled state
|
||||
*/
|
||||
public get pointerSelectionEnabled(): boolean {
|
||||
return this._pointerSelectionEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get observable for state changes
|
||||
*/
|
||||
public get onStateChanged(): Observable<InputControlStateChange> {
|
||||
return this._onStateChangedObservable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup (for testing or hot reload)
|
||||
*/
|
||||
public dispose(): void {
|
||||
debugLog('[InputControlManager] Disposing');
|
||||
this._onStateChangedObservable.clear();
|
||||
this._keyboardInput = null;
|
||||
this._controllerInput = null;
|
||||
this._xrPointerFeature = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset singleton instance (for testing)
|
||||
*/
|
||||
public static reset(): void {
|
||||
if (InputControlManager._instance) {
|
||||
InputControlManager._instance.dispose();
|
||||
InputControlManager._instance = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -29,6 +29,7 @@ import { WeaponSystem } from "./weaponSystem";
|
||||
import { StatusScreen } from "../ui/hud/statusScreen";
|
||||
import { GameStats } from "../game/gameStats";
|
||||
import { getAnalytics } from "../analytics";
|
||||
import { InputControlManager } from "./input/inputControlManager";
|
||||
|
||||
export class Ship {
|
||||
private _ship: TransformNode;
|
||||
@ -124,6 +125,7 @@ export class Ship {
|
||||
this._ship = new TransformNode("shipBase", DefaultScene.MainScene);
|
||||
const data = await loadAsset("ship.glb");
|
||||
this._ship = data.container.transformNodes[0];
|
||||
// this._ship.id = "Ship"; // Set ID so mission brief can find it
|
||||
this._ship.position.y = 5;
|
||||
|
||||
// Create physics if enabled
|
||||
@ -221,6 +223,10 @@ export class Ship {
|
||||
|
||||
this._controllerInput = new ControllerInput();
|
||||
|
||||
// Register input systems with InputControlManager
|
||||
const inputManager = InputControlManager.getInstance();
|
||||
inputManager.registerInputSystems(this._keyboardInput, this._controllerInput);
|
||||
|
||||
// Wire up shooting events
|
||||
this._keyboardInput.onShootObservable.add(() => {
|
||||
this.handleShoot();
|
||||
@ -234,15 +240,12 @@ export class Ship {
|
||||
this._controllerInput.onStatusScreenToggleObservable.add(() => {
|
||||
if (this._statusScreen) {
|
||||
if (this._statusScreen.isVisible) {
|
||||
// Hide status screen and re-enable controls
|
||||
// Hide status screen - InputControlManager will handle control re-enabling
|
||||
this._statusScreen.hide();
|
||||
this._keyboardInput?.setEnabled(true);
|
||||
this._controllerInput?.setEnabled(true);
|
||||
} else {
|
||||
// Show status screen (manual pause, not game end) and disable controls
|
||||
// Show status screen (manual pause, not game end)
|
||||
// InputControlManager will handle control disabling
|
||||
this._statusScreen.show(false);
|
||||
this._keyboardInput?.setEnabled(false);
|
||||
this._controllerInput?.setEnabled(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -392,10 +395,9 @@ export class Ship {
|
||||
* Handle resume button click from status screen
|
||||
*/
|
||||
private handleResume(): void {
|
||||
debugLog('Resume button clicked - hiding status screen and re-enabling controls');
|
||||
debugLog('Resume button clicked - hiding status screen');
|
||||
// InputControlManager will handle re-enabling controls when status screen hides
|
||||
this._statusScreen.hide();
|
||||
this._keyboardInput?.setEnabled(true);
|
||||
this._controllerInput?.setEnabled(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -439,8 +441,7 @@ export class Ship {
|
||||
if (!this._isInLandingZone && hull < 0.01) {
|
||||
debugLog('Game end condition met: Hull critical outside landing zone');
|
||||
this._statusScreen.show(true, false); // Game ended, not victory
|
||||
this._keyboardInput?.setEnabled(false);
|
||||
this._controllerInput?.setEnabled(false);
|
||||
// InputControlManager will handle disabling controls when status screen shows
|
||||
this._statusScreenAutoShown = true;
|
||||
return;
|
||||
}
|
||||
@ -449,8 +450,7 @@ export class Ship {
|
||||
if (!this._isInLandingZone && fuel < 0.01 && totalVelocity < 5) {
|
||||
debugLog('Game end condition met: Stranded (no fuel, low velocity)');
|
||||
this._statusScreen.show(true, false); // Game ended, not victory
|
||||
this._keyboardInput?.setEnabled(false);
|
||||
this._controllerInput?.setEnabled(false);
|
||||
// InputControlManager will handle disabling controls when status screen shows
|
||||
this._statusScreenAutoShown = true;
|
||||
return;
|
||||
}
|
||||
@ -459,8 +459,7 @@ export class Ship {
|
||||
if (asteroidsRemaining <= 0 && this._isInLandingZone) {
|
||||
debugLog('Game end condition met: Victory (all asteroids destroyed)');
|
||||
this._statusScreen.show(true, true); // Game ended, VICTORY!
|
||||
this._keyboardInput?.setEnabled(false);
|
||||
this._controllerInput?.setEnabled(false);
|
||||
// InputControlManager will handle disabling controls when status screen shows
|
||||
this._statusScreenAutoShown = true;
|
||||
return;
|
||||
}
|
||||
@ -628,33 +627,6 @@ export class Ship {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable ship controls (for mission brief, etc.)
|
||||
*/
|
||||
public disableControls(): void {
|
||||
debugLog('[Ship] Disabling controls');
|
||||
this._controlsEnabled = false;
|
||||
if (this._controllerInput) {
|
||||
this._controllerInput.setEnabled(false);
|
||||
}
|
||||
if (this._keyboardInput) {
|
||||
this._keyboardInput.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable ship controls
|
||||
*/
|
||||
public enableControls(): void {
|
||||
debugLog('[Ship] Enabling controls');
|
||||
this._controlsEnabled = true;
|
||||
if (this._controllerInput) {
|
||||
this._controllerInput.setEnabled(true);
|
||||
}
|
||||
if (this._keyboardInput) {
|
||||
this._keyboardInput.setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose of ship resources
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import {
|
||||
AdvancedDynamicTexture,
|
||||
Button,
|
||||
Control,
|
||||
Rectangle,
|
||||
StackPanel,
|
||||
@ -199,16 +200,30 @@ export class MissionBrief {
|
||||
spacer3.thickness = 0;
|
||||
contentPanel.addControl(spacer3);
|
||||
|
||||
const startText = new TextBlock("startTExt");
|
||||
startText.text = 'Pull trigger to start';
|
||||
startText.color = "#00aa00";
|
||||
startText.fontSize = 48;
|
||||
startText.textWrapping = true;
|
||||
startText.height = "80px";
|
||||
startText.textHorizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER;
|
||||
startText.textVerticalAlignment = Control.VERTICAL_ALIGNMENT_TOP;
|
||||
startText.paddingLeft = "20px";
|
||||
contentPanel.addControl(startText);
|
||||
// START button
|
||||
const startButton = Button.CreateSimpleButton("startButton", "START MISSION");
|
||||
startButton.width = "400px";
|
||||
startButton.height = "60px";
|
||||
startButton.color = "white";
|
||||
startButton.background = "#00ff88";
|
||||
startButton.cornerRadius = 10;
|
||||
startButton.thickness = 0;
|
||||
startButton.fontSize = "36px";
|
||||
startButton.fontWeight = "bold";
|
||||
startButton.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER;
|
||||
startButton.onPointerClickObservable.add(() => {
|
||||
debugLog('[MissionBrief] START button clicked - dismissing mission brief');
|
||||
this.hide();
|
||||
if (this._onStartCallback) {
|
||||
this._onStartCallback();
|
||||
}
|
||||
// Remove trigger observer when button is clicked
|
||||
if (this._triggerObserver) {
|
||||
triggerObservable.remove(this._triggerObserver);
|
||||
this._triggerObserver = null;
|
||||
}
|
||||
});
|
||||
contentPanel.addControl(startButton);
|
||||
|
||||
// Show the container
|
||||
this._container.isVisible = true;
|
||||
|
||||
@ -19,6 +19,7 @@ import { DefaultScene } from "../../core/defaultScene";
|
||||
import { ProgressionManager } from "../../game/progression";
|
||||
import { AuthService } from "../../services/authService";
|
||||
import { FacebookShare, ShareData } from "../../services/facebookShare";
|
||||
import { InputControlManager } from "../../ship/input/inputControlManager";
|
||||
|
||||
/**
|
||||
* Status screen that displays game statistics
|
||||
@ -300,37 +301,6 @@ export class StatusScreen {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable VR controller picking for button interaction
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current level name for progression tracking
|
||||
@ -394,8 +364,9 @@ export class StatusScreen {
|
||||
}
|
||||
}
|
||||
|
||||
// Enable pointer selection for button interaction
|
||||
this.enablePointerSelection();
|
||||
// Disable ship controls and enable pointer selection via InputControlManager
|
||||
const inputManager = InputControlManager.getInstance();
|
||||
inputManager.disableShipControls("StatusScreen");
|
||||
|
||||
// Update statistics before showing
|
||||
this.updateStatistics();
|
||||
@ -426,8 +397,9 @@ export class StatusScreen {
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable pointer selection when hiding
|
||||
this.disablePointerSelection();
|
||||
// Re-enable ship controls and disable pointer selection via InputControlManager
|
||||
const inputManager = InputControlManager.getInstance();
|
||||
inputManager.enableShipControls("StatusScreen");
|
||||
|
||||
this._screenMesh.setEnabled(false);
|
||||
this._isVisible = false;
|
||||
|
||||
BIN
themes/default/base2.blend1
Normal file
BIN
themes/default/base2.blend1
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user