Swift SpriteKit使用Sprite纹理中的SKPhysicsBody检测SKSpriteNode上的TouchesBegan

时间:2018-05-14 08:15:09

标签: swift sprite-kit touch skphysicsbody hittest

我有一个带有精灵的SpriteKit场景。精灵有一个物理体,它来自纹理的alpha,以获得精确的物理形状,如下所示:

let texture_bottle = SKTexture(imageNamed:"Bottle")
let sprite_bottle = SKSpriteNode(texture: texture_bottle)
physicsBody_bottle = SKPhysicsBody(texture: texture_bottle, size: size)
physicsBody_bottle.affectedByGravity = false
sprite_bottle.physicsBody = physicsBody_bottle
root.addChild(sprite_bottle)

....

func touchesBegan(_ touches: Set<UITouch>?, with event: UIEvent?, touchLocation: CGPoint!) {

    let hitNodes = self.nodes(at: touchLocation)

}

当用户点击屏幕时,如何检测他们是否实际触及了物理体形状(不是精灵的直肠)?

1 个答案:

答案 0 :(得分:1)

你“不能”(不容易)

UITouch命令基于CGRects,因此let hitNodes = self.nodes(at: touchLocation)将填充框架与该触摸相交的任何节点。

这是无法避免的,因此下一步是从注册为“命中”的节点确定像素精度。你应该做的第一件事是将触摸位置转换为你的精灵的本地坐标。

for node in hitNodes
{
    //assuming touchLocation is based on screen coordinates
    let localLocation = node.convertPoint(touchLocation,from:scene)
}

然后从这一点开始,您需要确定要使用的方法。

如果你需要速度,那么我建议创建一个表现为掩模的2D布尔数组,并为透明区域填充此数组为false,对于不透明区域填充true。然后你可以使用localLocation指向数组的某个索引(记住将anchorPoint * width和height添加到x和y值然后转换为int)

func isHit(node: SKNode,mask: [[Boolean]],position:CGPoint) -> Boolean
{
    return mask[Int(node.size.height * node.anchorPoint.y + position.y)][Int(node.size.width * node.anchorPoint.x + position.x)]
} 

如果不考虑速度,则可以创建CGContext,将纹理填充到此上下文中,然后检查上下文中的点是否透明。

这样的事情会帮助你:

  

How do I get the RGB Value of a pixel using CGContext?

//: Playground - noun: a place where people can play

import UIKit
import XCPlayground

extension CALayer {

    func colorOfPoint(point:CGPoint) -> UIColor
    {
        var pixel:[CUnsignedChar] = [0,0,0,0]

        let colorSpace = CGColorSpaceCreateDeviceRGB()
        let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.PremultipliedLast.rawValue)

        let context = CGBitmapContextCreate(&pixel, 1, 1, 8, 4, colorSpace,bitmapInfo.rawValue)

        CGContextTranslateCTM(context, -point.x, -point.y)

        self.renderInContext(context!)

        let red:CGFloat = CGFloat(pixel[0])/255.0
        let green:CGFloat = CGFloat(pixel[1])/255.0
        let blue:CGFloat = CGFloat(pixel[2])/255.0
        let alpha:CGFloat = CGFloat(pixel[3])/255.0

        //println("point color - red:\(red) green:\(green) blue:\(blue)")

        let color = UIColor(red:red, green: green, blue:blue, alpha:alpha)

        return color
    }
}

extension UIColor {
    var components:(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
        var r:CGFloat = 0
        var g:CGFloat = 0
        var b:CGFloat = 0
        var a:CGFloat = 0
        getRed(&r, green: &g, blue: &b, alpha: &a)
        return (r,g,b,a)
    }
}


//get an image we can work on
var imageFromURL = UIImage(data: NSData(contentsOfURL: NSURL(string:"https://www.gravatar.com/avatar/ba4178644a33a51e928ffd820269347c?s=328&d=identicon&r=PG&f=1")!)!)
//only use a small area of that image - 50 x 50 square
let imageSliceArea = CGRectMake(0, 0, 50, 50);
let imageSlice  = CGImageCreateWithImageInRect(imageFromURL?.CGImage, imageSliceArea);
//we'll work on this image
var image = UIImage(CGImage: imageSlice!)


let imageView = UIImageView(image: image)
//test out the extension above on the point (0,0) - returns r 0.541 g 0.78 b 0.227 a 1.0
var pointColor = imageView.layer.colorOfPoint(CGPoint(x: 0, y: 0))



let imageRect = CGRectMake(0, 0, image.size.width, image.size.height)

UIGraphicsBeginImageContext(image.size)
let context = UIGraphicsGetCurrentContext()

CGContextSaveGState(context)
CGContextDrawImage(context, imageRect, image.CGImage)

for x in 0...Int(image.size.width) {
    for y in 0...Int(image.size.height) {
        var pointColor = imageView.layer.colorOfPoint(CGPoint(x: x, y: y))
        //I used my own creativity here - change this to whatever logic you want
        if y % 2 == 0 {
            CGContextSetRGBFillColor(context, pointColor.components.red , 0.5, 0.5, 1)
        }
        else {
            CGContextSetRGBFillColor(context, 255, 0.5, 0.5, 1)
        }

        CGContextFillRect(context, CGRectMake(CGFloat(x), CGFloat(y), 1, 1))
    }
}
CGContextRestoreGState(context)
image = UIGraphicsGetImageFromCurrentImageContext()

您最终会调用colorOfPoint(point:localLocation).cgColor.alpha > 0来确定您是否正在触摸某个节点。

现在我建议你将colorOfPoint作为SKSpriteNode的扩展,所以请使用上面发布的代码进行创意。

func isHit(node: SKSpriteNode,position:CGPoint) -> Boolean
{
    return node.colorOfPoint(point:localLocation).cgColor.alpha > 0
} 

您的最终代码如下所示:

hitNodes = hitNodes.filter
           {
               node in
               //assuming touchLocation is based on screen coordinates
               let localLocation = node.convertPoint(touchLocation,from:node.scene)
               return isHit(node:node,mask:mask,position:localLocation)
           }

OR

hitNodes = hitNodes.filter
           {
               node in
               //assuming touchLocation is based on screen coordinates
               let localLocation = node.convertPoint(touchLocation,from:node.scene)
               return isHit(node:node,position:localLocation)
           }

基本上过滤掉帧比较检测到的所有节点,留下像素完美触摸节点。

注意:单独的SO链接中的代码可能需要转换为Swift 4。