使用Canvas进行Pixel完美2D鼠标拾取

时间:2011-12-20 16:22:12

标签: javascript html5 canvas

我正在使用Canvas在html5中编写2D游戏,需要鼠标点击和悬停事件才能被检测到。这有三个问题:检测必须是​​像素完美的,对象不是矩形(房屋,奇怪的UI按钮......),并且要求快速和响应。 (显然蛮力不是一种选择)

所以我想问的是如何找出鼠标所在的对象,以及可能的优化内容。

P.S:我做了一些调查,找到了一个使用QuadTree here的人。

4 个答案:

答案 0 :(得分:3)

我有一个(过时的)教程,解释了鬼画布的概念,这对于像素完美的命中检测是不错的。教程是here。忽略有关较新教程的警告,较新的教程不使用ghost canvas概念。

我们的想法是将有问题的图像绘制到内存中的画布上,然后使用getImageData来获取鼠标单击的单个像素。然后你会看到那个像素是否完全透明。

如果它不完全透明,那么,你已经有了目标。

如果它是完全透明的,则将下一个对象绘制到内存中的画布并重复。

您只需要在最后清除内存中的画布。

getImageData速度很慢,但如果你想要像素完美的命中检测并且没有预先计算任何东西,那么它是你唯一的选择。

或者,您可以预先计算路径或具有偏移量的像素数组。这将是很多工作,但可能会更快。例如,如果你有一个40x20的图像具有一定的透明度,你就会计算出一个数组[40] [20],这个数组的真或假对应于透明或不对应。然后你要针对鼠标位置进行测试,有一些偏移量,如果在(25,55)处绘制图像,你想从鼠标位置减去该值,然后在你看时测试新位置是否为真阵列[POSX] [POSY]。

这是我对你问题的回答。 我的建议?如果这是游戏,请忘记像素完美检测。

严重。

而是创建表示对象但不是像素完美的路径(不是在画布中,在普通的javascript代码中),例如房子可能是顶部有三角形的正方形,这是一个非常接近的图像但是在进行热门测试时,它被用来代替它。如果一个点在路径内部而不是像素完美检测,则计算相对非常快。在多边形匝数规则检测中查找点。老实说,这是你最好的选择。

答案 1 :(得分:2)

传统游戏开发中的常见解决方案是构建一个点击掩码。您可以将所有内容重新渲染到一个单独的离屏画布上,呈现纯色(渲染应该非常快)。当您想要弄清楚所点击的内容时,您只需在屏幕外画布上的x / y坐标处采样颜色。您最终会构建一个颜色 - > obj哈希,类似于:

var map = {
      '#000000' : obj1
    , '#000001' : obj2
    , ...
};

您还可以优化渲染到辅助画布,只有在用户点击某些内容时才会发生。使用各种技术,您可以进一步优化它以仅绘制用户单击的画布部分(例如,您可以将画布分割为NxN网格,例如20x20像素正方形的网格,并标记所有那个方块中的对象 - 你只需要重新绘制少量的对象)

答案 2 :(得分:0)

HTML5 Canvas只是一个绘图平面,您可以在调用每个绘图API函数之前设置不同的变换。无法创建对象,也没有显示列表。因此,您必须自己构建这些功能,或者可以使用不同的库。

http://www.kineticjs.com/

http://easeljs.com/

在我对此感兴趣之前的几个月,甚至为此目的写了一个图书馆。你可以在这里看到它:http://exsprite.com。结束了很多性能问题,但由于时间不够,我无法对其进行优化。这真的很有趣,所以等待一段时间才能完美。

答案 3 :(得分:0)

我认为评论应该足够了。这就是我在我的二维等距卷轴中确定用户意图的方法,目前位于http://untitled.servegame.com

var lastUp = 0;
function mouseUp(){
    mousedown = false; //one of my program globals.
    var timeNow = new Date().getTime();
    if(mouseX == xmouse && mouseY == ymouse && timeNow > lastUp + 100){//if it was a centralized click. (mouseX = click down point, xmouse = mouse's most recent x) and is at least 1/10th of a second after the previous click.
        lastUp = new Date().getTime();
        var elem = document.elementFromPoint(mouseX, mouseY); //get the element under the mouse.
        var url = extractUrl($(elem).css('background-image')); // function I found here: http://webdevel.blogspot.com/2009/07/jquery-quick-tip-extract-css-background.html

        imgW = $("#hiddenCanvas").width(); //EVERY art file is 88px wide. thus my canvas element is set to 88px wide. 
        imgH = $(elem).css('height').split('p')[0]; //But they vary in height. (currently up to 200);

        hiddenCanvas.clearRect(0, 0, imgW, imgH); //so only clear what is necessary.

        var img = new Image();
        img.src = url;                          
        img.onload = function(){
            //draw this elements image to the canvas at 0,0
            hiddenCanvas.drawImage(img,0,0);

            ///This computes where the mouse is clicking the element.
            var left = $(elem).css('left').split('p')[0]; //get this element's css absolute left.
            var top = $(elem).css('top').split('p')[0];
            offX = left - offsetLeft; //left minus the game rendering element's absolute left. gives us the element's position relative of document 0,0
            offY = top - offsetTop;
            offX = mouseX - offX; //apply the difference of the click point's x and y
            offY = mouseY - offY;


            var imgPixel = hiddenCanvas.getImageData(offX, offY, 1, 1); //Grab that pixel. Start at it's relative X and it's relative Y and only grab one pixel.
            var opacity = imgPixel.data[3]; //get the opacity value of this pixel.

            if(opacity == 0){//if that pixel is fully transparent
                $(elem).hide();
                var temp = document.elementFromPoint(mouseX, mouseY); //set the element right under this one
                $(elem).show();
                elem = temp;
            }

            //draw a circle on our hiddenCanvas so when it's not hidden we can see it working!
            hiddenCanvas.beginPath();
            hiddenCanvas.arc(offX, offY, 10, 0, Math.PI*2, true); 
            hiddenCanvas.closePath();
            hiddenCanvas.fill();


            $(elem).css("top", "+=1"); //apply something to the final element.

        }
    }
}

与此同时:

<canvas id="hiddenCanvas" width="88" height="200"></canvas>

设置CSS定位绝对值和x = - (宽度)以隐藏;