space-game/src/keyboardInput.ts
Michael Mainguy 72573054dd
All checks were successful
Build / build (push) Successful in 1m21s
Refactor Ship class into modular components
Major Refactoring:
- Extracted input handling, physics, audio, and weapons into separate modules
- Reduced Ship class from 542 lines to 305 lines (44% reduction)
- Ship now acts as coordinator between modular systems

New Modules Created:
- src/shipPhysics.ts - Pure force calculation and application logic
  - No external dependencies, fully testable in isolation
  - Handles linear/angular forces with velocity caps
  - Returns force magnitudes for audio feedback

- src/keyboardInput.ts - Keyboard and mouse input handling
  - Combines both input methods in unified interface
  - Exposes observables for shoot and camera change events
  - Clean getInputState() API

- src/controllerInput.ts - VR controller input handling
  - Maps WebXR controllers to input state
  - Handles thumbstick and button events
  - Observables for shooting and camera adjustments

- src/shipAudio.ts - Audio management system
  - Manages thrust sounds (primary and secondary)
  - Weapon fire sounds
  - Dynamic volume based on force magnitudes

- src/weaponSystem.ts - Projectile creation and lifecycle
  - Creates and manages ammo instances
  - Physics setup for projectiles
  - Auto-disposal timer

Ship Class Changes:
- Removed all input handling code (keyboard, mouse, VR)
- Removed force calculation logic
- Removed audio management code
- Removed weapon creation code
- Now wires up modular systems via observables
- Maintains same external API (backward compatible)

Material Improvements:
- Updated planet materials to use emissive texture
- Enhanced sun material with better color settings
- Set planets to unlit mode for better performance

Benefits:
- Each system independently testable
- Clear separation of concerns
- Easier to maintain and extend
- Better code organization

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-07 12:48:17 -06:00

144 lines
4.0 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
*/
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 _scene: Scene;
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 current input state (stick positions)
*/
public getInputState() {
return {
leftStick: this._leftStick.clone(),
rightStick: this._rightStick.clone(),
};
}
/**
* 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) => {
switch (ev.key) {
case '1':
this._onCameraChangeObservable.notifyObservers(1);
break;
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;
}
};
}
/**
* 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();
}
}