javascript gba游戏模拟器

        继续分享个没用的技术,现在ios上不越狱好像找不到能用的gba模拟器,最近偶然在GitHub上发现一个大牛用JavaScript写的gba模拟器,居然可以流畅运行大多数gba游戏,像口袋妖怪,塞尔达啥的,只是只能用电脑键盘操作,存档功能也没法用,于是克隆了一份代码简单修改了下,把keydown和keyup事件改成了手机上的touchstart和touchend,覆盖了window.event,改成自定义的keycode,这样就可以在手机上用button模拟gba键盘了,存档部分也做了简单的修改,使其能导入和导出存档,还是先上效果图。

         这个项目在GitHub上的地址为GBA.js (endrift.github.io),粗略的看了下,原作者用JavaScript模拟了一个gba掌机cpu和内存,在上面运行thumb指令集,thumb指令集也是ARM指令集的一种,所有的显存操作,键盘事件的监听都由虚拟的cpu执行,看了下默认的时钟是16毫秒,也就是每16毫秒刷新一下屏幕显示和声音以及键盘的监听。

this.throttle = 16; // This is rough, but the 2/3ms difference gives us a good overhead

    var self = this;
    window.queueFrame = function (f) {
        self.queue = window.setTimeout(f, self.throttle);
    };

        可以看到原作者介绍这个cpu帧率16ms就足够了,要是硬件足够好可以调整到2或者3毫秒。

       下面这段代码就是键盘的监听,模拟gba键盘的按下和弹起,可以看到这个this.currentDown就是gba键盘键值,虚拟的cpu会不断地监听它的变化,然后执行相应的指令。

function GameBoyAdvanceKeypad() {
	this.KEYCODE_LEFT = 37;
	this.KEYCODE_UP = 38;
	this.KEYCODE_RIGHT = 39;
	this.KEYCODE_DOWN = 40;
	this.KEYCODE_START = 13;
	this.KEYCODE_SELECT = 220;
	this.KEYCODE_A = 90;
	this.KEYCODE_B = 88;
	this.KEYCODE_L = 65;
	this.KEYCODE_R = 83;

	this.GAMEPAD_LEFT = 14;
	this.GAMEPAD_UP = 12;
	this.GAMEPAD_RIGHT = 15;
	this.GAMEPAD_DOWN = 13;
	this.GAMEPAD_START = 9;
	this.GAMEPAD_SELECT = 8;
	this.GAMEPAD_A = 1;
	this.GAMEPAD_B = 0;
	this.GAMEPAD_L = 4;
	this.GAMEPAD_R = 5;
	this.GAMEPAD_THRESHOLD = 0.2;

	this.A = 0;
	this.B = 1;
	this.SELECT = 2;
	this.START = 3;
	this.RIGHT = 4;
	this.LEFT = 5;
	this.UP = 6;
	this.DOWN = 7;
	this.R = 8;
	this.L = 9;

	this.currentDown = 0x03FF;
	this.eatInput = false;

	this.gamepads = [];
	this.keyboardHandler = function(e) {
	var toggle = 0;
	switch (e.keyCode) {
	case this.KEYCODE_START:
		toggle = this.START;
		break;
	case this.KEYCODE_SELECT:
		toggle = this.SELECT;
		break;
	case this.KEYCODE_A:
		toggle = this.A;
		break;
	case this.KEYCODE_B:
		toggle = this.B;
		break;
	case this.KEYCODE_L:
		toggle = this.L;
		break;
	case this.KEYCODE_R:
		toggle = this.R;
		break;
	case this.KEYCODE_UP:
		toggle = this.UP;
		break;
	case this.KEYCODE_RIGHT:
		toggle = this.RIGHT;
		break;
	case this.KEYCODE_DOWN:
		toggle = this.DOWN;
		break;
	case this.KEYCODE_LEFT:
		toggle = this.LEFT;
		break;
	default:
		return;
	}
	toggle = 1 << toggle;
	if (e.type == "keydown") {
		this.currentDown &= ~toggle;
	} else {
		this.currentDown |= toggle;
	}
	if (this.eatInput) {
	 	window.event.preventDefault();
	}
};

}
	
GameBoyAdvanceKeypad.prototype.gamepadHandler = function(gamepad) {
	var value = 0;
	if (gamepad.buttons[this.GAMEPAD_LEFT] > this.GAMEPAD_THRESHOLD) {
		value |= 1 << this.LEFT;
	}
	if (gamepad.buttons[this.GAMEPAD_UP] > this.GAMEPAD_THRESHOLD) {
		value |= 1 << this.UP;
	}
	if (gamepad.buttons[this.GAMEPAD_RIGHT] > this.GAMEPAD_THRESHOLD) {
		value |= 1 << this.RIGHT;
	}
	if (gamepad.buttons[this.GAMEPAD_DOWN] > this.GAMEPAD_THRESHOLD) {
		value |= 1 << this.DOWN;
	}
	if (gamepad.buttons[this.GAMEPAD_START] > this.GAMEPAD_THRESHOLD) {
		value |= 1 << this.START;
	}
	if (gamepad.buttons[this.GAMEPAD_SELECT] > this.GAMEPAD_THRESHOLD) {
		value |= 1 << this.SELECT;
	}
	if (gamepad.buttons[this.GAMEPAD_A] > this.GAMEPAD_THRESHOLD) {
		value |= 1 << this.A;
	}
	if (gamepad.buttons[this.GAMEPAD_B] > this.GAMEPAD_THRESHOLD) {
		value |= 1 << this.B;
	}
	if (gamepad.buttons[this.GAMEPAD_L] > this.GAMEPAD_THRESHOLD) {
		value |= 1 << this.L;
	}
	if (gamepad.buttons[this.GAMEPAD_R] > this.GAMEPAD_THRESHOLD) {
		value |= 1 << this.R;
	}

	this.currentDown = ~value & 0x3FF;
};

GameBoyAdvanceKeypad.prototype.gamepadConnectHandler = function(gamepad) {
	this.gamepads.push(gamepad);
};

GameBoyAdvanceKeypad.prototype.gamepadDisconnectHandler = function(gamepad) {
	this.gamepads = self.gamepads.filter(function(other) { return other != gamepad });
};

GameBoyAdvanceKeypad.prototype.pollGamepads = function() {
	var navigatorList = [];
	if (navigator.webkitGetGamepads) {
		navigatorList = navigator.webkitGetGamepads();
	} else if (navigator.getGamepads) {
		navigatorList = navigator.getGamepads();
	}

	// Let's all give a shout out to Chrome for making us get the gamepads EVERY FRAME
	if (navigatorList.length) {
		this.gamepads = [];
	}
	for (var i = 0; i < navigatorList.length; ++i) {
		if (navigatorList[i]) {
			this.gamepads.push(navigatorList[i]);
		}
	}
	if (this.gamepads.length > 0) {
		this.gamepadHandler(this.gamepads[0]);
	}

};

GameBoyAdvanceKeypad.prototype.registerHandlers = function() {
	//window.addEventListener("keydown", this.keyboardHandler.bind(this), true);
	//window.addEventListener("keyup", this.keyboardHandler.bind(this), true);

	//window.addEventListener("gamepadconnected", this.gamepadConnectHandler.bind(this), true);
	//window.addEventListener("mozgamepadconnected", this.gamepadConnectHandler.bind(this), true);
	//window.addEventListener("webkitgamepadconnected", this.gamepadConnectHandler.bind(this), true);

	//window.addEventListener("gamepaddisconnected", this.gamepadDisconnectHandler.bind(this), true);
	//window.addEventListener("mozgamepaddisconnected", this.gamepadDisconnectHandler.bind(this), true);
	//window.addEventListener("webkitgamepaddisconnected", this.gamepadDisconnectHandler.bind(this), true);
};

        因为我要把这个模拟器移植到手机端,所以注释掉了所有的键盘监听事件,然后把window.event这个参数构造成了自定义的对象,把按键改成了button,然后就可以这样调用<button ontouchstart="gba.keypad.keyboardHandler({keyCode:65,type:'keydown'})"  ontouchend="gba.keypad.keyboardHandler({keyCode:65,type:'keyup'})">L</button>,这个就是L按键,其他的类似。

然后就是存档的导入,我们可以在mmu.js找到这个方法,如下:

 this.save.replaceData(save);,这个方法就可以把读取到的存档数据加载到内存中,但是直接运行会发现报错,this.save为null,继续跟踪会发现这个this.save对象是先加载完游戏镜像文件之后才会创建的,如下:

        这样我们只需要把读取存档的操作修改为加载游戏镜像之后 就可以了。

        最后来一张GitHub上的版本截图

        看完原版代码,不得不佩服原作者的知识水平,一定是硬件和软件都精通的人写的,尤其是那些cpu指令集的模拟,突然想起之前看新闻说国外有人用JavaScript写了个虚拟机跑linux系统,之前还当作笑话,现在看来也不是没有可能,等找到相关代码研究下会继续和大家分享。 

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值