我目前正在为现代浏览器和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已经消化了这一点,并创造了一个很棒的概念,该概念在iPadJavaScript / 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视频。您会发现看到一些青色的圆圈跟踪手指,然后是一个红色的圆圈跟踪鼠标,一个绿色的圆圈跟踪笔:
下载视频: VideoJS的MP4 , WebM , 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的触摸设备上运行!
大卫