Advertisement

← tower-of-time-game

src/components/InputManager.ts

6.8 KB Download file
export interface InputMapping {
  keyboard?: number[];
  gamepad?: number[]; // Gamepad button indices
}

export interface InputMappings {
  [actionName: string]: InputMapping;
}

export default class InputManager {
  private scene: Phaser.Scene;
  private gamepad?: Phaser.Input.Gamepad.Gamepad;
  private keyboardKeys: { [key: string]: Phaser.Input.Keyboard.Key } = {};
  private mappings: InputMappings;
  private previousButtonStates: { [buttonIndex: number]: boolean } = {};
  private currentButtonStates: { [buttonIndex: number]: boolean } = {};

  // Default input mappings
  private static readonly DEFAULT_MAPPINGS: InputMappings = {
    ACTION: {
      keyboard: [Phaser.Input.Keyboard.KeyCodes.SPACE],
      gamepad: [0], // A button
    },
    CANCEL: {
      keyboard: [Phaser.Input.Keyboard.KeyCodes.ESC],
      gamepad: [1], // B button
    },
    REWIND: {
      keyboard: [Phaser.Input.Keyboard.KeyCodes.BACKSPACE],
      gamepad: [4, 6], // Left trigger
    },
    UP: {
      keyboard: [
        Phaser.Input.Keyboard.KeyCodes.UP,
        Phaser.Input.Keyboard.KeyCodes.W,
      ],
      gamepad: [12], // D-pad up
    },
    DOWN: {
      keyboard: [
        Phaser.Input.Keyboard.KeyCodes.DOWN,
        Phaser.Input.Keyboard.KeyCodes.S,
      ],
      gamepad: [13], // D-pad down
    },
    LEFT: {
      keyboard: [
        Phaser.Input.Keyboard.KeyCodes.LEFT,
        Phaser.Input.Keyboard.KeyCodes.A,
      ],
      gamepad: [14], // D-pad left
    },
    RIGHT: {
      keyboard: [
        Phaser.Input.Keyboard.KeyCodes.RIGHT,
        Phaser.Input.Keyboard.KeyCodes.D,
      ],
      gamepad: [15], // D-pad right
    },
  };

  constructor(scene: Phaser.Scene, customMappings?: Partial<InputMappings>) {
    this.scene = scene;
    this.mappings = {
      ...InputManager.DEFAULT_MAPPINGS,
      ...customMappings,
    } as InputMappings;

    this.initializeInput();
  }

  private initializeInput(): void {
    // Initialize keyboard keys
    Object.entries(this.mappings).forEach(([actionName, mapping]) => {
      if (mapping.keyboard) {
        mapping.keyboard.forEach((keyCode) => {
          const keyName = `${actionName}_${keyCode}`;
          this.keyboardKeys[keyName] =
            this.scene.input.keyboard!.addKey(keyCode);
        });
      }
    });

    // Initialize gamepad input
    if (this.scene.input.gamepad) {
      // Check if a gamepad is already connected
      if (this.scene.input.gamepad.total > 0) {
        this.gamepad = this.scene.input.gamepad.getPad(0);
        console.log("Gamepad already connected, using existing gamepad");
      }

      // Also listen for future connections
      this.scene.input.gamepad.once(
        "connected",
        (gamepad: Phaser.Input.Gamepad.Gamepad) => {
          console.log("Gamepad connected");
          this.gamepad = gamepad;
        }
      );
    }
  }

  public isActionPressed(actionName: string): boolean {
    const mapping = this.mappings[actionName];
    if (!mapping) return false;

    // Check keyboard input
    if (mapping.keyboard) {
      for (const keyCode of mapping.keyboard) {
        const keyName = `${actionName}_${keyCode}`;
        const key = this.keyboardKeys[keyName];
        if (key && key.isDown) {
          return true;
        }
      }
    }

    // Check gamepad input
    if (mapping.gamepad && this.gamepad) {
      for (const buttonIndex of mapping.gamepad) {
        if (
          this.gamepad.buttons[buttonIndex] &&
          this.gamepad.buttons[buttonIndex].pressed
        ) {
          return true;
        }
      }

      // Handle analog stick input for directional actions
      if (actionName === "UP" && this.gamepad.leftStick.y < -0.5) return true;
      if (actionName === "DOWN" && this.gamepad.leftStick.y > 0.5) return true;
      if (actionName === "LEFT" && this.gamepad.leftStick.x < -0.5) return true;
      if (actionName === "RIGHT" && this.gamepad.leftStick.x > 0.5) return true;
    }

    return false;
  }

  public isActionJustPressed(actionName: string): boolean {
    const mapping = this.mappings[actionName];
    if (!mapping) return false;

    // Check keyboard input
    if (mapping.keyboard) {
      for (const keyCode of mapping.keyboard) {
        const keyName = `${actionName}_${keyCode}`;
        const key = this.keyboardKeys[keyName];
        if (key && Phaser.Input.Keyboard.JustDown(key)) {
          return true;
        }
      }
    }

    // Check gamepad input
    if (mapping.gamepad && this.gamepad) {
      for (const buttonIndex of mapping.gamepad) {
        // Check if button was just pressed (transitioned from false to true)
        const wasPressed = this.previousButtonStates[buttonIndex] || false;
        const isPressed = this.currentButtonStates[buttonIndex] || false;

        if (!wasPressed && isPressed) {
          return true;
        }
      }
    }

    return false;
  }

  public getMovementVector(): { x: number; y: number } {
    let x = 0;
    let y = 0;

    // Keyboard/gamepad digital input
    if (this.isActionPressed("LEFT")) x -= 1;
    if (this.isActionPressed("RIGHT")) x += 1;
    if (this.isActionPressed("UP")) y -= 1;
    if (this.isActionPressed("DOWN")) y += 1;

    // Gamepad analog stick input (overrides digital if stronger)
    if (this.gamepad) {
      const stickX = this.gamepad.leftStick.x;
      const stickY = this.gamepad.leftStick.y;

      if (Math.abs(stickX) > 0.1) x = stickX;
      if (Math.abs(stickY) > 0.1) y = stickY;
    }

    // Normalize diagonal movement
    if (x !== 0 && y !== 0) {
      const length = Math.sqrt(x * x + y * y);
      x /= length;
      y /= length;
    }

    return { x, y };
  }

  public updateMappings(newMappings: Partial<InputMappings>): void {
    this.mappings = { ...this.mappings, ...newMappings } as InputMappings;

    // Reinitialize keyboard keys
    this.keyboardKeys = {};
    Object.entries(this.mappings).forEach(([actionName, mapping]) => {
      if (mapping.keyboard) {
        mapping.keyboard.forEach((keyCode) => {
          const keyName = `${actionName}_${keyCode}`;
          this.keyboardKeys[keyName] =
            this.scene.input.keyboard!.addKey(keyCode);
        });
      }
    });
  }

  public getMappings(): InputMappings {
    return { ...this.mappings };
  }

  public getGamepad(): Phaser.Input.Gamepad.Gamepad | undefined {
    return this.gamepad;
  }

  public update(): void {
    // Update gamepad button states
    if (this.gamepad) {
      // Copy current states to previous states
      this.previousButtonStates = { ...this.currentButtonStates };

      // Update current states
      this.gamepad.buttons.forEach((button, index) => {
        const isPressed = button ? button.pressed : false;
        this.currentButtonStates[index] = isPressed;
      });
    }
  }
}