赤裸裸的来蹭下热点。 微信跳一跳小游戏,风格简约,忍不住动心思自动跳一跳。代码阅读起来太费劲,决定写一篇文章描述一下本身的代码。javascript
仅供练习nodejs技能,勿讨论做弊手段。vue
使用的开箱即用工具java
游戏目标分析node
设备数据(借助别人github repo,非ADB)python
图像处理webpack
技能点:git
游戏中,小人蓄力时长决定弹跳距离,成功跳到下一个墩子,即加分。
目标即获取小人位置,获取目标点位置而后计算距离。
在作的过程当中,发现,人物弹跳方向为斜向30度,未跳到中心点的状况下,偏移位置彷佛不会致使游戏失败。
因而游戏目标简化为搜索小人位置,与搜索墩子中心点横坐标。
墩子中心点横坐标,与墩子顶点横坐标基本一致,只有一个长方形墩子不一致。
小人的圆形头部图像不变,使用opencv模板识别,直接可以准确搜索到人头位置。 因此游戏目标再简化为:github
将openstf/minicap
,openstf/minitouch
部署到安卓设备,而后经过adb启动socket,再经过adb链接socket,后续请求与发送数据不须要再次建立adb链接,实时性较好。 web
async function startMinicap
:
...
let command = util.format(
'LD_LIBRARY_PATH=%s exec %s %s',
path.dirname('/data/local/tmp/minicap.so'),
'/data/local/tmp/minicap',
`-P 1080x1920@360x640/${orientation} -S -Q ${quality}`
)
// `-P 540x960@360x640/${orientation} -S -Q ${quality}`
status.tryingStart = true
let stdout = await client.shell(device.id, command)
...
复制代码
stdout 为标准输出的socket对象,后续加一个200ms内无错误即resolve的Promise,令startMinicap可正确await。
链接Socket,获取Stream : /src/renderer/util/getStream.js#L6 async function liveStream
:算法
...
var { err, stream } = await client
.openLocal(device.id, 'localabstract:minicap')
.timeout(10000)
.then(out => ({ stream: out }))
.catch(err => ({ err }))
...
复制代码
获取stream ,而后使用on readable 事件取屏幕每帧图片,格式为jpeg压缩。
...
stream.on('readable', tryRead)
...
复制代码
function tryRead #L50,其逻辑为解析stream每次读取到的buffer,按条件拼成jpeg raw buffer 。
此处可简单作限图像刷新频率处理 #L154
显示图像,能够方便的反馈判别结果。
上一步的socket,能够在electron中轻松import,并能够方便的将每个framebuffer 赋值给 vm.screendata 。 使用vue监听screendata,便可实时将screendata显示到canvas中。
这里用到 vue 的 directives 。
<canvas v-screen='screendata' id='screen' :width="canvasWidth" :height="canvasHeight" :style="canvasStyle"></canvas>
复制代码
...
directives: {
screen(el, binding, vNode) {
// console.info('[canvas Screen]')
if (!binding.value) return
// console.info('render an image ---- ', +new Date())
let BLANK_IMG = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='
var g = el.getContext('2d')
var blob = new Blob([binding.value], { type: 'image/jpeg' })
var URL = window.URL || window.webkitURL
var img = new Image()
img.onload = () => {
vNode.context.canvasWidth = img.width
vNode.context.canvasHeight = img.height
g.drawImage(img, 0, 0)
// firstImgLoad = true
img.onload = null
img.src = BLANK_IMG
img = null
u = null
blob = null
}
var u = URL.createObjectURL(blob)
img.src = u
},
...
}
...
复制代码
使用 URL.createObjectURL
为img生成一个src地址,而后将img画到canvas中。 定义 directives
时,vNode
须要手动传入,不能直接用this
。
【
此处,伪装一个动态GIF:
stream.on('readable',function tryRead(){
...
framedata = chunk.read()
callback(framedata)
...
})
function callback (framedata){
vm.screendata = framedata
}
每个framedata 赋给 vm.screendata, Canvas上显示的图像刷新一下。
】
复制代码
代码中一样使用directives
作了一个辅助线层,用来显示辅助线,以及找到的点。
设备触摸事件发送
按照屏幕stream的方式,取得minitouch的socket,对socket按照minitouch README中格式进行write,便可完成触摸事件的模拟。
触摸时长的控制,经过控制touchdown与touchup的时间长度调节。兼容设备触摸事件,设定每超过200ms,进行原地touchmove一下。代码MirrorScreen.vue#L221
时间调节,经过async / await 实现。标准的api应用,彷佛没什么可说的。
敲下地面
到此,准备好的工具,可以提供给我截图,画点,精确ms时长蓄力,因而我采集到了一些数据:
X = [0,50,100,150,200,250,300,700,1000]
Y = [0,33, 69, 90,144,177,207,516, 753]
复制代码
f(x) = -6.232e-08 x^3 + 0.0001559 x^2 + 0.6601 x - 0.7638
复制代码
首先, open4nodejs 的使用。 opencv4nodejs 的README讲得挺全的。
最开始搜索node版opencv时,发现有2.4版本有3.0版本。这个repo使用的3.0版本,安装起来也很顺利。
README中,不一样通道数的图像,根据坐标获取图像的颜色信息,建立一个形状等,描述的都很清楚。
找顶点的方式,想到了使用漫水法填充背景色,而后二值化+反色取到最靠上的顶点。
实际过程当中会遇到:
而后,用此灰度图像,对背景进行漫水填充,闽值40,使用 BINARY_INV 方式,处理获得二值图。而后逐行搜索,找到顶点所在行。而后用数组方法,根据方差,对该行元素进行简易分类,获得最长连续像素范围,取中间值,即为顶点横坐标。
一样方式能够识别小药瓶:
使用opencv的templateMatch方法,可快速获得结果 findTarget2.js#L11:
...
let ballMat = cv.imread(path.resolve(__dirname, '..', 'ball.jpg'), 0) # 小人头部为固定图片
...
let { maxLoc: ballPoint } = colorMat
.bgrToGray()
.matchTemplate(ballMat, 3)
.minMaxLoc()
...
复制代码
结果中取maxLoc便可获得小人底座位置存入变量ballPoint
。每次取小球位置太准确了,以致于没有写异常捕捉。
new Promise(r=>{cachedArray.push(r)}).then(...)
的方式,变种使用promise,完成socket返回数据以后继续执行代码逻辑。实现先蓄力,而后 n 毫秒以后返回处理结果,再断定弹跳时间。这个辅助应用,是本身把所了解的技能连续堆积完成的,比demo大了。
此工具彻底非开箱即用: electron 部分、opencv部分。
不足
总结
熟练了socket的使用、buffer的操做,熟悉了opencv的基本使用、vue directives的使用。尝试了使用python。
最后。
实时性效果,坊一个之前的没有opencv的自动极速变色龙的视频:
youtu.be/7YSpqiYZJ0w