我有一个带有精灵的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)
}
当用户点击屏幕时,如何检测他们是否实际触及了物理体形状(不是精灵的直肠)?
答案 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。