jsnes icon indicating copy to clipboard operation
jsnes copied to clipboard

Gamepad web API integration

Open webdevbrian opened this issue 7 years ago • 3 comments

🎮

https://developer.mozilla.org/en-US/docs/Web/API/Gamepad_API/Using_the_Gamepad_API

webdevbrian avatar Jan 30 '18 21:01 webdevbrian

YES PLEASE!

edo9k avatar Feb 05 '18 14:02 edo9k

It's a pretty straighforward process for adapting the HTML5 gamepad API. Feel free to modify any of what I've done to suit your needs. This was a service I wrote in Angular 4. Sorry about the styling, couldn't get it to fit into a single code tag ` import { Injectable } from "@angular/core"; import { BehaviorSubject } from "rxjs/BehaviorSubject"; import { Observable } from "rxjs/Rx";

import { Controller } from "jsnes";

@Injectable() export class GamepadService { private scanTimer: any; private gamepadHash: boolean[] = []; player1Device: InputDevice = this.availableDevices()[0]; private player2Device: any = "Keyboard";

private msgSource = new BehaviorSubject<InputButton>(null);
changed = this.msgSource.asObservable();

constructor() { }

gamepadMap = {
    0: Controller.BUTTON_A,
    1: Controller.BUTTON_B,
    8: Controller.BUTTON_SELECT,
    9: Controller.BUTTON_START,
    12: Controller.BUTTON_UP,
    13: Controller.BUTTON_DOWN,
    14: Controller.BUTTON_LEFT,
    15: Controller.BUTTON_RIGHT
};

KEYS = {
    88: Controller.BUTTON_A, // X
    89: Controller.BUTTON_B, // Y (Central European keyboard)
    90: Controller.BUTTON_B, // Z
    17: Controller.BUTTON_SELECT, // Right Ctrl
    13: Controller.BUTTON_START, // Enter
    38: Controller.BUTTON_UP, // Up
    40: Controller.BUTTON_DOWN, // Down
    37: Controller.BUTTON_LEFT, // Left
    39: Controller.BUTTON_RIGHT, // Right
    /*103: [2, Controller.BUTTON_A], // Num-7
    105: [2, Controller.BUTTON_B], // Num-9
    99: [2, Controller.BUTTON_SELECT], // Num-3
    97: [2, Controller.BUTTON_START], // Num-1
    104: [2, Controller.BUTTON_UP], // Num-8
    98: [2, Controller.BUTTON_DOWN], // Num-2
    100: [2, Controller.BUTTON_LEFT], // Num-4
    102: [2, Controller.BUTTON_RIGHT], // Num-6*/
};

update(button: InputButton) {
    this.msgSource.next(button);
}

gamepadSupported(): boolean {
    return "getGamepads" in navigator;
}

availableDevices(): InputDevice[] {
    var devices: InputDevice[] = [];
    if(!this.gamepadSupported()) return [new InputDevice({ id: 0, isGamepad: false, name: "Keyboard" })];
    var gamepads = navigator.getGamepads();
    for(var i = 0; i < gamepads.length; ++i) {
        if(gamepads[i] != null && this.isValidGamepad(gamepads[i])) {
            devices.push(new InputDevice({ id: devices.length, isGamepad: true, name: gamepads[i].id }));
        }
    }
    devices.push(new InputDevice({ id: devices.length, isGamepad: false, name: "Keyboard" }));
    return devices;
}

connect() {
    document.addEventListener("keydown",  this.keydown.bind(this));
    document.addEventListener("keyup", this.keyup.bind(this));

    for(let key of Object.keys(this.gamepadMap)) {
        this.gamepadHash.push(false);
    }

    this.scanTimer = Observable.interval(100);
    this.scanTimer.subscribe(x => { this.onGamepad() });
}

disconnect() {
    document.removeEventListener("keydown", this.keydown);
    document.removeEventListener("keyup", this.keyup);
}

setPlayerDevice(id: number) {
    var devices = this.availableDevices();
    if(id >= devices.length) throw new Error("Input device doesn't exist");

    this.player1Device = devices[id];
    return devices[id];
}

onGamepad() {
    if(!this.player1Device.isGamepad) return;

    var gp = this.getSystemDevice(this.player1Device);
    if(gp == null) return;
    
    var id = gp.id;

    Object.keys(this.gamepadMap).forEach((key, index) => {
        var btn = gp.buttons[key];
        if(btn.pressed != this.gamepadHash[index]) {
            this.gamepadHash[index] = btn.pressed;
            this.update(new InputButton({ button: this.gamepadMap[key], pressed: btn.pressed, player: 1 }));
        }
    });
}

keydown(e) {
    if(this.player1Device.isGamepad) return;
    var code = this.KEYS[e.keyCode];
    if(code == undefined) return;
    this.update(new InputButton({ button: code, pressed: true, player: 1 }));
}

keyup(e) {
    if(this.player1Device.isGamepad) return;
    var code = this.KEYS[e.keyCode];
    if(code == undefined) return;
    this.update(new InputButton({ button: code, pressed: false, player: 1 }));
}

private isValidGamepad(gp: Gamepad):boolean {
    return gp.buttons.length >= 6;
}

private getSystemDevice(device: InputDevice) {
    var gamepads = navigator.getGamepads();
    for(var i = 0; i < gamepads.length; ++i) {
        if(gamepads[i].id == device.name) return gamepads[i];
    }     
    return null;   
}
}

export class InputButton { button: any; pressed: boolean; player: number;

public constructor(init?:Partial<InputButton>) {
    Object.assign(this, init);
}

} export class InputDevice { id: number; isGamepad: boolean; name: string;

public constructor(init?:Partial<InputDevice>) {
    Object.assign(this, init);
}

}`

brianushman avatar Feb 15 '18 19:02 brianushman

@brianushman Do you have any reference for onscreen controller?

bhavincb avatar Mar 12 '19 12:03 bhavincb