原文:Pixel accurate collision detection with Javascript and Canvas

译者:nzbin

我正在开发一个需要再次使用碰撞检测的游戏。我通常会使用简单高效的盒模型碰撞检测。盒子模型的主要原则就是把所有的物体都抽象成正方形,如果两个正方形有重叠,就认为是一次碰撞。这通常是一个简单的游戏所需要的。但是因为这种模型我之前用过多次,我想尝试一些更深刻更准确的方法。

我选择从像素级层面来看是否发生了碰撞。首先我要了解“像素是什么”。我测试的元素透明度都不为 0,换句话说,所有的可见像素都被看做一个碰撞点。为了提高算法效率,我预先创建了一张图片的像素映射图。换句话说,就是一个数组内包含了屏幕上的所有可见像素。

    /* 描述像素图的伪代码 */
var pixelMap = [];
for( var y = 0; y < image.width; y++ ) {
for( var x = 0; x < image.height; x++ ) {
// 获取当前位置的元素
var pixel = ctx.getImageData( x, y, 1, 1 );
// 判断透明度不为0
if( pixel.data[3] != 0 ) {
pixelMap.push( { x:x, y:y } );
}
}
}
return pixelMap;

用这种方法,一张小图片会变得很大。一张 40X40 的图片会有 1600 像素,所以如果我在一个很大的 canvas 上做碰撞检测将会非常缓慢。测试之前我先将盒子模型重叠起来,如果点击测试返回 true,我会进一步测试是否有像素重叠。这意味着我们只需要测试一次。

    /* 盒模型测试, 碰撞返回 true */
function hitBox( source, target ) {
/* 源物体和目标物体都包含 x, y 以及 width, height */
return !(
( ( source.y + source.height ) < ( target.y ) ) ||
( source.y > ( target.y + target.height ) ) ||
( ( source.x + source.width ) < target.x ) ||
( source.x > ( target.x + target.width ) )
);
}

如果 hitBox 函数返回 true,我们需要比较两个物体的预渲染像素图。然后我们需要测试源物体的每一个像素是否与目标物体的像素有重叠。这是一个非常耗时耗能的函数。其实源物体的每个像素与目标物体的每个像素的匹配需要检测 n*x 次。假如我们匹配两个 40*40 像素的正方形,最坏的情况就是,经过 2560000 次的计算而没有得到一次匹配。

    /* 像素碰撞检测的伪代码 */
function pixelHitTest( source, target ) {
// 循环源图像的所有像素
for( var s = 0; s < source.pixelMap.length; s++ ) {
var sourcePixel = source.pixelMap[s];
// 添加位置偏移
var sourceArea = {
x: sourcePixel.x + source.x,
y: sourcePixel.y + source.y,
width: 1,
height: 1
}; // 循环目标图像的所有像素
for( var t = 0; t < target.pixelMap.length; t++ ) {
var targetPixel = target.pixelMap[t];
// 添加位置偏移
var targetArea = {
x: targetPixel.x + target.x,
y: targetPixel.y + target.y,
width: 1,
height: 1
}; /* 使用之前提到的 hitbox 函数 */
if( hitBox( sourceArea, targetArea ) ) {
return true;
}
}
}
}

当我把物体描绘出来,我几乎没有时间测试物体是否发生了碰撞。如果我们想要一个平滑的 60 帧动画(我相信大多数浏览器倾向于requestAnimationFrame函数),除了浏览器进程和帧渲染的时间,理论上我们测试两帧的时间只有 16.6ms(实际的时间更少)。

为了解决这个问题,我们可以使用更大的分辨率。我们可以测试一组像素而不是单个像素。所以如果我们在像素图渲染器和像素碰撞测试中使用更大的分辨率,我们必须把计算量降到一个合理的数字上。

   /* 描绘更大分辨率像素图的伪代码 */
function generateRenderMap( image, resolution ) {
var pixelMap = [];
for( var y = 0; y < image.width; y=y+resolution ) {
for( var x = 0; x < image.height; x=x+resolution ) {
// 获取当前位置的像素群
var pixel = ctx.getImageData( x, y, resolution, resolution ); // 判断像素群的透明度不为0
if( pixel.data[3] != 0 ) {
pixelMap.push( { x:x, y:y } );
}
}
}
return {
data: pixelMap,
resolution: resolution
};
} /* 像素碰撞测试伪代码 */
function pixelHitTest( source, target ) { // 源对象和目标对象包含两张属性
// { data: a render-map, resolution: The precision of the render-map} // 循环源对象的所有像素
for( var s = 0; s < source.pixelMap.data.length; s++ ) {
var sourcePixel = source.data.pixelMap[s];
// 添加位置偏移
var sourceArea = {
x: sourcePixel.x + source.x,
y: sourcePixel.y + source.y,
width: target.pixelMap.resolution,
height: target.pixelMap.resolution
}; // 循环源对象的所有像素
for( var t = 0; t < target.pixelMap.data.length; t++ ) {
var targetPixel = target.pixelMap.data[t];
// 添加位置偏移
var targetArea = {
x: targetPixel.x + target.x,
y: targetPixel.y + target.y,
width: target.pixelMap.resolution,
height: target.pixelMap.resolution
}; /*使用之前提到的 hitbox 函数 */
if( hitBox( sourceArea, targetArea ) ) {
return true;
}
}
}
}

同样的 40X40 的像素块如今只有 100 组像素点,而之前是有1600像素的图像。我们将 2650000 次的计算量降低到 10000 次的计算量,只有原始 计算量的 0.39%。如果你有更多不同分辨率的渲染图,你会建立精度更高的系统,从分辨率大的像素群开始依次计算,当然系统的复杂度也会逐渐提高。在两个 40X40 像素的圆形物体上使用3的分辨率(13.33X13.33),当前的方案在最差的碰撞测试中会耗时 1-2ms。

使用 JavaScript 和 canvas 做精确的像素碰撞检测的更多相关文章

  1. 初探Javascript之Canvas

    什么是Canvas <canvas>是 HTML5 新增的元素,可使用JavaScript脚本来绘制图形. canvas是一个矩形区域,您可以控制其每一像素. 引入Canvas ```ht ...

  2. html5 canvas做的图表插件

    用highchart的时候发现它是用svg来画图的,那么用canvas来做怎么样的. 以前做AS图表插件的时候,绘制图画主要用容器的Graphics对象来绘制,而canvas的context和Grap ...

  3. JavaScript修改Canvas图片

    用JavaScript修改Canvas图片的分辨率(DPI)   应用场景: 仓库每次发货需要打印标签, Canvas根据从数据库读取的产品信息可以生成标签JPG, 但是这个JPG图片的默认分辨率(D ...

  4. JavaScript+html5 canvas实现本地截图教程

    这篇文章主要介绍了JavaScript+html5 canvas实现本地截图教程,对截图功能感兴趣的小伙伴们可以参考一下 最近有时间了解了下html5的各API,发现新浪微博的头像设置是使用canva ...

  5. 用Canvas做动画

    之前看过不少HTML5动画的书,讲解的是如何去做,对于其中的数学原理讲解的不详细,常有困惑.最近看的<HTML5+JavaScript 动画基础>这个是译本,Keith Peters曾写过 ...

  6. 我们能用canvas做什么?

    什么是Canvas? Canvas元素是HTML5的一部分,允许脚本语言动态渲染位图像.Canvas由一个可绘制地区HTML代码中的属性定义决定高度和宽度.JavaScript代码可以访问该地区,通过 ...

  7. canvas做的时钟,学习下

    canvas标签只是图形容器,您必须使用脚本来绘制图形. getContext() 方法可返回一个对象,该对象提供了用于在画布上绘图的方法和属性.——获取上下文对象. getContext(" ...

  8. HTML5 Canvas 获取网页的像素值。

    我之前在网上看过一个插件叫做出JScolor   颜色拾取器  说白了就是通过1*1PX的DOM设置颜色值通过JS来获取当前鼠标点击位置DOM的颜色值. 自从HTML5 画布出来之后.就有更好的方法来 ...

  9. 除了Web和Node,JavaScript还能做什么

    前言 提起JavaScript,我们也许经常会想到的是,可以用来写Web页面嘛,又或者,会想起Node.js 这个服务端环境,搞前后端同构. 那么,除此之外, JavaScript还可以做什么?   ...

随机推荐

  1. 分享一个SQLSERVER脚本(计算数据库中各个表的数据量和每行记录所占用空间)

    分享一个SQLSERVER脚本(计算数据库中各个表的数据量和每行记录所占用空间) 很多时候我们都需要计算数据库中各个表的数据量和每行记录所占用空间 这里共享一个脚本 CREATE TABLE #tab ...

  2. TODO:Laravel 使用blade标签布局页面

    TODO:Laravel 使用blade标签布局页面 本文主要介绍Laravel的标签使用,统一布局页面.主要用到到标签有@yield,@ stack,@extends,@section,@stop, ...

  3. Linux中进行单文件内容的复制

    文件内容复制的常规方法: 开辟一段空间,不断读取文件的内容并写入另一文件当中,这种方法好在安全,一般在类型允许的最大范围内是安全的,缺点就是复制内容的时间长 一次性复制文件的内容,这种方法必须首先获取 ...

  4. 在.Net中实现自己的简易AOP

    RealProxy基本代理类 RealProxy类提供代理的基本功能.这个类中有一个GetTransparentProxy方法,此方法返回当前代理实例的透明代理.这是我们AOP实现的主要依赖. 新建一 ...

  5. 冒泡,setinterval,背景图的div绑定事件,匿名函数问题

    1.会冒泡到兄弟元素么? $(function(){ $("#a").click(function(){alert("a")}) $("#b" ...

  6. ASP.NET MVC5+EF6+EasyUI 后台管理系统(64)-WebApi与Unity注入

    系列目录 前言: 有时候我们系统需要开放数据给手机App端或其他移动设备,不得不说Asp.net WebApi是目前首选 本节记录Asp.net MVC WebApi怎么利用Unity注入.系列开头已 ...

  7. 谈谈一些有趣的CSS题目(一)-- 左边竖条的实现方法

    开本系列,讨论一些有趣的 CSS 题目,抛开实用性而言,一些题目为了拓宽一下解决问题的思路,此外,涉及一些容易忽视的 CSS 细节. 解题不考虑兼容性,题目天马行空,想到什么说什么,如果解题中有你感觉 ...

  8. CRL快速开发框架系列教程三(更新数据)

    本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...

  9. ABAP实现屏幕自己刷新和跳转功能

    ABAP开发工程中,有时候需要让跳转出的屏幕自动实现跳转和刷新的功能,该功能的实现需要在屏幕PBO 里面调用相应的事件执行. 关键代码为: SET TITLEBAR ' 屏幕自动程序'. IF g_c ...

  10. kvm 使用入门详解

    kvm 是虚拟化技术的一个典型实现,功能非常强大,使用很方便.kvm 本身主要实现对 CPU 的虚拟化,内存和IO的虚拟化使用了开源软件 qemu,qemu 是纯软件层面的虚拟化,其实就是个模拟器.k ...