space-game/src/keyboardInput.ts
Michael Mainguy ccc1745ed2
All checks were successful
Build / build (push) Successful in 1m21s
Refactor asteroid scaling and reorganize assets
Major changes:
- Change asteroid config to use single scale number instead of Vector3
- Move planetTextures to public/assets/materials/planetTextures
- Add GLB path configuration for start base
- Fix inspector toggle to work bidirectionally
- Add progression system support

Asteroid Scaling Changes:
- Update AsteroidConfig interface to use 'scale: number' instead of 'scaling: Vector3Array'
- Modify RockFactory.createRock() to accept single scale parameter
- Update level serializer/deserializer to use uniform scale
- Simplify level generation code in levelEditor and levelGenerator
- Update validation to check for positive number instead of 3-element array

Asset Organization:
- Move public/planetTextures → public/assets/materials/planetTextures
- Update all texture path references in planetTextures.ts (210 paths)
- Update default texture paths in createSun.ts and levelSerializer.ts
- Update CLAUDE.md documentation with new asset structure

Start Base Improvements:
- Add baseGlbPath and landingGlbPath to StartBaseConfig
- Update StarBase.buildStarBase() to accept GLB path parameter
- Add position parameter support to StarBase
- Store GLB path in mesh metadata for serialization
- Add UI field in level editor for base GLB path

Inspector Toggle:
- Fix 'i' key to toggle inspector on/off instead of only on
- Use scene.debugLayer.isVisible() for state checking
- Consistent with ReplayManager implementation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-10 12:19:31 -06:00

227 lines
6.8 KiB
TypeScript

import { FreeCamera, Observable, Scene, Vector2 } from "@babylonjs/core";
/**
* Handles keyboard and mouse input for ship control
* Combines both input methods into a unified interface
*/
/**
* Recording control action types
*/
export type RecordingAction =
| "exportRingBuffer" // R key
| "toggleLongRecording" // Ctrl+R
| "exportLongRecording"; // Shift+R
export class KeyboardInput {
private _leftStick: Vector2 = Vector2.Zero();
private _rightStick: Vector2 = Vector2.Zero();
private _mouseDown: boolean = false;
private _mousePos: Vector2 = new Vector2(0, 0);
private _onShootObservable: Observable<void> = new Observable<void>();
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;
}
/**
* Get observable that fires when shoot key/button is pressed
*/
public get onShootObservable(): Observable<void> {
return this._onShootObservable;
}
/**
* Get observable that fires when camera change key is pressed
*/
public get onCameraChangeObservable(): Observable<number> {
return this._onCameraChangeObservable;
}
/**
* Get observable that fires when recording action is triggered
*/
public get onRecordingActionObservable(): Observable<RecordingAction> {
return this._onRecordingActionObservable;
}
/**
* 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
*/
public setup(): void {
this.setupKeyboard();
this.setupMouse();
}
/**
* Setup keyboard event listeners
*/
private setupKeyboard(): void {
document.onkeyup = () => {
this._leftStick.y = 0;
this._leftStick.x = 0;
this._rightStick.y = 0;
this._rightStick.x = 0;
};
document.onkeydown = (ev) => {
// Always allow inspector and camera toggle, even when disabled
if (ev.key === 'i') {
// Toggle Babylon Inspector
if (this._scene.debugLayer.isVisible()) {
this._scene.debugLayer.hide();
} else {
this._scene.debugLayer.show({
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) {
// Ctrl+R or Cmd+R: Toggle long recording
ev.preventDefault(); // Prevent browser reload
this._onRecordingActionObservable.notifyObservers("toggleLongRecording");
return;
} else if (ev.shiftKey) {
// Shift+R: Export long recording
this._onRecordingActionObservable.notifyObservers("exportLongRecording");
return;
} else {
// R: Export ring buffer (last 30 seconds)
this._onRecordingActionObservable.notifyObservers("exportRingBuffer");
return;
}
}*/
switch (ev.key) {
case ' ':
this._onShootObservable.notifyObservers();
break;
case 'e':
break;
case 'w':
this._leftStick.y = -1;
break;
case 's':
this._leftStick.y = 1;
break;
case 'a':
this._leftStick.x = -1;
break;
case 'd':
this._leftStick.x = 1;
break;
case 'ArrowUp':
this._rightStick.y = -1;
break;
case 'ArrowDown':
this._rightStick.y = 1;
break;
case 'ArrowLeft':
this._rightStick.x = -1;
break;
case 'ArrowRight':
this._rightStick.x = 1;
break;
}
};
}
/**
* Setup mouse event listeners for drag-based rotation control
*/
private setupMouse(): void {
this._scene.onPointerDown = (evt) => {
this._mousePos.x = evt.x;
this._mousePos.y = evt.y;
this._mouseDown = true;
};
this._scene.onPointerUp = () => {
this._mouseDown = false;
};
this._scene.onPointerMove = (ev) => {
if (!this._mouseDown) {
return;
}
const xInc = (ev.x - this._mousePos.x) / 100;
const yInc = (ev.y - this._mousePos.y) / 100;
if (Math.abs(xInc) <= 1) {
this._rightStick.x = xInc;
} else {
this._rightStick.x = Math.sign(xInc);
}
if (Math.abs(yInc) <= 1) {
this._rightStick.y = yInc;
} else {
this._rightStick.y = Math.sign(yInc);
}
};
}
/**
* Cleanup event listeners
*/
public dispose(): void {
document.onkeydown = null;
document.onkeyup = null;
this._scene.onPointerDown = null;
this._scene.onPointerUp = null;
this._scene.onPointerMove = null;
this._onShootObservable.clear();
this._onCameraChangeObservable.clear();
this._onRecordingActionObservable.clear();
}
}