前言

移动端浏览器是没有实体键盘的,想要操作游戏就必须为其设置虚拟按键,通过虚拟按键(按钮)的标识与实体键盘的keyCode进行绑定,来达到想要的效果。

这个随笔只封装NES游戏手柄右边的按键,不包含方向键。方向键的封装在另一个章节。

1.按键UI

NES游戏手柄分为6个键,如图:

连发按键这里不做,我看到很多第三方网页模拟器也是没有的。不过虚拟键盘的A+B同按是很困难的,这里会为其增加一个宏按键。设计图如下:

封装的初衷在于用户只需要自己配置好外层容器的大小和id,其他的事交给插件去做。所以插件要自动适应容器大小,而且能动态添加dom元素到目标容器中。

虚拟按键设计思路:

(1)最外层的容器宽高100%,使用flex布局,它的子元素就是虚拟按键

(2)子元素分为3行,每个按键的宽度以百分比计算。

html容器结构:

<body>
<!-- user_btn_box 为用户设置的容器-->
<div id="user_btn_box">
<!-- nes_btn_box 为插件添加的容器,配合js动态添加-->
<div class="nes_btn_box">
<span class="btn btn-select">SELECT</span>
<span class="btn btn-start">START</span>
<span class="btn btn-ab">B + A</span>
<span class="btn btn-b">B</span>
<span class="btn btn-a">A</span>
</div>
</div>
</body>

样式文件:

.nes_btn_box {
width:100%;
height:100%;
display: flex;
flex-wrap: wrap;
justify-content: space-around;
align-content: space-around;
-webkit-tap-highlight-color: transparent;
} .nes_btn_box .btn {
height:30px;
line-height: 30px;
text-align: center;
background-color: lightcoral;
color: lightcyan;
cursor: pointer;
border-radius: 5px;
}
/* 点击时按钮的背景色 */
.nes_btn_box .btn.isTouch {
background-color: linen;
} .nes_btn_box .btn-select,
.nes_btn_box .btn-start {
width:40%;
}
.nes_btn_box .btn-ab {
width:80%;
height:60px;
line-height: 60px;
} .nes_btn_box .btn-a,
.nes_btn_box .btn-b {
width:40%;
height:60px;
line-height: 60px;
}

2.动态添加虚拟按键

插件其实就是一个构造函数,为其设置init方法,接收一系列参数来完成所需要的功能

(1)创建构造函数,接收参数

(2)为构造函数设置init方法,传入接收的参数

(3)通过传入的参数,创建dom元素,传入对应的容器中

插件代码:

function VirtualNesBtn(opt){
//接收容器的标识
this.el = opt.el //生成5个带随机数后缀的id
this.id_arr = []
this.id_arr.push('btn_select_' + Math.floor(Math.random() * 100000))
this.id_arr.push('btn_start_' + Math.floor(Math.random() * 100000))
this.id_arr.push('btn_ab_' + Math.floor(Math.random() * 100000))
this.id_arr.push('btn_b_' + Math.floor(Math.random() * 100000))
this.id_arr.push('btn_a_' + Math.floor(Math.random() * 100000)) //设定5个按钮的文本名称
this.name_arr = ['SELECT', 'START', 'B + A', 'B', 'A']
} //初始化 创建虚拟按钮
VirtualNesBtn.prototype.init = function(opt){
var me = this
//创建5个按键
var btn_select = document.createElement('span')
var btn_start = document.createElement('span')
var btn_ab = document.createElement('span')
var btn_b = document.createElement('span')
var btn_a = document.createElement('span') //为5个按键设置css类名
btn_select.classList.add('btn','btn-select')
btn_start.classList.add('btn','btn-start')
btn_ab.classList.add('btn','btn-ab')
btn_b.classList.add('btn','btn-b')
btn_a.classList.add('btn','btn-a') //为5个按键设置id
btn_select.id = me.id_arr[0]
btn_start.id = me.id_arr[1]
btn_ab.id = me.id_arr[2]
btn_b.id = me.id_arr[3]
btn_a.id = me.id_arr[04] //为5个按键设置 文本
btn_select.innerText = me.name_arr[0]
btn_start.innerText = me.name_arr[1]
btn_ab.innerText = me.name_arr[2]
btn_b.innerText = me.name_arr[3]
btn_a.innerText = me.name_arr[4] //创建容器,并将5个按钮插入其中
var nes_btn_box = document.createElement('div')
nes_btn_box.classList.add('nes_btn_box')
nes_btn_box.appendChild(btn_select)
nes_btn_box.appendChild(btn_start)
nes_btn_box.appendChild(btn_ab)
nes_btn_box.appendChild(btn_b)
nes_btn_box.appendChild(btn_a) //插入到目标容器中
var target = document.querySelector(me.el)
target.appendChild(nes_btn_box) //为按钮设置点击高亮效果
me.set_tap_highlight()
} //监听点击 设置点击高亮效果
VirtualNesBtn.prototype.set_tap_highlight = function(){
var me = this
var box = document.querySelector(me.el) box.addEventListener('touchstart',(evt) => {
//阻止默认事件,防止快速点击时页面放大
evt.preventDefault()
//判断点击的目标元素是否是虚拟按钮之一
if(me.id_arr.includes(evt.target.id)){
evt.target.classList.add('isTouch')
}
}) box.addEventListener('touchend',(evt) => {
//判断点击的目标元素是否是虚拟按钮之一
if(me.id_arr.includes(evt.target.id)){
evt.target.classList.remove('isTouch')
}
})
}

3.将虚拟按钮的touch事件绑定到实体键盘事件中

游戏模拟器本身是通过监听键盘事件,在回调函数中判断事件对象的keyCode属性来判断用户按下的是哪个按键,虚拟按钮的touch事件对象中没有keyCode属性,我们可以手动添加这个属性,供回调函数使用。

注意:宏按键(B+A)要单独处理

模拟器相关代码如图:

前面的点击高亮的函数已经监听了touch事件,这里也需要监听touch事件,为了代码复用,要对刚才的代码进行优化。

创建实例时,接收4个属性,分别是:

el:容器的标识(必须),例如 '#user_box'

btn_down_fn:虚拟按钮按下时的回调(必须)

btn_ip_fn:虚拟按钮释放时的回调(必须)

keyCodes:[] 按顺序分别是 select start b a 对应的keyCode (可选)

对元素进行touch监听时,通过evt.target.id可以获取目标元素的id,为了方便根据id获得其绑定的keycode,封装一个方法。

获取keyCode后,进行一系列判断,并调用相关回调。

function VirtualNesBtn(opt){
//接收容器的标识
this.el = opt.el
//接收回调
this.btn_down_fn = opt.btn_down_fn //fn
this.btn_up_fn = opt.btn_up_fn //fn //保存按钮信息
this.btns_info = [] //为5个按钮添加带数字后缀的 id
this.btns_info = [{},{},{},{},{}]
this.btns_info[0].id = 'btn_select_' + Math.floor(Math.random() * 100000)
this.btns_info[1].id = 'btn_start_' + Math.floor(Math.random() * 100000)
this.btns_info[2].id = 'btn_ab_' + Math.floor(Math.random() * 100000)
this.btns_info[3].id = 'btn_b_' + Math.floor(Math.random() * 100000)
this.btns_info[4].id = 'btn_a_' + Math.floor(Math.random() * 100000) //设定5个按钮的文本名称 name
this.btns_info[0].name = 'SELECT'
this.btns_info[1].name = 'START'
this.btns_info[2].name = 'B + A'
this.btns_info[3].name = 'B'
this.btns_info[4].name = 'A' //配置5个按钮的 keycode 默认为 空格 回车 J K
this.btns_info[0].keyCode = opt.keyCodes && opt.keyCodes[0] || 32
this.btns_info[1].keyCode = opt.keyCodes && opt.keyCodes[1] || 13
this.btns_info[2].keyCode = 'macro_key'
this.btns_info[3].keyCode = opt.keyCodes && opt.keyCodes[2] || 74
this.btns_info[4].keyCode = opt.keyCodes && opt.keyCodes[3] || 75 } //初始化 创建虚拟按钮
VirtualNesBtn.prototype.init = function(opt){
var me = this
//创建5个按键
var btn_select = document.createElement('span')
var btn_start = document.createElement('span')
var btn_ab = document.createElement('span')
var btn_b = document.createElement('span')
var btn_a = document.createElement('span') //为5个按键设置css类名
btn_select.classList.add('btn','btn-select')
btn_start.classList.add('btn','btn-start')
btn_ab.classList.add('btn','btn-ab')
btn_b.classList.add('btn','btn-b')
btn_a.classList.add('btn','btn-a') //为5个按键设置id
btn_select.id = me.btns_info[0].id
btn_start.id = me.btns_info[1].id
btn_ab.id = me.btns_info[2].id
btn_b.id = me.btns_info[3].id
btn_a.id = me.btns_info[4].id //为5个按键设置 文本
btn_select.innerText = me.btns_info[0].name
btn_start.innerText = me.btns_info[1].name
btn_ab.innerText = me.btns_info[2].name
btn_b.innerText = me.btns_info[3].name
btn_a.innerText = me.btns_info[4].name //创建容器,并将5个按钮插入其中
var nes_btn_box = document.createElement('div')
nes_btn_box.classList.add('nes_btn_box')
nes_btn_box.appendChild(btn_select)
nes_btn_box.appendChild(btn_start)
nes_btn_box.appendChild(btn_ab)
nes_btn_box.appendChild(btn_b)
nes_btn_box.appendChild(btn_a) //插入到目标容器中
var target = document.querySelector(me.el)
target.appendChild(nes_btn_box) //设置touch事件监听
target.addEventListener('touchstart',(evt) => {
//阻止默认事件,防止快速点击时页面放大
evt.preventDefault() //判断点中的是否是虚拟按钮
var is_nes_btn = me.btns_info.some(function(item){
return item.id === evt.target.id
}) if(is_nes_btn){
//添加高亮
evt.target.classList.add('isTouch')
//处理此次点击
me.handleBtn(evt,'btn_down')
}
}) target.addEventListener('touchend',(evt) => {
//判断点中的是否是虚拟按钮
var is_nes_btn = me.btns_info.some(function(item){
return item.id === evt.target.id
}) if(is_nes_btn){
//移除高亮
evt.target.classList.remove('isTouch')
//处理此次点击
me.handleBtn(evt,'btn_up')
}
})
} //对虚拟按键的id进行判断,返回要绑定的 keyCode
VirtualNesBtn.prototype.getCode = function(id){
var me = this
//1.根据id查到按钮信息在数组中的下标
var index = me.btns_info.findIndex(function(item){
return item.id === id
})
//2.根据下标找到对应的keyCode
return me.btns_info[index].keyCode
} //对按键进行处理
VirtualNesBtn.prototype.handleBtn = function(evt,type){
var me = this
//1.找到keycode
var keyCode = me.getCode(evt.target.id)
//2.根据keycode判是否是宏按键
if(keyCode === 'macro_key'){
//要触发2个按键
var evt_tem = {}
var evt_tem2 = {}
evt_tem.keyCode = me.btns_info[3].keyCode
evt_tem2.keyCode = me.btns_info[4].keyCode
if(type === 'btn_down'){
me.btn_down_fn && me.btn_down_fn(evt_tem)
me.btn_down_fn && me.btn_down_fn(evt_tem2)
}else{
me.btn_up_fn && me.btn_up_fn(evt_tem)
me.btn_up_fn && me.btn_up_fn(evt_tem2)
}
}else{
//添加keyCode属性
evt.keyCode = keyCode
//不是宏按键则执行相应的回调
if(type === 'btn_down'){
me.btn_down_fn && me.btn_down_fn(evt)
}else{
me.btn_up_fn && me.btn_up_fn(evt)
}
}
}

使用示例:

<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NES虚拟按键</title>
<!-- 引入插件的css文件 -->
<link rel="stylesheet" href="./css/nes_btn.css">
<style>
#user_btn_box{
width:250px;
height:250px;
margin: 20px auto;
border: 1px solid greenyellow;
}
</style>
</head>
<body>
<!-- user_btn_box 为用户设置的容器-->
<div id="user_btn_box"></div>
</body>
</html>
<!-- 引入插件 -->
<script src="./js/nes_btn.js"></script>
<script>
function keydown(evt){
console.log('keydown keyCode = ' + evt.keyCode)
}
function keyup(evt){
console.log('keyup keyCode = ' + evt.keyCode)
}
window.onload = function(){
//创建实例
var nesBtn = new VirtualNesBtn({
el:"#user_btn_box", //容器
btn_down_fn:keydown,//虚拟按钮按下时的回调 参数evt
btn_up_fn:keyup,//虚拟按钮弹起时的回调 参数evt
keyCodes:[32,13,74,75] //按顺序分别是 select start b a
}) //实例初始化
nesBtn.init()
}
</script>

执行效果:

移动端NES网页模拟器(1)的更多相关文章

  1. 使用Chrome开发者工具调试Android端内网页(微信,QQ,UC,App内嵌页等)

    使用Chrome开发者工具调试Android端内网页(微信,QQ,UC,App内嵌页等) 前言 移动端页面调试一直是好多朋友头疼的问题,iOS 由于其封闭的特性和整体较高的性能,整体适配相对好做,调试 ...

  2. 移动端 Web 网页调试技巧

    原文出处: 盛瀚钦 本文主要列举了调试本地网页.查看测试环境网页的各种方法,涵盖了PC.iPad.移动端的调试技巧. 本文的不足之处在于,小溪里暂时还没有找到调试位于微信中和安卓各国产浏览器上的网页. ...

  3. 借助FreeHttp为任意移动端web网页添加vConsole调试

        以下介绍在不用修改代码并发布项目的情况下,为我们日常使用的移动web应用(如手机web淘宝)添加vConsole调试工具的方法   vConsole介绍 vConsole是一个轻量.可拓展.针 ...

  4. Appium移动端自动化测试--搭建模拟器和真机测试环境

    详细介绍安装Android Studio及Android SDK.安装Appium Server. 文章目录如下 目录 文章目录如下 模拟器--安装Android Studio及Android SDK ...

  5. HTML5和CSS3 PC端静态网页琐碎知识点

    1.PC端为了兼容IE9以及IE9以下,尽量要使用float进行布局,兼容性好,一般不要用flex进行布局. 2.问起CSS选择器的分类,先说id选择器,类选择器,属性选择器,伪类选择器,伪元素选择器 ...

  6. C#搞个跨平台的桌面NES游戏模拟器

    支持Windows,Mac,Linux    NES模拟器内核源码来自 https://github.com/colinvella/EmuNes   他这边的源码功能很完善了的,支持视频录制,手柄,金 ...

  7. 【转载】小tips: PC端传统网页试试使用Zepto.js进行开发

    Zepto.js设计之初专为移动端,不对一些古董浏览器支持.所以,尺寸很小,压缩后20K多一点,但是,jQuery压缩后,3.*版本要80多K,1.*版本则要90多K,4倍差距. 由于每个页面都会使用 ...

  8. JS判断PC还是移动端打开网页

    最近在做移动端网站,也需兼容PC端.还没找到更好的方法,只能用javascr判断用户是在PC端打开还是移动端打开. JS判断 var isPC = function (){    var userAg ...

  9. 如何实用便捷的在本地真机调试WEB端HTML5网页

    先简单介绍两款常用但需要一定条件或限制的工具 1.如果你能FQ chrome在32版本后就自带了移动端调度工具,可以在Android直接联调,但唯一遗憾的是,在我大天朝要FQ后才能行的通,我自己试了后 ...

  10. 移动端调试 — chrome模拟器基础调试

    打开开发者工具,进入chrome调试状态,点击左上角的手机图标,进入手机模拟器调试状态. 模拟器支持操作: 切换设备类型,模拟网络环境,模拟bar,keyboard弹出状态,横屏状态,更改UserAg ...

随机推荐

  1. SimMTM: 用于掩码时间序列建模的简单预训练框架《SimMTM: A Simple Pre-Training Framework for Masked Time-Series Modeling》(预训练模型、时序表征学习、掩码建模、流行学习、近邻聚合、低级表示学习(掩码)、高级表示学习(对比)、segment-wise 和point- wise)

    今天是2024年7月3日10:15,写一篇1月7日就看过的论文,哈哈哈哈哈哈哈哈哈,突然想到这篇论文了. 论文:SimMTM: A Simple Pre-Training Framework for ...

  2. chatGPT能做职业规划?看完之后发现3年软测白做了!

    "每天都是重复.单调的工作,收入不理想,想跳槽无力,学习又没有动力和方向,不知道未来的发展在哪里,甚至想转行·····" 做测试久了,很多人都有诸如此类的疑惑,不想一直停留在测试需 ...

  3. Vue3——SVG 图标配置

    1. SVG 图标配置 安装 SVG 依赖插件 vite-plugin-svg-icons npm i vite-plugin-svg-icons -D npm install fast-glob - ...

  4. 基于RHEL 9 搭建 KVM 虚拟化环境

    一.准备工作 1. 检查硬件虚拟化支持 KVM 要求处理器支持硬件虚拟化技术:Intel VT-x(虚拟化技术扩展)或 AMD-V(虚拟化技术扩展). 检查方法: 使用以下命令检查 CPU 是否支持虚 ...

  5. 以后基于 Topass 的博客加密方法通告

    Topass 加密方法 以后会将部分未公开内容公开,请你通过此加密途径来破解密码 特别地,为了保证博客的浏览体验,我不会通过这种方法加密任何一种应该公开的文章 话说你们不妨猜猜用的什么算法

  6. P1438 无聊的数列 题解

    背景 看到题解都是差分,竟然还有建两颗线段树和二阶差分的大佬. 我感到不理解,很不理解. 题目正解 本题正解很明显就是:线段树 是的,你没有看错,就只有线段树. 很显然我们直接按照线段树板题写就可以了 ...

  7. 【翻译】实现 Blocked Floyd-Warshall 用于解决所有对最短路径问题 C# 实现

    介绍 在之前的帖子中,我们实现了Floyd-Warshall(弗洛伊德-沃沙尔算法)(四种变体)以及路由重建算法.在这些帖子中,我们探讨了所有对最短路径问题的基本概念.内存中的数据表示.并行性.向量化 ...

  8. Flutter TextField 的高度问题

    示例 先来看一个例子:假设我们要做一个表单,左边是提示文字,右边是输入框 给出代码: Row( crossAxisAlignment: CrossAxisAlignment.center, child ...

  9. 关于Transformer中feed forward layer理解

    今天记录一下个人关于Transformer中前馈神经网络(FFN)的一点理解. 第一点,我们知道,FFN之前是有一个多头注意力机制的模块.我们可以想想一下,如果这个模块直接输出,不经过FFN层会发生什 ...

  10. MySQL故障诊断常用方法手册(含脚本、案例)

    当你在使用MySQL数据库时,突然遇到故障,你是否会感到迷茫? ● 数据库响应变慢.SQL慢.数据库插入出现延时-- ● 表不见了.日志出现多个断连记录-- ● 非法断电造成MySQL启动报错.同步复 ...