zoukankan      html  css  js  c++  java
  • 使用HTML5开发Kinect体感游戏

    https://tgideas.qq.com/webplat/info/news_version3/804/7104/7106/m5723/201612/538908.shtml

    一、简介
    我们要做的是怎样一款游戏?
      在前不久成都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、环境搭建流程:
    1. 连接上Kinect v2
    2. 安装 KinectSDK-v2.0
    3. 安装 Nodejs
    4. 安装 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){}); //还有哪些数据类型呢?
    2、骨骼节点类型
    body.joints[11] // joints包括哪些呢?
    
    
    
    

     4、骨骼数据
    body [object] {
      bodyIndex [number]:索引,允许6人
      joints [array]:骨骼节点,包含坐标信息,颜色信息
      leftHandState [number]:左手手势
      rightHandState [number]:右手手势
      tracked [boolean]:是否捕获到
      trackingId

    
    
    
    
    

     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、结合移动设备,太可以挖掘的东西了……想想都激动不是么!

    评论

     
  • 相关阅读:
    JQuery
    C#基础知识
    CSS
    学习.NET
    Grunt常见问题
    一个小型的类库
    SQL字符串处理!
    安装Java8以后,Eclipse运行异常解决方案
    谈薪四式让你谈好薪
    使用js实现input输入框的增加
  • 原文地址:https://www.cnblogs.com/kekeoutlook/p/11695927.html
Copyright © 2011-2022 走看看