avatar


一些关于游戏整合的方案

整合的游戏有牧场物语口袋妖怪上古神器等,具体参考《一些关于游戏整合的例子》

Ruffle

运行Flash

运行Flash所利用的是Ruffle
(Flash虽然已经于2020年12月31日结束了,但是Ruffle可以继续运行Flash。)

官网:https://ruffle.rs
GitHub:https://github.com/ruffle-rs/ruffle

通过官网的介绍,我们知道,Ruffle可以通过应用程序、浏览器插件、JavaScript等多种方式运行Flash,我利用的是其JavaScript方式。

在官网有一个非常清晰的DEMO:

1
2
3
4
5
6
7
8
9
10
11
<script>
window.RufflePlayer = window.RufflePlayer || {};
window.addEventListener("load", (event) => {
const ruffle = window.RufflePlayer.newest();
const player = ruffle.createPlayer();
const container = document.getElementById("container");
container.appendChild(player);
player.load("movie.swf");
});
</script>
<script src="path/to/ruffle/ruffle.js"></script>

window.addEventListener('load', function () {});window.onload = function () {};

官网提供的DEMO,利用的是window.addEventListener('load', function () {});,而不是window.onload = function () {};,关于这两者的区别,我们在《基于JavaScript的前端开发入门:2.DOM和BOM》讨论过。

window.onload注册事件的只能写一次,如果有多个,会以最后一个window.onload为准;addEventListener没有限制。

DOMContentLoadedload

DOMContentLoaded:浏览器已完全加载HTML,并构建了DOM树,但像<img>和样式表之类的外部资源可能尚未加载完成。
load:浏览器不仅加载完成了HTML,还加载完成了所有外部资源:图片,样式等。

我们通过一个具体的例子,讨论其区别。
DOMContentLoaded中,获取<img>标签中图片的尺寸,可能是0,因为图片可能未加载完成,示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
<script>
function ready() {
alert('DOM is ready');

// 图片目前尚未加载完成(除非已经被缓存),所以图片的大小为 0x0
alert(`Image size: ${img.offsetWidth}x${img.offsetHeight}`);
}

document.addEventListener("DOMContentLoaded", ready);
</script>

<img id="img" src="https://en.js.cx/clipart/train.gif?speed=1&cache=0">

为什么要在绑定DOMContentLoadedload事件呢?

不绑定直接写在<script>标签中可以吗?
一般情况下,<script>标签中的内容,会在构建DOM之前运行。
(浏览器这么设计,是因为有些<script>标签中的内容会修改DOM,甚至对其执行document.write操作。)

而且,我们有document.getElementById("container")这段代码,所以我们需要在构建DOM之后执行。
所以我们绑定了DOMContentLoadedload事件。

例如,如下的代码,一定是先弹出Library loaded,再弹出DOM ready

1
2
3
4
5
6
7
<script>
document.addEventListener("DOMContentLoaded", () => {
alert("DOM ready");
});

alert("Library loaded");
</script>

Flash下载地址

分享两个链接:

这里有不少高质量的Flash游戏。

进度条

进度条利用的是LayUI。

官网:https://layui.github.io
GitHub:https://github.com/layui/layui

关于LayUI中进度条的操作方法,参考:https://layui.gitee.io/v2/docs/element/progress.html
本文不赘述,主要讨论具体应用。

一般进度条都是"假的",并不完全真实反应加载进度(部分关键节点会是准确的),更多只是出于用户友好的一种设计。在这里也是。

定义进度条:

1
2
3
4
5
6
7
<div id="progressBarId">
<div style="text-align: center;">加载中</div>
<div class="layui-progress layui-progress-big" lay-showpercent="true" lay-filter="progressBar">
<div class="layui-progress-bar layui-bg-red" lay-percent="0%"></div>
</div>
<br/>
</div>

定时任务,每隔200毫秒,进度条随机加一些,但最多到40%:

1
2
3
4
5
6
7
8
9
10
function checkLoad(){
if (per < 40 && player_tfqy.readyState == 0 && player_wlwz.readyState == 0){
per = per + Math.ceil(Math.random() * 3);
element.progress('progressBar', per + '%');
}else{
clearInterval(checkLoadInterval)
}
}

checkLoadInterval = self.setInterval(checkLoad,200)

这里利用的随机数方法是Math.ceil(Math.random() * 3)
《基于JavaScript的前端开发入门:1.基础语法》,我们讨论过,有三种随机数方法

  • Math.ceil(Math.random()*100);,向上取整,那么取到0的概率极小。
  • Math.floor(Math.random()*100);,可均衡获取0到99的随机整数。
  • Math.round(Math.random()*100);,基本均衡获取0到100的随机整数,但获取最小值00和最大值100100的几率少一半,因为头尾的分布区间只有其他数字的一半。

加载完成之后,隐去进度条。

1
2
3
4
5
6
7
8
9
10
11
12
player_tfqy.load("/-/9/04/01/tfqy.swf").then(() => {
per = per + 50;
if (per >= 100){
element.progress('progressBar', 100 + '%');
layer.msg('加载完成')
document.getElementById("progressBarId").setAttribute("style","display:none");
}else{
element.progress('progressBar', per + '%');
}
}).catch((e) => {
layer.msg(`Ruffle failed to load the file: ${e}`);
});

如上述代码,每加载完成一个,进度加50,如果进度大于等于100,表示加载完成。
根据我们上文的讨论,定时任务最多将进度条加到40,完成一个最多是90,所以要大于等于100,一定是两个都加载好了。而且如果两个都加载好了,一般 都会大于等于100。

为什么说一般? 因为存在一个线程安全的问题。
我们是两个Flash文件同时加载,而且在加载过程中,有共享的变量per
比如,其中一个读取了per,这时候per是20,加上50,等于70,但变量还没写进去。第二个又读取了perper是20,加上50,等于70。然后第一个和第二个都把结果写进去,per等于70。两个文件都加载完成了,确只有70。

解决方法,可以顺序执行。
本人JavaScript水平有限,根据一些资料,JavaScript是单线程,这个也可能是多虑了。

切换暂停

我们看到,在该页面存在两个小游戏。现在需要的是,切换到一个的时候,将另一个暂停。

切换暂停

绑定click事件即可。

1
2
3
4
5
document.getElementsByClassName('tab')[0].addEventListener("click", () => {
if(player_wlwz.isPlaying){
player_wlwz.pause();
}
})

虚拟键盘

两种方案

《上古神器(基于Ruffle)》,有如下的叙述:

《上古神器》一共有4部。前3部是通过键盘 W A S D 等来操作,暂时只支持PC和Mac,不支持移动设备。

但是在《牧场物语(基于IodineGBA)》《口袋妖怪(基于IodineGBA)》,又有针对移动设备的虚拟键盘。

虚拟键盘的方案有两种:

  1. 点击虚拟键盘后,手动调用键盘的keydownkeyup等事件对应的方法。
    例如,键盘的keydown,对应方法funcTest(),那么就在点击虚拟键盘后,手动调用用funcTest()方法。
  2. 点击虚拟键盘后,手动注入键盘的keydownkeyup等事件。

第一种方案

《牧场物语(基于IodineGBA)》《口袋妖怪(基于IodineGBA)》,采用的就是第一种方案。

如果我们看JSNES的源码,会发现也是这种方法。在js/ui.js有代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
$('#joystick_btn_A').bind('touchstart', function(e) {
self.nes.keyboard.keyDown({
keyCode: 75
});
e.preventDefault();
});

$('#joystick_btn_B').bind('touchend', function(e) {
self.nes.keyboard.keyUp({
keyCode: 76
});
e.preventDefault();
});
  • 因为修改过JSNES的键盘映射关系,所以具体的keyCode的值和原始的不一样。

基于Ruffle,第一种方案,以现在的能力不现实。Ruffle是用Rust这门语言写的,完全不了解,而且源码确实看不太明白。

第二种方案

至于第二种方案,手动注入键盘的keydownkeyup等,注入确实是成功了,但是游戏没有对应的响应,原因待确认。

记录一下当时的一些方法。

注入函数:

1
2
3
4
5
6
function fireKeyEvent(element, evtType, keyChar) {
element.focus();
var KeyboardEventInit = {key:keyChar, code:"", location:0, repeat:false, isComposing:false};
var evtObj = new KeyboardEvent(evtType, KeyboardEventInit);
element.dispatchEvent(evtObj);
}

调用示例:

1
fireKeyEvent(document,"keydown","d");

监听方法:

1
2
3
document.onkeydown=function(e){
// 对整个页面监听
}
  • 某个键按下事件:onkeydown
  • 某个键被按下或者按住:onkeypress
  • 某个按下的键被松开:onkeyup

可能的原因:isTrusted: false

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<html>
<body>
<script>
// 注入方法
function fireKeyEvent(element, evtType, keyChar) {
element.focus();
var KeyboardEventInit = {key:keyChar, code:"", location:0, repeat:false, isComposing:false};
var evtObj = new KeyboardEvent(evtType, KeyboardEventInit);
element.dispatchEvent(evtObj);
}
// 监听
window.onkeydown=function(e){
console.log(e)
}
</script>
<button onclick="fireKeyEvent(window,'keydown','d');">click2Keydown</button>
</body>
</html>

isTrusted: false

我们敲键盘,与点击那个Button,都能监听到键盘事件,说明注入是成功了。但是通过点击Button注入的事件,isTrusted: false

JSWQX

iframe

该部分其实很简单。

其"更好的实现",虽然没有开源,但是通过浏览器的F12功能,进行分析之后,完全可以把项目完整的复制下来。
进行简单的修改后,即可以进行部署,本文采取的集成到Hexo博客中进行部署,所以需要在Hexo的配置文件skip_render配置上wqx/*(注意,前面没有/),表示不进行渲染。

然后通过iframe标签,嵌入到博客的网页中。

1
2
3
4
5
<div style="text-align:center">
<iframe src="/wqx/index.html" class="div-iframe" frameborder="0">
<p>加载失败</p>
</iframe>
</div>
  • style="text-align:center",用于控制居中。
  • frameborder="0",表示不要边框。

缩放策略

因为JSWQX的宽度是400px,但是在很多移动设备上,最大宽度没有400px,所以需要进行自适应的缩放。

主页面:

1
2
3
4
5
6
7
8
9
10
11
.div-iframe{
width: 600px;
height: 600px
}

@media screen and (max-width:900px) {
.div-iframe{
width: 340px;
height: 340px
}
}
  • 默认的宽和高是600px,但是当宽度小于900px时,采取340px的宽和高。

子页面

1
2
3
4
5
6
7
8
9
.zoom-body{
zoom: 1.5
}

@media screen and (max-width:400px) {
.zoom-body{
zoom: 0.85
}
}
  • 直接在body标签上配置class="zoom-body"
  • 子页面判断max-width依据是iframe的width,在这里即div-iframewidth

模拟器的原理

如果我们看JSWQX的源码,会看到这么一部分。

CPU

文件部分内容如下:

1
2
3
4
5
6
7
8
9
10
function M65C02Context() {
this.ram = null;
this.memmap = null;
this.io_read_map = null;
this.io_write_map = null;
this.io_read = null;
this.io_write = null;
this.cycles = 0;

......

根据文件名、提交注释、文件内容,以及在我向大神请教后,确认这是一个CPU。

这就是很多模拟器的原理,通过编程语言,模拟出CPU、内存等硬件,然后把ROM放在上面跑。

题外话,通过JavaScript,在浏览器上模拟出了一个CPU,这是真的大神。

JSNES

同JSWQX,JSNES,也是基于开源项目。

我只是在其基础上进行了一些简单的修改:

因为样式方面,同时适配移动设备横屏、移动设备竖屏以及PC(Mac)的难度较大,索性禁用了移动设备横屏。

具体是如果发现是移动设备横屏,提示请竖屏。

提示请竖屏

实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<script>
(function rotate(){
var orientation=window.orientation;
var pd = null;
function createPd(){
if(document.getElementById('preventTran') === null){
var imgData = 'data:image/png;base64,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';
pd = document.createElement('div');
pd.setAttribute('id','preventTran');
pd.style.position = 'fixed';
pd.style.left = '0';
pd.style.top = '0';
pd.style.width = '100%';
pd.style.height = '100%';
pd.style.overflow = 'hidden';
pd.style.backgroundColor = '#2e2e2e';
pd.style.textAlign = 'center';
pd.style.zIndex = '99999';
document.getElementsByTagName('body')[0].appendChild(pd);
var img = document.createElement('img');
img.src = imgData;
pd.appendChild(img);
img.style.margin = '60px auto 30px'
var br = document.createElement('br');
var p = document.createElement('p');
p.style.width = '100%';
p.style.height = 'auto';
p.style.fontSize = '22px';
p.style.color = '#626262';
p.style.lineHeight = '34px';
p.style.textAlign = 'center';
p.innerHTML = '为了您的良好体验';
p.appendChild(br);
p.innerHTML += '请将手机竖屏操作';
pd.appendChild(p);
}
}
if(orientation==90||orientation==-90){
if(pd == null && document.getElementById('preventTran') === null) createPd();
document.getElementById('preventTran').style.display = 'block';
}
window.onorientationchange=function(){
if(pd == null && document.getElementById('preventTran') == null) createPd();
document.getElementById('preventTran').style.display='none';
rotate();
};
})();
</script>

IodineGBA

基于开源项目:https://github.com/Browncha023/GBA
我只是在原项目上进行了一些简单的修改,同样也是通过iframe标签嵌入。

开启声音

根据浏览器的最新规则,声音需要手动开启。

The AudioContext was not allowed to start. It must be resumed (or created) after a user gesture on the page. https://goo.gl/7K7WLu

规则所附的链接,提供了几个方法,其中一个方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
// Existing code unchanged.
window.onload = function() {
var context = new AudioContext();
// Setup all nodes
// ...
}

// One-liner to resume playback when user interacted with the page.
document.querySelector('button').addEventListener('click', function() {
context.resume().then(() => {
console.log('Playback resumed successfully');
});
});

本文采取的就是这种方法。

根据Chrome浏览器提示框的堆栈信息,能找到AudioContext是由哪个变量所定义的,然后可以在相关事件的方法上,调用其resume方法。

提示堆栈信息

另附,开启和关闭声音的方法:

1
2
3
4
5
6
7
function XAudioJSWebAudioContextHandleSwitch(){
if(XAudioJSWebAudioContextHandle.state === 'running') {
XAudioJSWebAudioContextHandle.suspend();
} else if(XAudioJSWebAudioContextHandle.state === 'suspended') {
XAudioJSWebAudioContextHandle.resume();
}
}

iframe实时传值

因为我们的GBA模拟器实际上是通过iframe标签嵌入到我们的博客页面的,如果在博客页面敲键盘,即GBA模拟器没有获得焦点,这时候GBA模拟器是不会有响应的。
解决方法为:

  • iframe实时传值,利用的是window对象下的postMessage方法。
  • GBA模拟器页面在收到值后,手动调用原本键盘事件对应的方法。

我们主要讨论iframe实时传值。
假设现在有两个页面,iframePage.htmlindex.html的子页面.

1
2
3
4
5
<!-- index.html -->
<body style="border:5px solid #333;">
<h1>this is index</h1>
<iframe src="./iframePage.html" id='myframe'></iframe>
</body>
1
2
3
4
<!-- iframePage -->
<body style="border:5px solid #333;">
<h1>this is iframePage</h1>
</body>

我们以父页面向子页面传值为例。

发送消息

1
2
3
4
// 获取iframe元素
iFrame = document.getElementById('myframe')
// iframe加载完后再发送消息,否则子页面接收不到message
iFrame.contentWindow.postMessage('MessageFromIndex1','*');}
  • postMessage是挂载在window对象上的,所以在iframe加载完毕后,用iFrame.contentWindow获取到iframewindow对象,然后调用postMessage方法,相当于给子页面发送了一条消息。
  • postMessage方法第一个参数是要发送的数据,可以是任何原始类型的数据。
  • postMessage方法第二个参数可以设置要发送到哪个url

接受消息

我们只需要在子页面监听message事件,并且设置好回调函数即可。

1
2
3
4
5
6
7
//回调函数
function receiveMessageFromIndex ( event ) {
console.log('receiveMessageFromIndex', event )
}

//监听message事件
window.addEventListener("message", receiveMessageFromIndex, false);

补充

上文的例子是父页面给子页面发送消息,子页面给父页面发送消息的方法类似。

子页面利用window.parent获取父页面对象。

1
window.parent.postMessage( {msg: 'MessageFromIframePage'}, '*');

postMessage的官方文档:https://developer.mozilla.org/zh-CN/docs/Web/API/Window/postMessage

虚拟键盘

虚拟键盘基于weel-keypad实现的。

官网:https://keypad.weel.cool
GitHub:https://github.com/wallenweel/weel-keypad

关于其操作方法,本文不赘述,主要讨论具体应用,关键代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
let keyMap = {
W:87,
O:79,
P:90,
A:65,
S:83,
D:68,
K:75,
L:76,
Enter:13,
Z:90,
X:88,
C:67,
N:78,
M:77,
Shift:16
}

let kypd = new Keypad({
name: 'wasd', // 使用新增布局的名字
onstart: function (key) {
let keyName = key[1] // 按键命令
if(keyMap[keyName]){
let keyCode = keyMap[keyName];
gbaEle.contentWindow.postMessage('onkeydown-' + keyCode,'*');
}
},
onend: function (key) {
let keyName = key[1] // 按键命令
if(keyMap[keyName]){
let keyCode = keyMap[keyName];
gbaEle.contentWindow.postMessage('onkeyup-' + keyCode,'*');
}
}
}, {
// 新增的键盘布局
wasd: [
[[' '], ['W'], [' '], ['O'], ['P'], [' ']],
[['A'], ['S'], ['D'], ['K'], ['L'], ['Enter']],
[['Z'], ['X'], ['C'], ['N'], ['M'], ['Shift']]
]
})
  • gbaEle.contentWindow.postMessage就是我们上文讨论的iframe实时传值。



我简单看了一下IodineGBA的加速部分的代码,发现和《离线异构数据同步工具DataX:2.源码概览》的思路很相近,都是控制单位时间处理的数据。

文章作者: Kaka Wan Yifan
文章链接: https://kakawanyifan.com/90499
版权声明: 本博客所有文章版权为文章作者所有,未经书面许可,任何机构和个人不得以任何形式转载、摘编或复制。

评论区