我目前正在为现代浏览器和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的触摸设备上运行!

大卫