cocos2d 多边形触摸检测

发布时间:2026/6/25 23:10:07
cocos2d 多边形触摸检测 业务上会遇到这么一种情况不规则图片需要添加触摸事件这个时候点击图片的空白区域也会有触摸事件进来这个时候如果我想只在有像素的地方点击有事件进来那么要怎么处理呢比如下图找了一下资料有2种方法方案一射线法Ray Casting Algorithm- 通用性最强这是最经典且适用于任意多边形凸多边形和凹多边形的算法。其原理是从触摸点向任意方向通常向右发射一条射线统计射线与多边形边相交的次数。如果相交次数为‌奇数‌则点在多边形内如果是‌偶数‌则在多边形外。方案二像素级检测Alpha 检测- 适合复杂异形图片如果形状极其不规则且难以用多边形描述可以通过读取图片像素的 Alpha 值来判断。如果触摸点对应的像素 Alpha 0则视为击中。‌注意‌此方法性能开销较大不建议在每帧或大量对象中使用通常用于静态按钮。需要修改 C 底层或使用RenderTexture获取像素数据纯 Lua 实现较困难且效率低。一般建议优先使用多边形近似。那么这里只讲一下方案一的实现逻辑1.先用DrawNode画多边形前提需要把顶点坐标找到。2.用射线法检测触摸点是否在多边形内。可以看一下我画的图顶点数越多一般就和多边形越贴合我本来3个顶点就够了只是为了试试而已不止如此我还试了凹多边形虽然drawNode不支持渲染凹多边形只支持凸多边形但不影响射线法的判断。至于这个射线法我也去研究了一下一、核心原理射线法的核心逻辑是‌交点数奇偶性判定‌从待判断点向任意方向通常选水平向右引一条无限延伸的射线统计该射线与多边形所有边的交点总数。若交点数为奇数说明点在多边形内部若为偶数则点在多边形外部。背后的拓扑逻辑是射线每穿越一次多边形边界点的内外状态就会翻转一次从初始的外部0次偶数经过奇数次翻转后最终会停留在内部区域。二、关键前提多边形的顶点必须按顺时针或逆时针的顺序环绕排列且首尾顶点闭合否则算法会直接失效。该方法天然支持凹多边形也可适配带孔多边形外环用射线法判定为内部后再对每个内环单独判定点落在任意内环内则最终结果为外部。三、特殊情况处理射线刚好穿过多边形顶点通过规则过滤仅统计边的纵坐标较大的上端点作为有效交点避免重复计数导致结果错误。射线与多边形某条边完全重合直接跳过该水平边避免无效计算和除零错误。点落在多边形的边上可在主逻辑外单独增加点在线段上的判断根据业务需求将其判定为内部或外部。下面贴一下测试代码local test class(test, function() return display.newNode() end) function test:ctor(param) self:setTouchEnabled(true) self:setNodeEventEnabled(true) self:setTouchSwallowEnabled(true) self:initUI() end function test:initUI() self:setContentSize(cc.size(display.width,display.height)) local colorLayer cc.LayerColor:create(cc.c4b(255,255,255,255)) :addTo(self) :align(display.CENTER,0,0) :size(display.width*1.5,display.height*1.5) self.sprite display.newSprite(#xh_role_02_lock.png) :addTo(self) :align(display.CENTER,display.cx,display.cy) self.verts { cc.p(-230, -170), -- 顶点 A cc.p(350, 180), -- 顶点 B cc.p(-385, 180), -- 顶点 C cc.p(-250, 50), -- 顶点 D } local drawNode cc.DrawNode:create() self:addChild(drawNode) -- 假设 self 是当前 Layer 或 Scene drawNode:setPosition(self.sprite:getPositionX(),self.sprite:getPositionY()) self.drawNode drawNode --self.drawNode:hide() -- 2. 定义顶点 (顺时针或逆时针均可建议保持一致) local verts self.verts -- 3. 定义颜色 local fillColor cc.c4f(1, 0, 0, 1) -- 半透明红色填充 cc.c4f(1, 0, 0, 0.5) local borderColor cc.c4f(0, 0, 0, 1) -- 黑色边框 cc.c4f(0, 0, 0, 1) local borderWidth 2 -- 边框宽度 -- 4. 绘制多边形 drawNode:drawPolygon(self.verts,{fillColor fillColor, borderWidth borderWidth, borderColor borderColor}) self:setTouchEnabled(true) self:setTouchSwallowEnabled(true) self:addNodeEventListener(cc.NODE_TOUCH_EVENT, handler(self, self.onTouch)) end function test:onTouch(event) local name,x,y event.name, event.x, event.y if name began then return true elseif name moved then elseif name ended then local location cc.p(x,y) local localPos self.drawNode:convertToNodeSpace(location) if self:isPointInPolygon(localPos, self.verts) then print( 触摸点在多边形内部) else print( 触摸点在多边形外部。) end end end function test:isPointInPolygon(pos, points) local nCross 0 -- 交点计数器 local count #points for i 1, count do local p1 points[i] local p2 points[(i % count) 1] -- 下一个点最后一个点连回第一个点 -- 求解 yp.y 与线段 p1p2 的交点 -- 1. 判断线段是否跨越水平线 p.y -- 如果两点都在水平线同侧则不相交 if (p1.y pos.y and p2.y pos.y) or (p1.y pos.y and p2.y pos.y) then -- 2. 计算交点的 x 坐标 -- 直线方程两点式: (x - x1)/(x2 - x1) (y - y1)/(y2 - y1) -- 推导 x: x x1 (y - y1) * (x2 - x1) / (y2 - y1) local xIntersect p1.x (pos.y - p1.y) * (p2.x - p1.x) / (p2.y - p1.y) -- 3. 判断交点是否在测试点的右侧 -- 如果交点在右侧说明射线穿过了一条边 if xIntersect pos.x then nCross nCross 1 end end end -- 如果交点数为奇数则在多边形内偶数则在外部 return (nCross % 2 1) end return test判断交点的时候判断的是右侧其实左侧也是一样的拿上纸笔试试看还真是那么回事但这个方法也是有弊端的‌特殊边界判定复杂‌当射线恰好经过多边形顶点、与多边形边重合时常规奇偶计数逻辑会失效必须额外编写代码处理这类临界情况否则会直接出现检测错误。‌性能开销随顶点数上升‌算法需要遍历多边形的所有边做相交判断若多边形顶点数量多单次检测的计算量会明显增加大量对象同时检测时容易出现性能卡顿。‌无法直接处理非简单多边形‌对于存在自相交的复杂多边形射线法的奇偶计数规则会完全失效不能直接得到正确的点内外判定结果。‌点在边上的判定冗余‌射线法本身无法直接识别点落在多边形边上的情况必须额外单独编写逻辑做前置判断增加了代码的复杂度。