mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2025-01-27 08:31:17 +00:00
added Gamepad API support
This commit is contained in:
parent
271c2ea020
commit
9324b23def
@ -86,7 +86,7 @@ TODO:
|
||||
- upload binary files doesn't do what's expected, changing pulldown and whatnot
|
||||
- chrome autostart audio: https://github.com/processing/p5.js-sound/issues/249
|
||||
- firefox autostart audio: https://support.mozilla.org/en-US/kb/block-autoplay
|
||||
- show player controls for each platform, allow touch support, navigator.getGamepads
|
||||
- show player controls for each platform, allow touch support
|
||||
- better undo/diff for mistakes?
|
||||
- ide bug/feature visualizer for sponsors
|
||||
- global undo/redo at checkpoints (when rom changes)
|
||||
|
@ -1,5 +1,5 @@
|
||||
|
||||
import { RAM, RasterVideo, dumpRAM, AnimationTimer, setKeyboardFromMap, padBytes } from "./emu";
|
||||
import { RAM, RasterVideo, dumpRAM, AnimationTimer, setKeyboardFromMap, padBytes, ControllerPoller } from "./emu";
|
||||
import { hex, printFlags, invertMap } from "./util";
|
||||
import { CodeAnalyzer } from "./analysis";
|
||||
import { disassemble6502 } from "./cpu/disasm6502";
|
||||
@ -265,11 +265,14 @@ export abstract class BaseDebugPlatform extends BasePlatform {
|
||||
this.resume();
|
||||
}
|
||||
preFrame() {
|
||||
this.updateRecorder();
|
||||
}
|
||||
postFrame() {
|
||||
}
|
||||
pollControls() {
|
||||
}
|
||||
nextFrame(novideo : boolean) {
|
||||
this.pollControls();
|
||||
this.updateRecorder();
|
||||
this.preFrame();
|
||||
this.advance(novideo);
|
||||
this.postFrame();
|
||||
@ -1101,6 +1104,7 @@ export abstract class BasicZ80ScanlinePlatform extends BaseZ80Platform {
|
||||
pixels : Uint32Array;
|
||||
inputs = new Uint8Array(16);
|
||||
mainElement : HTMLElement;
|
||||
poller : ControllerPoller;
|
||||
|
||||
abstract newRAM() : Uint8Array;
|
||||
abstract newMembus() : MemoryBus;
|
||||
@ -1126,13 +1130,15 @@ export abstract class BasicZ80ScanlinePlatform extends BaseZ80Platform {
|
||||
this.video = new RasterVideo(this.mainElement, this.canvasWidth, this.numVisibleScanlines, this.getVideoOptions());
|
||||
this.video.create();
|
||||
this.pixels = this.video.getFrameData();
|
||||
setKeyboardFromMap(this.video, this.inputs, this.getKeyboardMap(), this.getKeyboardFunction());
|
||||
this.poller = setKeyboardFromMap(this.video, this.inputs, this.getKeyboardMap(), this.getKeyboardFunction());
|
||||
this.timer = new AnimationTimer(60, this.nextFrame.bind(this));
|
||||
}
|
||||
|
||||
readAddress(addr) {
|
||||
return this.membus.read(addr);
|
||||
}
|
||||
|
||||
pollControls() { this.poller.poll(); }
|
||||
|
||||
advance(novideo : boolean) {
|
||||
var extraCycles = 0;
|
||||
|
116
src/emu.ts
116
src/emu.ts
@ -51,13 +51,13 @@ export enum KeyFlags {
|
||||
}
|
||||
|
||||
function _setKeyboardEvents(canvas:HTMLElement, callback:KeyboardCallback) {
|
||||
canvas.onkeydown = function(e) {
|
||||
canvas.onkeydown = (e) => {
|
||||
callback(e.which, 0, KeyFlags.KeyDown|_metakeyflags(e));
|
||||
};
|
||||
canvas.onkeyup = function(e) {
|
||||
canvas.onkeyup = (e) => {
|
||||
callback(e.which, 0, KeyFlags.KeyUp|_metakeyflags(e));
|
||||
};
|
||||
canvas.onkeypress = function(e) {
|
||||
canvas.onkeypress = (e) => {
|
||||
callback(e.which, e.charCode, KeyFlags.KeyPress|_metakeyflags(e));
|
||||
};
|
||||
};
|
||||
@ -327,16 +327,24 @@ interface KeyDef {
|
||||
button?:number
|
||||
};
|
||||
|
||||
interface KeyMapEntry {
|
||||
index:number;
|
||||
mask:number;
|
||||
def:KeyDef;
|
||||
}
|
||||
|
||||
type KeyCodeMap = Map<number,KeyMapEntry>;
|
||||
|
||||
export const Keys : {[keycode:string]:KeyDef} = {
|
||||
// gamepad and keyboard (player 0)
|
||||
UP: {c: 38, n: "Up", plyr:0, yaxis:-1},
|
||||
DOWN: {c: 40, n: "Down", plyr:0, yaxis:1},
|
||||
LEFT: {c: 37, n: "Left", plyr:0, xaxis:-1},
|
||||
RIGHT: {c: 39, n: "Right", plyr:0, xaxis:1},
|
||||
A: {c: 32, n: "Space", plyr:0, button:0},
|
||||
B: {c: 17, n: "Ctrl", plyr:0, button:1},
|
||||
GP_A: {c: 88, n: "X", plyr:0, button:0},
|
||||
GP_B: {c: 90, n: "Z", plyr:0, button:1},
|
||||
A: {c: 32, n: "Space", plyr:0, button:0},
|
||||
B: {c: 17, n: "Ctrl", plyr:0, button:1},
|
||||
GP_A: {c: 88, n: "X", plyr:0, button:0},
|
||||
GP_B: {c: 90, n: "Z", plyr:0, button:1},
|
||||
SELECT: {c: 220, n: "\\", plyr:0, button:8},
|
||||
START: {c: 13, n: "Enter", plyr:0, button:9},
|
||||
// gamepad and keyboard (player 1)
|
||||
@ -344,8 +352,8 @@ export const Keys : {[keycode:string]:KeyDef} = {
|
||||
P2_DOWN: {c: 83, n: "S", plyr:1, yaxis:1},
|
||||
P2_LEFT: {c: 65, n: "A", plyr:1, xaxis:-1},
|
||||
P2_RIGHT: {c: 68, n: "D", plyr:1, xaxis:1},
|
||||
P2_A: {c: 84, n: "T", plyr:1, button:0},
|
||||
P2_B: {c: 82, n: "R", plyr:1, button:1},
|
||||
P2_A: {c: 84, n: "T", plyr:1, button:0},
|
||||
P2_B: {c: 82, n: "R", plyr:1, button:1},
|
||||
P2_SELECT: {c: 70, n: "F", plyr:1, button:8},
|
||||
P2_START: {c: 71, n: "G", plyr:1, button:9},
|
||||
// keyboard only
|
||||
@ -459,9 +467,11 @@ function _metakeyflags(e) {
|
||||
(e.metaKey?KeyFlags.Meta:0);
|
||||
}
|
||||
|
||||
export function setKeyboardFromMap(video, switches, map, func?) {
|
||||
video.setKeyboardEvents(function(key,code,flags) {
|
||||
var o = map[key];
|
||||
type KeyMapFunction = (o:KeyMapEntry, key:number, code:number, flags:number) => void;
|
||||
|
||||
export function setKeyboardFromMap(video:RasterVideo, switches:number[]|Uint8Array, map:KeyCodeMap, func?:KeyMapFunction) {
|
||||
var handler = (key,code,flags) => {
|
||||
var o : KeyMapEntry = map[key];
|
||||
if (o && func) {
|
||||
func(o, key, code, flags);
|
||||
}
|
||||
@ -479,18 +489,92 @@ export function setKeyboardFromMap(video, switches, map, func?) {
|
||||
switches[o.index] &= ~mask;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
video.setKeyboardEvents(handler);
|
||||
return new ControllerPoller(map, handler);
|
||||
}
|
||||
|
||||
export function makeKeycodeMap(table : [KeyDef,number,number][]) {
|
||||
var map = new Map();
|
||||
export function makeKeycodeMap(table : [KeyDef,number,number][]) : KeyCodeMap {
|
||||
var map = new Map<number,KeyMapEntry>();
|
||||
for (var i=0; i<table.length; i++) {
|
||||
var entry = table[i];
|
||||
map[entry[0].c] = {index:entry[1], mask:entry[2]};
|
||||
var val : KeyMapEntry = {index:entry[1], mask:entry[2], def:entry[0]};
|
||||
map[entry[0].c] = val;
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
export class ControllerPoller {
|
||||
active = false;
|
||||
map : KeyCodeMap;
|
||||
handler;
|
||||
state = new Int8Array(32);
|
||||
lastState = new Int8Array(32);
|
||||
AXIS0 = 24; // first joystick axis index
|
||||
constructor(map:KeyCodeMap, handler:(key,code,flags) => void) {
|
||||
this.map = map;
|
||||
this.handler = handler;
|
||||
window.addEventListener("gamepadconnected", (event) => {
|
||||
console.log("Gamepad connected:", event);
|
||||
this.active = typeof navigator.getGamepads === 'function';
|
||||
});
|
||||
window.addEventListener("gamepaddisconnected", (event) => {
|
||||
console.log("Gamepad disconnected:", event);
|
||||
});
|
||||
}
|
||||
poll() {
|
||||
if (!this.active) return;
|
||||
var gamepads = navigator.getGamepads();
|
||||
for (var gpi=0; gpi<gamepads.length; gpi++) {
|
||||
var gp = gamepads[gpi];
|
||||
if (gp) {
|
||||
for (var i=0; i<gp.axes.length; i++) {
|
||||
var k = i + this.AXIS0;
|
||||
this.state[k] = Math.round(gp.axes[i]);
|
||||
if (this.state[k] != this.lastState[k]) {
|
||||
this.handleStateChange(gpi,k);
|
||||
}
|
||||
}
|
||||
for (var i=0; i<gp.buttons.length; i++) {
|
||||
this.state[i] = gp.buttons[i].pressed ? 1 : 0;
|
||||
if (this.state[i] != this.lastState[i]) {
|
||||
this.handleStateChange(gpi,i);
|
||||
}
|
||||
}
|
||||
this.lastState.set(this.state);
|
||||
}
|
||||
}
|
||||
}
|
||||
handleStateChange(gpi:number, k:number) {
|
||||
var axis = k - this.AXIS0;
|
||||
for (var code in this.map) {
|
||||
var entry = this.map[code];
|
||||
var def = entry.def;
|
||||
// is this a gamepad entry? same player #?
|
||||
if (def && def.plyr == gpi) {
|
||||
var state = this.state[k];
|
||||
var lastState = this.lastState[k];
|
||||
// check for button/axis match
|
||||
if (k == def.button || (axis == 0 && def.xaxis == state) || (axis == 1 && def.yaxis == state)) {
|
||||
//console.log(gpi,k,state,entry);
|
||||
if (state != 0) {
|
||||
this.handler(code, 0, KeyFlags.KeyDown);
|
||||
} else {
|
||||
this.handler(code, 0, KeyFlags.KeyUp);
|
||||
}
|
||||
break;
|
||||
}
|
||||
// joystick released?
|
||||
else if (state == 0 && (axis == 0 && def.xaxis == lastState) || (axis == 1 && def.yaxis == lastState)) {
|
||||
this.handler(code, 0, KeyFlags.KeyUp);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function padBytes(data:Uint8Array|number[], len:number) : Uint8Array {
|
||||
if (data.length > len) {
|
||||
throw Error("Data too long, " + data.length + " > " + len);
|
||||
|
@ -201,6 +201,7 @@ const _BallyAstrocadePlatform = function(mainElement, arcade) {
|
||||
class BallyAstrocadePlatform extends BaseZ80Platform implements Platform {
|
||||
|
||||
scanline : number;
|
||||
poller;
|
||||
|
||||
getPresets() {
|
||||
return ASTROCADE_PRESETS;
|
||||
@ -328,7 +329,7 @@ const _BallyAstrocadePlatform = function(mainElement, arcade) {
|
||||
video.create();
|
||||
video.setupMouseEvents();
|
||||
var idata = video.getFrameData();
|
||||
setKeyboardFromMap(video, inputs, ASTROCADE_KEYCODE_MAP);
|
||||
this.poller = setKeyboardFromMap(video, inputs, ASTROCADE_KEYCODE_MAP);
|
||||
pixels = video.getFrameData();
|
||||
timer = new AnimationTimer(60, this.nextFrame.bind(this));
|
||||
// default palette
|
||||
@ -345,6 +346,8 @@ const _BallyAstrocadePlatform = function(mainElement, arcade) {
|
||||
inputs[0x1d] = video.paddle_y & 0xff;
|
||||
}
|
||||
|
||||
pollControls() { this.poller.poll(); }
|
||||
|
||||
advance(novideo : boolean) {
|
||||
this.scanline = 0;
|
||||
var extra = 0; // keep track of spare cycles
|
||||
|
@ -72,6 +72,7 @@ const _ColecoVisionPlatform = function(mainElement) {
|
||||
var audio, psg;
|
||||
var inputs = new Uint8Array(4);
|
||||
var keypadMode = false;
|
||||
var poller;
|
||||
|
||||
class ColecoVisionPlatform extends BaseZ80Platform implements Platform {
|
||||
|
||||
@ -137,9 +138,11 @@ const _ColecoVisionPlatform = function(mainElement) {
|
||||
}
|
||||
};
|
||||
vdp = new TMS9918A(video.getFrameData(), cru, true); // true = 4 sprites/line
|
||||
setKeyboardFromMap(video, inputs, COLECOVISION_KEYCODE_MAP);
|
||||
poller = setKeyboardFromMap(video, inputs, COLECOVISION_KEYCODE_MAP);
|
||||
timer = new AnimationTimer(60, this.nextFrame.bind(this));
|
||||
}
|
||||
|
||||
pollControls() { poller.poll(); }
|
||||
|
||||
readAddress(addr) {
|
||||
return membus.read(addr);
|
||||
|
@ -203,6 +203,7 @@ const _GalaxianPlatform = function(mainElement, options) {
|
||||
class GalaxianPlatform extends BaseZ80Platform implements Platform {
|
||||
|
||||
scanline : number;
|
||||
poller;
|
||||
|
||||
getPresets() {
|
||||
return GALAXIAN_PRESETS;
|
||||
@ -295,10 +296,12 @@ const _GalaxianPlatform = function(mainElement, options) {
|
||||
video = new RasterVideo(mainElement,264,264,{rotate:90});
|
||||
video.create();
|
||||
var idata = video.getFrameData();
|
||||
setKeyboardFromMap(video, inputs, keyMap);
|
||||
this.poller = setKeyboardFromMap(video, inputs, keyMap);
|
||||
pixels = video.getFrameData();
|
||||
timer = new AnimationTimer(60, this.nextFrame.bind(this));
|
||||
}
|
||||
|
||||
pollControls() { this.poller.poll(); }
|
||||
|
||||
readAddress(a) {
|
||||
return (a == 0x7000 || a == 0x7800) ? null : membus.read(a); // ignore watchdog
|
||||
|
@ -1,7 +1,7 @@
|
||||
"use strict";
|
||||
|
||||
import { Platform, Base6502Platform, BaseMAMEPlatform, getOpcodeMetadata_6502, cpuStateToLongString_6502, getToolForFilename_6502, dumpStackToString, ProfilerOutput } from "../baseplatform";
|
||||
import { PLATFORMS, RAM, newAddressDecoder, padBytes, noise, setKeyboardFromMap, AnimationTimer, RasterVideo, Keys, makeKeycodeMap, dumpRAM, KeyFlags, EmuHalt } from "../emu";
|
||||
import { PLATFORMS, RAM, newAddressDecoder, padBytes, noise, setKeyboardFromMap, AnimationTimer, RasterVideo, Keys, makeKeycodeMap, dumpRAM, KeyFlags, EmuHalt, ControllerPoller } from "../emu";
|
||||
import { hex, lpad, lzgmini, byteArrayToString } from "../util";
|
||||
import { CodeAnalyzer_nes } from "../analysis";
|
||||
import { SampleAudio } from "../audio";
|
||||
@ -71,6 +71,7 @@ class JSNESPlatform extends Base6502Platform implements Platform {
|
||||
video;
|
||||
audio;
|
||||
timer;
|
||||
poller : ControllerPoller;
|
||||
audioFrequency = 44030; //44100
|
||||
frameindex = 0;
|
||||
ntvideo;
|
||||
@ -136,7 +137,7 @@ class JSNESPlatform extends Base6502Platform implements Platform {
|
||||
}
|
||||
this.timer = new AnimationTimer(60, this.nextFrame.bind(this));
|
||||
// set keyboard map
|
||||
setKeyboardFromMap(this.video, [], JSNES_KEYCODE_MAP, (o,key,code,flags) => {
|
||||
this.poller = setKeyboardFromMap(this.video, [], JSNES_KEYCODE_MAP, (o,key,code,flags) => {
|
||||
if (flags & KeyFlags.KeyDown)
|
||||
this.nes.buttonDown(o.index+1, o.mask); // controller, button
|
||||
else if (flags & KeyFlags.KeyUp)
|
||||
@ -144,6 +145,8 @@ class JSNESPlatform extends Base6502Platform implements Platform {
|
||||
});
|
||||
//var s = ''; nes.ppu.palTable.curTable.forEach((rgb) => { s += "0x"+hex(rgb,6)+", "; }); console.log(s);
|
||||
}
|
||||
|
||||
pollControls() { this.poller.poll(); }
|
||||
|
||||
advance(novideo : boolean) {
|
||||
this.nes.frame();
|
||||
|
@ -223,6 +223,7 @@ var VerilogPlatform = function(mainElement, options) {
|
||||
this.__proto__ = new (BasePlatform as any)();
|
||||
|
||||
var video, audio;
|
||||
var poller;
|
||||
var useAudio = false;
|
||||
var videoWidth = 292;
|
||||
var videoHeight = 256;
|
||||
@ -327,7 +328,7 @@ var VerilogPlatform = function(mainElement, options) {
|
||||
ctx.font = "8px TinyFont";
|
||||
ctx.fillStyle = "white";
|
||||
ctx.textAlign = "left";
|
||||
setKeyboardFromMap(video, switches, VERILOG_KEYCODE_MAP);
|
||||
poller = setKeyboardFromMap(video, switches, VERILOG_KEYCODE_MAP);
|
||||
var vcanvas = $(video.canvas);
|
||||
idata = video.getFrameData();
|
||||
timerCallback = () => {
|
||||
@ -376,6 +377,8 @@ var VerilogPlatform = function(mainElement, options) {
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: pollControls() { poller.poll(); }
|
||||
|
||||
resize() {
|
||||
if (this.waveview) this.waveview.recreate();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user