检测鼠标悬停在重叠+透明图像上

时间:2015-01-14 00:03:12

标签: jquery canvas z-index alpha hittest

我正在构建一个小游戏,用户可以购买物品来装饰他的房子。

  • 我有很多项目/图片;所以我决定为每个人使用一个“遮罩”(图像)来定义可挖掘的区域,而不是为每个图像绘制一个地图区域。
    示例:此处为the displayed couchits matte 我将遮罩“转换”为画布元素,稍后会检查悬停的像素是否透明以检测物品是否悬停。

  • 第二件事是很多物品都是重叠的,所以我还需要检查哪一层在上面。

我在house元素上有 mousemove 事件(jQuery);绑定函数 getObjectsUnderMouse()

基本上,这就是getObjectsUnderMouse()的工作原理:

  1. 获取鼠标坐标
  2. 获取房屋中的活动(显示)项目
  3. 过滤这些项目,只保留鼠标点击画布边界,知道项目位置和宽度/高度)
  4. 过滤这些项目,只保留鼠标不在透明像素上的 (画布)
  5. 过滤这些项目只保留顶部的那个(z-index)
  6. 为该项目提供 mouseon
  7. 我对我的代码感到非常满意,这对于Chrome来说非常有挑战性。但

    我遇到的问题是它在其他地方比较慢(不是那么大),但是;最重要的是,似乎在ipad上崩溃;我需要我的游戏在ipad上运行...:/

    有谁知道为什么或有更好的解决方案?

    这是a demo of the game,这里是the javascript file,您可以在其中查看getObjectsUnderMouse()。

    欢迎任何建议!

1 个答案:

答案 0 :(得分:1)

虽然无光泽画布包含您需要进行测试的信息,但就内存而言,为每个遮罩保留一个完整尺寸的画布是很昂贵的。为每个遮罩保留画布可能会占用比iPad更多的资源。

这是一种大大减少内存使用量的方法:

首先,从每个对象中裁剪出任何额外的透明空间。例如,您的沙发是600x400 = 240000像素,但裁剪掉空白区域会将图像缩小为612x163 = 99756像素。与原始图像尺寸相比节省了58%。较少的像素意味着较少的内存用于遮罩。

不是为每个对象保留一个完整大小的画布,而是为每个对象保留一个数组,该数组仅包含该图像中每个像素的不透明度。数组值1表示像素是不透明的(并且是对象的一部分)。数组值0表示像素是透明的(对象的任何部分都不在此像素处)。

然后针对像素阵列进行点击测试,而不是对无光泽画布进行点击测试。

如果以z-index顺序测试数组,您甚至可以判断哪个对象位于另一个对象之上。

这是示例代码和演示:

var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var $canvas=$("#canvas");
var canvasOffset=$canvas.offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;

// display which object the mouse is over
var $result=$('#result');

// create an array of target objects
var targets=[];
targets.push({ name:'couch', x:25, y:50, hitArray:[], url:'https://dl.dropboxusercontent.com/u/139992952/multple/couch.png' });
targets.push({ name:'lamp', x:50, y:30, hitArray:[], url:'https://dl.dropboxusercontent.com/u/139992952/multple/lamp.png' });
var imgCount=targets.length;

// load the image associated with each target object
for(var i=0;i<targets.length;i++){
  var t=targets[i];
  t.image=new Image();
  t.image.crossOrigin='anonymous';
  t.image.index=i;
  t.image.onload=start;
  t.image.src=t.url;   
}

// this is called when each image is fully loaded
function start(){

  // return if all target images are not loaded
  if(--imgCount>0){return;}

  // make hit arrays for all targets
  for(var i=0;i<targets.length;i++){
    var t=targets[i];
    t.hitArray=makeHitArray(t.image);
  }

  // resize the canvas back to its original size
  canvas.width=cw;
  canvas.height=ch;   

  // draw all targets on the canvas
  for(var i=0;i<targets.length;i++){
    var t=targets[i];
    t.width=t.image.width;
    t.height=t.image.height;
    ctx.drawImage(t.image,t.x,t.y);
  }

  // listen for events
  $("#canvas").mousemove(function(e){handleMouseMove(e);});

}

// Draw a target image on a canvas
// Get the imageData of that canvas
// Make an array containing the opacity of each pixel on the canvas
// ( 0==pixel is not part of the object, 1==pixel is part of the object)
function makeHitArray(img){
  var a=[];
  canvas.width=img.width;
  canvas.height=img.height;
  ctx.drawImage(img,0,0);
  var data=ctx.getImageData(0,0,canvas.width,canvas.height).data;
  for(var i=0;i<data.length;i+=4){
    // if this pixel is mostly opaque push 1 else push 0
    a.push(data[i+3]>250?1:0);
  }
  return(a);
}


function handleMouseMove(e){

  // tell the browser we're handling this event
  e.preventDefault();
  e.stopPropagation();

  // get the mouse position
  mouseX=parseInt(e.clientX-offsetX);
  mouseY=parseInt(e.clientY-offsetY);

  // Test the mouse position against each object's pixel array
  // Report hitting the topmost object if 2+ objects overlap
  var hit='Not hovering';
  for(var i=0;i<targets.length;i++){
    var t=targets[i];
    var imgX=mouseX-t.x;
    var imgY=mouseY-t.y;
    if(imgX<=t.width && imgY<=t.height){
      var hitArrayIndex=imgY*t.width+imgX;
      if(hitArrayIndex<t.hitArray.length-1){
        if(t.hitArray[hitArrayIndex]>0){
          hit='Hovering over '+t.name;
        }
      }     
    }
  }

  $result.text(hit);

}
body{ background-color: ivory; padding:10px; }
#canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4 id='result'>Move mouse over objects.</h4>
<canvas id="canvas" width=450 height=250></canvas>