我目前正在为现代浏览器和Windows 8 Store项目开发多个游戏项目。 他们中的一些人以HTML5为基础,以简化多设备定位。 然后,我正在寻找一种统一的方法来处理所有平台上的所有输入:Windows 8 / RT,Windows Phone 8,iPad,Android和FirefoxOS。
正如您在我之前的文章《 统一触摸和鼠标—指针事件如何使跨浏览器的触摸支持变得容易》中发现的那样 , Windows 8 / RT和Windows Phone 8上的IE10实现了我们提交给W3C的Pointer Events模型。 为了以统一的方式解决这个Pointer Events模型以及在基于WebKit的浏览器中实现的问题,我们将使用David Catuhe的HandJS库。 在这里查看他的博客文章: HandJS一个polyfill,用于在每个浏览器上支持指针事件 。 这个想法是针对Pointer模型的,并且库会将触摸事件传播到所有平台细节。
掌握了所有技术知识后,我正在寻找一种在游戏中实现虚拟触摸操纵杆的好方法。 我不是要触摸箭头键的忠实粉丝。 另一方面,虚拟类比垫通常放置得不太好。 但是我终于发现, Seb Lee-Delisle已经消化了这一点,并创造了一个很棒的概念,该概念在iPad的JavaScript / HTML5中的多点触控游戏控制器中有所描述。 该代码可在GitHub上找到: JSTouchController
当时的想法是采用他的代码并重构触摸部分,使其以Pointer模型为目标,而不是原始的WebKit Touch方法。 在几个月前进行这项工作时,我发现Google的Boris Smus已经或多或少开始这样做。 正如他在跨设备网络上的文章Generalized input中所描述的那样,当他在自己的Pointer.js库上工作时就已经完成了。 但是,当时Boris模仿的是IE10指针事件实现的旧版本,并且他的库不在IE10中工作。 这就是为什么即使鲍里斯(Boris)的作品很棒,我们仍然决定使用自己的版本。 确实,David的库当前针对的是最新的W3C版本(当前在最新的通话草案中) 。 如果您同时查看两个库,您还将看到HandJS在代码的多个部分中使用了一些不同的方法。 然后,我们将在本文中使用HandJS来构建我们的触摸操纵杆。
示例1:指针跟踪器
此样本可帮助您跟踪屏幕上的各种输入。 它跟踪并跟随按下画布元素的各种手指。 它基于GitHub上提供的Seb示例: Touches.html
感谢Hand.js,我们将使其与所有浏览器兼容。 它甚至还可以根据您当前正在测试的硬件类型来跟踪触控笔和/或鼠标!
这是在Windows 8下运行IE10的结果的HTML5视频。您会发现看到一些青色的圆圈跟踪手指,然后是一个红色的圆圈跟踪鼠标,一个绿色的圆圈跟踪笔:
下载视频: MP4 , WebM , VideoJS的HTML5视频播放器
在Windows 8或iOS / Android / FirefoxOS设备上的Chrome下,同一网页提供的结果非常相同(除了IE10仅支持笔)。 多亏了HandJS,只需编写一次就可以在任何地方运行!
您已经在视频中看到,青色指针的类型为“ TOUCH”,而红色指针的类型为“ MOUSE”。 如果您有触摸屏,则可以通过测试此iframe中嵌入的以下页面来获得相同的结果:
该示例可在Windows 8 / RT触摸设备,Windows Phone 8,iPad / iPhone或Android / FirefoxOS设备上正常运行! 如果您没有触摸设备,HandJS将自动退回到鼠标。 然后,您应该可以用鼠标至少跟踪1个指针。
让我们看看如何以统一的方式获得此结果。 所有代码都保存在Touches.js中 :
"use strict" ; // shim layer with setTimeout fallback window.requestAnimFrame = ( function () { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) { window.setTimeout(callback, 1000 / 60); }; })(); var pointers; // collections of pointers var canvas, c; // c is the canvas' context 2D document.addEventListener( "DOMContentLoaded" , init); window.onorientationchange = resetCanvas; window.onresize = resetCanvas; function init() { setupCanvas(); pointers = new Collection(); canvas.addEventListener( 'pointerdown' , onPointerDown, false ); canvas.addEventListener( 'pointermove' , onPointerMove, false ); canvas.addEventListener( 'pointerup' , onPointerUp, false ); canvas.addEventListener( 'pointerout' , onPointerUp, false ); requestAnimFrame(draw); } function resetCanvas(e) { // resize the canvas - but remember - this clears the canvas too. canvas.width = window.innerWidth; canvas.height = window.innerHeight; //make sure we scroll to the top left. window.scrollTo(0, 0); } function draw() { c.clearRect(0, 0, canvas.width, canvas.height); pointers.forEach( function (pointer) { c.beginPath(); c.fillStyle = "white" ; c.fillText(pointer.type + " id : " + pointer.identifier + " x:" + pointer.x + " y:" + pointer.y, pointer.x + 30, pointer.y - 30); c.beginPath(); c.strokeStyle = pointer.color; c.lineWidth = "6" ; c.arc(pointer.x, pointer.y, 40, 0, Math.PI * 2, true ); c.stroke(); }); requestAnimFrame(draw); } function createPointerObject(event) { var type; var color; switch (event.pointerType) { case event.POINTER_TYPE_MOUSE: type = "MOUSE" ; color = "red" ; break ; case event.POINTER_TYPE_PEN: type = "PEN" ; color = "lime" ; break ; case event.POINTER_TYPE_TOUCH: type = "TOUCH" ; color = "cyan" ; break ; } return { identifier: event.pointerId, x: event.clientX, y: event.clientY, type: type, color: color }; } function onPointerDown(e) { pointers.add(e.pointerId, createPointerObject(e)); } function onPointerMove(e) { if (pointers.item(e.pointerId)) { pointers.item(e.pointerId).x = e.clientX; pointers.item(e.pointerId).y = e.clientY; } } function onPointerUp(e) { pointers.remove(e.pointerId); } function setupCanvas() { canvas = document.getElementById( 'canvasSurface' ); c = canvas.getContext( '2d' ); canvas.width = window.innerWidth; canvas.height = window.innerHeight; c.strokeStyle = "#ffffff" ; c.lineWidth = 2; }
好吧,我认为代码非常简单。 我正在注册指针向下/上移/向上事件,如我在MSPointer Events的介绍文章中所述 。 在pointerdown处理程序中,我捕获了在指针集合对象中动态生成的对象内的ID,X和Y坐标以及指针的类型(触摸,笔或鼠标)。 该集合由指针的ID索引。 集合对象在Collection.js中进行了描述。 然后draw()函数枚举此集合,以根据触摸屏幕的确切位置处的类型绘制一些青色/红色/石灰圆形。 它还在每个圆的侧面添加一些文本以显示指针的详细信息。 指针移动处理程序更新了集合中关联指针的坐标,而pointerup / out只是将其从集合中删除。 Hand.JS通过将pointerdown / move / up / out传播到关联的MSPointerDown / Move / Up / Out事件以及WebKit浏览器的touchstart / move / end事件,使此代码与IE10兼容。
如果您愿意,可以在此处查看完整的源代码: http : //david.blob.core.windows.net/html5/touchjoystick/Touches.html
示例2:具有简单飞船游戏的视频游戏控制器
现在,让我们看一下我最感兴趣的示例。如果您正在为HTML5游戏寻找虚拟模拟触摸板,则可能也会这样做。 想法是触摸屏幕左侧的任何位置。 在您触摸屏幕的确切位置,它将显示一个简单但非常有效的键盘。 移动手指将更新虚拟触摸板,并将移动一个简单的太空飞船。 触摸屏幕右侧将显示一些红色圆圈,这些圆圈将生成一些从子弹飞出的子弹。 再一次,它基于Seb的示例,可在GitHub上找到: TouchControl.html
这是Windows 8下IE10中更新的示例结果的视频:
如果您有触摸屏,则可以在此iframe中实时测试该页面:
否则,您只能通过单击屏幕左侧的鼠标来移动飞船,或者通过单击右侧的箭头来开火,但是您将无法同时完成这两项操作。 实际上,如果浏览器或平台不支持触摸,HandJS就会提供鼠标后备功能。
注意: iPad似乎有一个未知的错误,该错误会阻止第二个iframe正常工作。 直接在另一个标签中打开示例 ,使其在iPad上运行。
让我们再次看看如何以统一的方式获得此结果。 这次所有代码都保存在TouchControl.js中 :
// shim layer with setTimeout fallback window.requestAnimFrame = ( function () { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) { window.setTimeout(callback, 1000 / 60); }; })(); var canvas, c, // c is the canvas' context 2D container, halfWidth, halfHeight, leftPointerID = -1, leftPointerPos = new Vector2(0, 0), leftPointerStartPos = new Vector2(0, 0), leftVector = new Vector2(0, 0); var pointers; // collections of pointers var ship; bullets = [], spareBullets = []; document.addEventListener( "DOMContentLoaded" , init); window.onorientationchange = resetCanvas; window.onresize = resetCanvas; function init() { setupCanvas(); pointers = new Collection(); ship = new ShipMoving(halfWidth, halfHeight); document.body.appendChild(ship.canvas); canvas.addEventListener( 'pointerdown' , onPointerDown, false ); canvas.addEventListener( 'pointermove' , onPointerMove, false ); canvas.addEventListener( 'pointerup' , onPointerUp, false ); canvas.addEventListener( 'pointerout' , onPointerUp, false ); requestAnimFrame(draw); } function resetCanvas(e) { // resize the canvas - but remember - this clears the canvas too. canvas.width = window.innerWidth; canvas.height = window.innerHeight; halfWidth = canvas.width / 2; halfHeight = canvas.height / 2; //make sure we scroll to the top left. window.scrollTo(0, 0); } function draw() { c.clearRect(0, 0, canvas.width, canvas.height); ship.targetVel.copyFrom(leftVector); ship.targetVel.multiplyEq(0.15); ship.update(); with (ship.pos) { if (x < 0) x = canvas.width; else if (x > canvas.width) x = 0; if (y < 0) y = canvas.height; else if (y > canvas.height) y = 0; } ship.draw(); for ( var i = 0; i < bullets.length; i++) { var bullet = bullets[i]; if (!bullet.enabled) continue ; bullet.update(); bullet.draw(c); if (!bullet.enabled) { spareBullets.push(bullet); } } pointers.forEach( function (pointer) { if (pointer.identifier == leftPointerID) { c.beginPath(); c.strokeStyle = "cyan" ; c.lineWidth = 6; c.arc(leftPointerStartPos.x, leftPointerStartPos.y, 40, 0, Math.PI * 2, true ); c.stroke(); c.beginPath(); c.strokeStyle = "cyan" ; c.lineWidth = 2; c.arc(leftPointerStartPos.x, leftPointerStartPos.y, 60, 0, Math.PI * 2, true ); c.stroke(); c.beginPath(); c.strokeStyle = "cyan" ; c.arc(leftPointerPos.x, leftPointerPos.y, 40, 0, Math.PI * 2, true ); c.stroke(); } else { c.beginPath(); c.fillStyle = "white" ; c.fillText( "type : " + pointer.type + " id : " + pointer.identifier + " x:" + pointer.x +
" y:" + pointer.y, pointer.x + 30, pointer.y - 30); c.beginPath(); c.strokeStyle = "red" ; c.lineWidth = "6" ; c.arc(pointer.x, pointer.y, 40, 0, Math.PI * 2, true ); c.stroke(); } }); requestAnimFrame(draw); } function makeBullet() { var bullet; if (spareBullets.length > 0) { bullet = spareBullets.pop(); bullet.reset(ship.pos.x, ship.pos.y, ship.angle); } else { bullet = new Bullet(ship.pos.x, ship.pos.y, ship.angle); bullets.push(bullet); } bullet.vel.plusEq(ship.vel); } function givePointerType(event) { switch (event.pointerType) { case event.POINTER_TYPE_MOUSE: return "MOUSE" ; break ; case event.POINTER_TYPE_PEN: return "PEN" ; break ; case event.POINTER_TYPE_TOUCH: return "TOUCH" ; break ; } } function onPointerDown(e) { var newPointer = { identifier: e.pointerId, x: e.clientX, y: e.clientY, type: givePointerType(e) }; if ((leftPointerID < 0) && (e.clientX < halfWidth)) { leftPointerID = e.pointerId; leftPointerStartPos.reset(e.clientX, e.clientY); leftPointerPos.copyFrom(leftPointerStartPos); leftVector.reset(0, 0); } else { makeBullet(); } pointers.add(e.pointerId, newPointer); } function onPointerMove(e) { if (leftPointerID == e.pointerId) { leftPointerPos.reset(e.clientX, e.clientY); leftVector.copyFrom(leftPointerPos); leftVector.minusEq(leftPointerStartPos); } else { if (pointers.item(e.pointerId)) { pointers.item(e.pointerId).x = e.clientX; pointers.item(e.pointerId).y = e.clientY; } } } function onPointerUp(e) { if (leftPointerID == e.pointerId) { leftPointerID = -1; leftVector.reset(0, 0); } leftVector.reset(0, 0); pointers.remove(e.pointerId); } function setupCanvas() { canvas = document.getElementById( 'canvasSurfaceGame' ); c = canvas.getContext( '2d' ); resetCanvas(); c.strokeStyle = "#ffffff" ; c.lineWidth = 2; }
代码再次非常简单,我不会花时间解释它。 您可以在此处查看完整的源代码: http : //david.blob.core.windows.net/html5/touchjoystick/TouchControl.html
总之,由于Seb Lee-Delisle和David Catuhe所做的工作,您现在拥有了为HTML5游戏实现自己的虚拟触摸游戏手柄所需的所有组件。 结果将在所有支持HTML5的触摸设备上运行!
大卫
From: https://www.sitepoint.com/create-a-cross-browser-touch-based-joystick-with-hand-js/