Javascript 文本碰撞检测

Javascript 文本碰撞检测,javascript,canvas,fonts,collision-detection,Javascript,Canvas,Fonts,Collision Detection,我正在构建一个web应用程序,它使用fillText在HTML5画布上绘制一组不同字体的字母。用户将单击画布上的某个位置,我需要检查他们单击了哪个字母(或者他们是否单击了某个字母) 我想我需要: 获取每个字母的向量路径(我不知道如何做) 使用一些简单的碰撞检测算法检查单击点是否在字母路径内 是否有一些简单的功能来完成这一点,我错过了?或者像这样的图书馆?如果没有任何库,如何获取特定字体的字母路径,以便自己进行检查 我需要使用字母的实际形状,而不是它的边界框,因为我不希望用户能够在 o>代码中点击

我正在构建一个web应用程序,它使用
fillText
在HTML5
画布上绘制一组不同字体的字母。用户将单击画布上的某个位置,我需要检查他们单击了哪个字母(或者他们是否单击了某个字母)

我想我需要:

  • 获取每个字母的向量路径(我不知道如何做)
  • 使用一些简单的碰撞检测算法检查单击点是否在字母路径内
  • 是否有一些简单的功能来完成这一点,我错过了?或者像这样的图书馆?如果没有任何库,如何获取特定字体的字母路径,以便自己进行检查

    我需要使用字母的实际形状,而不是它的边界框,因为我不希望用户能够在<代码> o>代码中点击,并且它登记为命中。< /P> 如有此方面的任何提示,将不胜感激。

    Logic 如果不为画布提供自定义逻辑,则无法在画布上处理单独的字母。绘制到画布上的所有内容都将合并为一组像素

    不幸的是,您不能将文本添加为纯路径,因此必须检查像素值。否则,您可以简单地将文本添加到新路径,并对每个字母使用
    isPointInPath
    方法

    一种方法 我们无法在此提供完整的解决方案,但这里有一个基础,您可以在此基础上构建,以提供在画布上单击单个字母的基本逻辑:

    • 每个字母都存储为对象,包括其位置、大小、字体和字符,但也有一个矩形命中区域(见下文)
    • 使用这些对象定义数组,然后将它们传递给渲染函数
    • 当您注册一个点击时,在数组中迭代,并对矩形命中区域进行测试,如果在内部,则检查像素(*)
    *)要区分重叠的字母,您需要按优先级进行检查。您还可以在单独的画布上渲染此字符,以便仅获取此字符的像素。我不会在演示中展示这个,但你会明白的

    演示

    更新的检查像素功能

    此更新的检查能够检查字母,即使它们在同一区域重叠

    我们创建一个单独的画布来绘制字母。这将隔离字母,当我们选择一个像素时,我们只能从该特定字母中获得一个像素。背景颜色也不重要,因为我们的屏幕外画布只为字母设置像素,而不是在检查期间设置背景。开销很小

    function checkPixel(o, x, y) {
    
        /// create off-screen canvas        
        var oc = document.createElement('canvas'),
            octx = oc.getContext('2d'),
            data,
            oldX = o.x,
            oldY = o.y;
       
        /// default canvas is 300x150, adjust if letter size is larger *)
        //oc.width = oc.height = 200;
        
        /// this can be refactored to something better but for demo...
        o.x = 0;
        o.y = 0;
    
        setLetterObject(octx, o, '#000');
        
        o.x = oldX;
        o.y = oldY;
    
        data = octx.getImageData(x - oldX, y - oldY, 1, 1).data;
        return (data[3] !== 0);
    }
    
    *)创建画布时,默认大小为300x150。为了避免重新分配一个新的位图,我们只需保持原样,因为内存已经分配给它了,我们只需要从中选取一个像素。如果字母的像素大小大于默认大小,我们当然需要重新分配以使字母适合


    在本演示中,我们临时覆盖x和y位置。对于生产,您应该启用
    setLetterObject
    方法以某种方式覆盖它,因为这样会更加优雅。但我将在演示中保持原样,因为最重要的是理解其原理。

    我想说的是,最好的选择是实际使用像素,顺便说一句,像素是您可以做到的最准确的事情(记住,用户在单击时看到的是像素,仅此而已)

    由于您不能直接使用颜色(因为可以有许多具有相同颜色的文本对象(也可能有具有相同颜色的其他基本体),因此可以使用单独的“拾取”画布

    基本上,当您在“重绘”功能的主画布上绘制对象时,您也会在另一个大小完全相同的隐藏画布上绘制对象,但您使用每个实体的唯一颜色绘制对象。 因此,您可以在画布上拥有多达1600万个实体(24位),通过在颜色代码和实体本身之间保留一个映射,您可以立即知道单击了哪个实体。顺便说一句,这种映射通常用于CAD应用程序,以加快拾取速度

    唯一有点恼人的是,在画布上绘制时,没有可移植的方法来禁用抗锯齿,因此从拾取画布返回的颜色可能不是您使用的颜色之一,或者更糟糕的是,通过单击实体的边界,可能会认为选择了不同的不相关实体


    这应该是一个非常罕见的事件,除非您的显示非常拥挤,而且拾取基本上是随机的。

    您可以使用画布获得位置的像素颜色吗?(我不使用画布)。如果是这样,在大多数情况下,这将允许您使用颜色来区分字母(不包括相同颜色的重叠)-如果需要,它还可以与简单的“框绑定”检查配对。@user2246674抱歉,我将绘制的大多数文本都是相同的颜色(黑色背景上的白色),我确实需要它来处理重叠字符。仅供参考:单击将“选择”字母(将其颜色更改为“选定”样式)@gamealchest我同意。我看到像素是相互作用的,但这两种解决方案的问题是,它们要么需要我将对象放在单独的画布上,要么无法选择字母边界框后面的对象。我可以从字体族和字体中获得的向量中绘制每个字符吗这是有帮助的,但是当我有两个字母部分重叠时,我不能点击后面的一个。第一个边界框挡出了后面的文本。我需要能够在一个巨大的<代码> o>代码下点击,例如一个小的<代码> m <代码>,即使<代码> o>代码>在上面。我会考虑<代码>校验像素< /代码>。哦。你提到了将文本添加为路径,我想这就是我想要的。你能举个例子吗?@SuperScript这是我想要的
    demo.onclick = function(e) {
    
        /// adjust mouse position to be relative to canvas
        var rect = demo.getBoundingClientRect(),
            x = e.clientX - rect.left,
            y = e.clientY - rect.top,
            i = 0, o;
        
        /// iterate
        for(;o = ltrs[i]; i++) {
    
            /// is in rectangle? "Older" letters has higher priority here...
            if (x > o.x && x < (o.x + o.rect[2]) &&
                y > o.y && y < (o.y + o.rect[3])) {
    
                /// it is, check if we actually clicked a letter
                /// This is what you would adopt to be on a separate canvas...    
                if (checkPixel(x, y) === true) {
                    setLetterObject(o, '#f00')
                    return;
                }
            }
        }
    }
    
    function checkPixel(x, y) {
        var data = ctx.getImageData(x, y, 1, 1).data;
        return (data[3] !== 0);
    }
    
    function checkPixel(o, x, y) {
    
        /// create off-screen canvas        
        var oc = document.createElement('canvas'),
            octx = oc.getContext('2d'),
            data,
            oldX = o.x,
            oldY = o.y;
       
        /// default canvas is 300x150, adjust if letter size is larger *)
        //oc.width = oc.height = 200;
        
        /// this can be refactored to something better but for demo...
        o.x = 0;
        o.y = 0;
    
        setLetterObject(octx, o, '#000');
        
        o.x = oldX;
        o.y = oldY;
    
        data = octx.getImageData(x - oldX, y - oldY, 1, 1).data;
        return (data[3] !== 0);
    }