使用CAShapeLayer的HitTest不起作用

时间:2013-06-09 11:02:42

标签: ios path collision-detection hittest cashapelayer

我使用以下代码检查玩家在圆圈区域中的位置:

if ([circle.presentationLayer hitTest:player.position])
{
    NSLog(@"hit");
}

我的圈子是CAShapeLayer:

CAShapeLayer *circle = [CAShapeLayer layer];
CGFloat radius = 50; 
[circle setMasksToBounds:YES];
[circle setBackgroundColor:[UIColor redColor].CGColor];
[circle setCornerRadius:radius1];
[circle setBounds:CGRectMake(0.0f, 0.0f, radius *2, radius *2)];
[self.view.layer addSublayer:circle];

碰撞检测非常有效。

现在我不想用圆形图层测试玩家的位置,但是沿着自定义路径绘制了CAShapeLayer:

CAShapeLayer *customLayer = [CAShapeLayer layer];
customLayer.path = customPath.CGPath;
customLayer.fillColor = [UIColor yellowColor].CGColor;
customLayer.shouldRasterize = YES;
customLayer.opacity = 0.2;
[self.view.layer addSublayer: customLayer];

当我想用自定义图层测试玩家的位置时,hittest不再有效。

我该如何解决这个问题?

2 个答案:

答案 0 :(得分:2)

您是否将位置转换为正确的相对位置?

例如:

CGPoint layerPoint = [[dynamicView layer] convertPoint:touchLocation toLayer:sublayer];

另外,也许你需要CGPathContainsPoint

if(CGPathContainsPoint(shapeLayer.path, 0, layerPoint, YES))
{

答案 1 :(得分:1)

您正在指定圆圈的边界/框架,但不是指定贝塞尔曲线的边界/框架。我不知道这一点,直到我自己偶然发现它,但是当你创建一个形状并指定一个CAShapeLayer()的路径时,你必须分配CALayer的框架(或单独的位置和边界)因为根据Apple的文档,CALayer()的框架默认为CGZeroRect,并且在设置路径时不会自动更新。 hitTest()基于位置+边界(或框架),因此失败。文档说:

  

/ *返回包含点'p'的图层的最远后代。    *兄弟姐妹按从上到下的顺序进行搜索。 'p'被定义为    *在接收器最近的祖先的坐标空间中    *不是CATransformLayer(变换图层没有2D    *可以指定点的坐标空间)。 * /

     
      
  • (可空的CALayer *)hitTest:(CGPoint)p;
  •   
     

/ *如果图层的边界包含点'p',则返回true。 * /

     
      
  • (BOOL)containsPoint:(CGPoint)P;
  •   

快速单元测试显示了这一点(扩展CGPath很有用并包含在之后):

func testShapeLayer() {
    let layer = CAShapeLayer()
    let bezier = NSBezierPath(rect: NSMakeRect(0,0,10,10))
    layer.path = bezier.CGPath
    // remove the following line to FAIL the test
    layer.frame = NSMakeRect(0,0,10,10) 
    XCTAssertNotNil(layer.hitTest(CGPoint(x:5,y:5)))
    XCTAssertTrue(layer.contains(CGPoint(x:5,y:5)))
}

extension NSBezierPath {

public var CGPath: CGPath {
    let path = CGMutablePath()
    var points = [CGPoint](repeating: .zero, count: 3)
    for i in 0 ..< self.elementCount {
        let type = self.element(at: i, associatedPoints: &points)
        switch type {
        case .moveToBezierPathElement: path.move(to: CGPoint(x: points[0].x, y: points[0].y) )
        case .lineToBezierPathElement: path.addLine(to: CGPoint(x: points[0].x, y: points[0].y) )
        case .curveToBezierPathElement: path.addCurve(      to: CGPoint(x: points[2].x, y: points[2].y),
                                                            control1: CGPoint(x: points[0].x, y: points[0].y),
                                                            control2: CGPoint(x: points[1].x, y: points[1].y) )
        case .closePathBezierPathElement: path.closeSubpath()
        }
    }
    return path
}

}

然而,这可能不是你想要的:你希望路径用于hitTesting(),而不仅仅是框架。因此,我可能会为你的hitTesting例程添加一个扩展名:如果命中了CAShapeLayer(),就意味着它在框架内,那么你可以更具体,并检查CGPathContainsPoint(),就像提到的其他答案一样。