创建基于 Hand.js的跨浏览器 触摸屏游戏杆

分享于 

17分钟阅读

Web开发

  繁體 雙語

我目前为现代浏览器和 Windows 存储应用程序研究几个游戏项目。 其中一些是基于HTML5的,从而简化了多设备支持。 然后我开始寻找解决所有平台上用户输入的方法- Windows 8和 Windows RT,Windows Phone 8,iPad,Android & FirefoxOS。

在我以前的文章中,你可能已经阅读了在浏览器中统一触控和鼠标的,Windows 8,和 Windows Phone 8实现了我们向W3C提交的指针事件模型。 7 上的IE10也对这些组件有部分支持。 我们将在 webkit 中使用 Catuhe,以统一的方式解决这个指针事件模型和基于的浏览器中实现的一种。 在上查看他的博客文章,作为支持指针事件的Polyfill,在每个浏览器上都可以使用。 思想是针对指针模型的,库将把触摸事件传播到所有的平台细节。

一旦我有了所有的技术片段,我就在寻找一种在游戏中实现虚拟触摸杆的好方法。 我不是一个触摸屏的箭头键的巨大粉丝。 另一方面,虚拟模拟板的放置往往不是很好。 ,finally delisle 已经在 javascript/html5的/html5游戏控制器中described了一个绝妙概念。 代码在GitHub上可用: JSTouchController

想法是采取他的代码并重构它的触摸组件,以目标指针模型而不是原始 webkit 触摸方式。 我的library Boris Boris Boris Boris cross cross cross cross cross cross cross cross cross cross several several several在几个月前,我发现来自谷歌的Boris已经开始做类似的事情了,as。 当时,Boris模仿IE10指针事件实现的旧版本,他的库在IE10中没有工作。 这就是我们决定在自己的版本上工作的原因。 实际上,david的库目前是最新的和最新的W3C指针事件,它目前在中的最后一个调用草稿状态。 如果你看到两个 librairies,你还会看到HandJS在代码的几个部分中使用了一些不同的方法。 我们将在本文中使用HandJS来构建我们的触摸游戏。

示例 1: 指针跟踪器

这里示例帮助你跟踪屏幕上的各种输入。 它跟踪并跟随各种手指按画布元素。 它是基于在GitHub上提供的标准样例的样例: Touches.html

多亏了 Hand.js,,我们将使它兼容所有浏览器。 它甚至还将根据你当前测试的硬件类型来跟踪手写笔和/或者鼠标。

同样的网页在 Chrome的Windows 8或者 iOS/Android/FirefoxOS 设备( 只支持IE10的钢笔) 上提供了相同的结果。 感谢 HandJS,编写它一次,它将随处运行 !

正如你刚刚在视频中看到的,青色指针是"触摸"类型的,而红色的指针是"鼠标"类型。 如果你有触摸屏,你可以通过测试这里iframe中嵌入的此页来体验相同的结果:

这里示例适用于 Windows 8/RT 触摸设备。Windows Phone 8.ipad/iphone或者 android/firefoxos设备 ! 如果你没有触摸屏,HandJS将自动回退到鼠标。 然后,你就可以用鼠标跟踪至少 1个指针。

让我们以统一的方式来了解如何获得这个结果。 所有的代码都在 Touches.js

按照下面的代码,我将在的介绍文章中描述 pointerdown/move/up 事件,如。pointerdown 处理程序中,我捕获 in。X 和Y 坐标以及在 pointers Collection 对象中生成的指针( 触摸,笔或者鼠标) inside的类型。 这里 Collection 由指针的id进行索引。 Collection 对象在 Collection.js 中描述。 当你触摸屏幕时,draw() 函数会根据指针的位置来枚举这个 Collection 以绘制一些 cyan/red/lime 圆圈,以及它的类型。 它还在圆的每一边添加一些文本来显示指针的细节。 pointermove 处理程序负责更新 Collection 中关联指针的坐标,而 pointerup/out 只将它的从 Collection 中删除。 Hand.JS 使这里代码可以通过将 pointerdown/move/up/out 传播到IE10事件上的关联 MSPointerDown/Move/Up/Out 和 webkit 浏览器的touchstart/move/end 事件来兼容。

"use strict";// shim layer with setTimeout fallback// use this to requestAnimationFrame across browserswindow.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 (regardless of input type)var canvas,
c; // c is the canvas' context 2Ddocument.addEventListener("DOMContentLoaded", init);// resize the canvas if user rotates slate or resizes browser windowwindow.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);
}// on detecting pointer events, create the pointer object to add to the collection // for different input type, show different color and textfunction 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;
}

如果愿意,你可以在这里查看完整的源代码

示例 2: 带有简单宇宙飞船游戏的视频游戏控制器

现在让我们看一下我对for游戏的模拟触摸板最感兴趣的示例。 用户应该能够触摸屏幕左边的任何地方。 在这个位置,画布将显示一个简单但非常有效的direction pad。 移动手指,同时按下,会更新虚拟触摸盘,并移动一个简单的飞船。 触摸屏幕右侧会显示一些红色圆圈,这些圆会产生一些弹出飞船的子弹。 同样,它是基于GitHub上可用的超标准样例的: TouchControl.html

如果你有触摸屏,可以在下面的框架中尝试这里游戏:

如果没有,你只能用鼠标点击屏幕左侧或者点击右侧,但是你不能同时完成这两个操作。 如你所见,如果浏览器或者平台不支持触摸,HandJS将提供鼠标回退。

注意:iPad似乎有一个未知的Bug,阻止第二个iframe正常工作。 直接在另一个标签中打开该示例,使它的在你的iPad上工作。

让我们再来看看如何以统一的方式获得这个结果。 所有代码都在 TouchControl.js, 中运行,你可以在这里找到

// shim layer with setTimeout fallback// use this to requestAnimationFrame across browserswindow.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 2Dcontainer,
halfWidth,
halfHeight,
leftPointerID = -1,
leftPointerPos = new Vector2(0, 0),
leftPointerStartPos = new Vector2(0, 0),
leftVector = new Vector2(0, 0);// halfWidth and halfHeight are use to separate the screen // so that we can decide whether to respond to touch joystick or bullet code// Vector2 is a custom class for two-dimensional vectors// the leftPointerStartPos is what we will us to place the joystick// we will track joystick movement relative to it, using leftPointerPos// in the Vector2 type object leftVector// We will compute direction and speed of the spaceship based on value of leftVectorvar pointers; // collections of pointersvar ship;
bullets = [],
spareBullets = [];
document.addEventListener("DOMContentLoaded", init);// resize the canvas if user rotates slate or resizes browser windowwindow.onorientationchange = resetCanvas;
window.onresize = resetCanvas;// associate the custom handJS events with their corresponding even listenersfunction 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();
 // if the ship moves offscreen left, redraw it re-entering from the right, and so onwith (ship.pos) {
 if (x <0) x = canvas.width;
 elseif (x> canvas.width) x = 0;
 if (y <0) y = canvas.height;
 elseif (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);
 }
 // make the bullet speed relative to the ship speed 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;
 }
}// here is where we handle the logic for whether to present a joystick or bullet-firing experience// only if there is no already existing joystick, and the user presses down on the left of the screen// we should begin joystick calculations using leftVector// else fire bulletsfunction 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);
}// track motion of pointer to alter ships direction and speedfunction 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;
 }
 }
}// release the joystick when user lifts up his finger/mousefunction 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;
}

Thanks,,,,,,,,,,,HTML5,HTML5. 结果将在支持HTML5的所有触摸屏设备上工作 !

本文是来自 IE 团队的HTML5技术系列的一部分。 在本文中试用 3个月的免费 BrowserStack 跨浏览器 测试 @ http://modern.IE

David是微软的一位开发者,专攻HTML5和web开发。 本文最初出现在他的MSDN博客上,Coding4Fun在 22th 上,. You 可以跟随他的@davrous 在 Twitter 上。


相关文章