HTML5开发Kinect体感游戏的实例应用
html5开发kinect体感游戏的实例应用
一、简介
我们要做的是怎样一款游戏?
在前不久成都tgc2016展会上,我们开发了一款《火影忍者手游》的体感游戏,主要模拟手游章节《九尾袭来 》,用户化身四代,与九尾进行对决,吸引了大量玩家参与。 表面上看,这款游戏与其它体感体验无异,实际上,它一直运行于浏览器chrome下,也就是说,我们只需要掌握前端相应技术,就可以开发基于kinect的网页体感游戏。
二、实现原理
实现思路是什么?
使用h5开发基于kinect的体感游戏,其实工作原理很简单,由kinect采集到玩家及环境数据,比如人体骨骼,使用某种方式,使浏览器可以访问这些数据。
1、采集数据
kinect有三个镜头,中间镜头类似普通摄像头,获取彩色图像。左右两边镜头则是通过红外线获取深度数据。我们使用微软提供的sdk去读取以下类型数据:
- 色彩数据:彩色图像;
- 深度数据:颜色尝试信息;
- 人体骨骼数据:基于以上数据经计算,获取到人体骨骼数据。
2、使浏览器可访问到kinect数据
我尝试和了解过的框架,基本上是以socket让浏览器进程与服务器进行通信 ,进行数据传输:
- kinect-html5 用c#搭建服务端,色彩数据、尝试数据、骨骼数据均有提供;
- zigfu 支持h5、u3d、flash进行开发,api较为完整,貌似收费;
- depthjs 以浏览器插件形式提供数据访问;
- node-kinect2 以nodejs搭建服务器端,提供数据比较完整,实例较多。
我最终选用node-kinect2,虽然没有文档,但是实例较多,使用前端工程师熟悉的nodejs,另外作者反馈比较快。
- kinect: 捕获玩家数据,比如深度图像、彩色图像等;
- node-kinect2: 从kinect获取相应数据,并进行二次加工;
- 浏览器: 监听node应用指定接口,获取玩家数据并完成游戏开发。
三、准备工作
先得买个kinect啊
1、系统要求:
这是硬性要求,我曾在不符合要求的环境下浪费太多时间。
- usb3.0
- 支持dx11的显卡
- win8及以上系统
- 支持web sockets的浏览器
- 当然kinect v2传感器是少不了的
2、环境搭建流程:
- 连接上kinect v2
- 安装 kinectsdk-v2.0
- 安装 nodejs
- 安装 node-kinect2
npm install kinect2
四、实例演示
说什么都不如给我一个例子!
如下图所示,我们演示如何获取人体骨骼,并标识脊椎中段及手势:
1、服务器端
创建web服务器,并将骨骼数据发送到浏览器端,代码如下:
var kinect2 = require('../../lib/kinect2'), express = require('express'), app = express(), server = require('http').createserver(app), io = require('socket.io').listen(server); var kinect = new kinect2(); // 打开kinect if(kinect.open()) { // 监听8000端口 server.listen(8000); // 指定请求指向根目录 app.get('/', function(req, res) { res.sendfile(__dirname + '/public/index.html'); }); // 将骨骼数据发送给浏览器端 kinect.on('bodyframe', function(bodyframe){ io.sockets.emit('bodyframe', bodyframe); }); // 开始读取骨骼数据 kinect.openbodyreader(); }
2、浏览器端
浏览器端获取骨骼数据,并用canvas描绘出来,关键代码如下:
var socket = io.connect('/'); var ctx = canvas.getcontext('2d'); socket.on('bodyframe', function(bodyframe){ ctx.clearrect(0, 0, canvas.width, canvas.height); var index = 0; // 遍历所有骨骼数据 bodyframe.bodies.foreach(function(body){ if(body.tracked) { for(var jointtype in body.joints) { var joint = body.joints[jointtype]; ctx.fillstyle = colors[index]; // 如果骨骼节点为脊椎中点 if(jointtype == 1) { ctx.fillstyle = colors[2]; } ctx.fillrect(joint.depthx * 512, joint.depthy * 424, 10, 10); } // 识别左右手手势 updatehandstate(body.lefthandstate, body.joints[7]); updatehandstate(body.righthandstate, body.joints[11]); index++; } }); });
很简单的几行代码,我们便完成了玩家骨骼捕获,有一定 javascript基础的同学应该很容易能看明白,但不明白的是我们能获取哪些数据?如何获取?骨骼节点名称分别是什么?而node-kienct2并没有文档告诉我们这些。
五、开发文档
node-kinect2并没有提供文档,我将我测试总结的文档整理如下:
1、服务器端能提供的数据类型;
kinect.on('bodyframe', function(bodyframe){}); //还有哪些数据类型呢?
bodyframe | 骨骼数据 |
infraredframe | 红外数据 |
longexposureinfraredframe | 类似infraredframe,貌似精度更高,优化后的数据 |
rawdepthframe | 未经处理的景深数据 |
depthframe | 景深数据 |
colorframe | 彩色图像 |
multisourceframe | 所有数据 |
audio | 音频数据,未测试 |
2、骨骼节点类型
body.joints[11] // joints包括哪些呢?
节点类型 | jointtype | 节点名称 |
0 | spinebase | 脊椎基部 |
1 | spinemid | 脊椎中部 |
2 | neck | 颈部 |
3 | head | 头部 |
4 | shoulderleft | 左肩 |
5 | elbowleft | 左肘 |
6 | wristleft | 左腕 |
7 | handleft | 左手掌 |
8 | shoulderright | 右肩 |
9 | elbowright | 右肘 |
10 | wristright | 右腕 |
11 | handright | 右手掌 |
12 | hipleft | 左屁 |
13 | kneeleft | 左膝 |
14 | ankleleft | 左踝 |
15 | footleft | 左脚 |
16 | hipright | 右屁 |
17 | kneeright | 右膝 |
18 | ankleright | 右踝 |
19 | footright | 右脚 |
20 | spineshoulder | 颈下脊椎 |
21 | handtipleft | 左手指(食中无小) |
22 | thumbleft | 左拇指 |
23 | handtipright | 右手指 |
24 | thumbright | 右拇指 |
3、手势,据测识别并不是太准确,在精度要求不高的情况下使用
0 | unknown | 不能识别 |
1 | nottracked | 未能检测到 |
2 | open | 手掌 |
3 | closed | 握拳 |
4 | lasso | 剪刀手,并合并中食指 |
4、骨骼数据
body [object] { bodyindex [number]:索引,允许6人 joints [array]:骨骼节点,包含坐标信息,颜色信息 lefthandstate [number]:左手手势 righthandstate [number]:右手手势 tracked [boolean]:是否捕获到 trackingid }
5、kinect对象
on | 监听数据 |
open | 打开kinect |
close | 关闭 |
openbodyreader | 读取骨骼数据 |
open**reader | 类似如上方法,读取其它类型数据 |
六、实战总结
火影体感游戏经验总结
接下来,我总结一下tgc2016《火影忍者手游》的体感游戏开发中碰到的一些问题。
1、讲解之前,我们首先需要了解下游戏流程。
1.1、通过手势触发开始游戏 |
1.2、玩家化身四代,左右跑动躲避九尾攻击 |
1.3、摆出手势“奥义”,触发四代大招 |
1.4、用户扫描二维码获取自己现场照片 |
2、服务器端
游戏需要玩家骨骼数据(移动、手势),彩色图像数据(某一手势下触发拍照),所以我们需要向客户端发送这两部分数据。值得注意的是,彩色图像数据体积过大,需要进行压缩。
var emitcolorframe = false; io.sockets.on('connection', function (socket){ socket.on('startcolorframe', function(data){ emitcolorframe = true; }); }); kinect.on('multisourceframe', function(frame){ // 发送玩家骨骼数据 io.sockets.emit('bodyframe', frame.body); // 玩家拍照 if(emitcolorframe) { var compression = 1; var origwidth = 1920; var origheight = 1080; var origlength = 4 * origwidth * origheight; var compressedwidth = origwidth / compression; var compressedheight = origheight / compression; var resizedlength = 4 * compressedwidth * compressedheight; var resizedbuffer = new buffer(resizedlength); // ... // 照片数据过大,需要压缩提高传输性能 zlib.deflate(resizedbuffer, function(err, result){ if(!err) { var buffer = result.tostring('base64'); io.sockets.emit('colorframe', buffer); } }); emitcolorframe = false; } }); kinect.openmultisourcereader({ frametypes: kinect2.frametype.body | kinect2.frametype.color });
3、客户端
客户端业务逻辑较复杂,我们提取关键步骤进行讲解。
3.1、用户拍照时,由于处理的数据比较大,为防止页面出现卡顿,我们需要使用web worker
(function(){ importscripts('pako.inflate.min.js'); var imagedata; function init() { addeventlistener('message', function (event) { switch (event.data.message) { case "setimagedata": imagedata = event.data.imagedata; break; case "processimagedata": processimagedata(event.data.imagebuffer); break; } }); } function processimagedata(compresseddata) { var imagebuffer = pako.inflate(atob(compresseddata)); var pixelarray = imagedata.data; var newpixeldata = new uint8array(imagebuffer); var imagedatasize = imagedata.data.length; for (var i = 0; i < imagedatasize; i++) { imagedata.data[i] = newpixeldata[i]; } for(var x = 0; x < 1920; x++) { for(var y = 0; y < 1080; y++) { var idx = (x + y * 1920) * 4; var r = imagedata.data[idx + 0]; var g = imagedata.data[idx + 1]; var b = imagedata.data[idx + 2]; } } self.postmessage({ "message": "imageready", "imagedata": imagedata }); } init(); })();
3.2、接投影仪后,如果渲染面积比较大,会出现白屏,需要关闭浏览器硬件加速。
3.3、现场光线较暗,其它玩家干扰,在追踪玩家运动轨迹的过程中,可能会出现抖动的情况,我们需要去除干扰数据。(当突然出现很大位移时,需要将数据移除)
var tracks = this.tracks; var len = tracks.length; // 数据过滤 if(tracks[len-1] !== window.undefined) { if(math.abs(n - tracks[len-1]) > 0.2) { return; } } this.tracks.push(n);
3.4、当玩家站立,只是左右少量晃动时,我们认为玩家是站立状态。
// 保留5个数据 if(this.tracks.length > 5) { this.tracks.shift(); } else { return; } // 位移总量 var dis = 0; for(var i = 1; i < this.tracks.length; i++) { dis += this.tracks[i] - this.tracks[i-1]; } if(math.abs(dis) < 0.01) { this.stand(); } else { if(this.tracks[4] > this.tracks[3]) { this.turnright(); } else { this.turnleft(); } this.run(); }
七、展望
1、使用html5开发kinect体感游戏,降低了技术门槛,前端工程师可以轻松的开发体感游戏;
2、大量的框架可以应用,比如用jquery、createjs、three.js(三种不同渲染方式);
3、无限想象空间,试想下体感游戏结合webar,结合webaudio、结合移动设备,太可以挖掘的东西了……想想都激动不是么!
如有疑问请留言或者到本站社区交流讨论,感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!
上一篇: JS分页的实现(同步与异步)
下一篇: Express使用html模板的详细代码