Swift SpriteKit обнаруживает штрихи, начатый на узле SKSprite с помощью SKPhysicsBody из текстуры Sprite - PullRequest
0 голосов
/ 14 мая 2018

У меня есть сцена SpriteKit со спрайтом.У спрайта есть физическое тело, полученное из альфы текстуры, чтобы получить точную физическую форму, например:

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 голосов
/ 14 мая 2018

Вы «не можете» (не легко)

UITouch команды основаны на CGRect, поэтому let hitNodes = self.nodes(at: touchLocation) будет заполнен любым узлом, чей кадр пересекается с этим прикосновением.

Этого нельзя избежать, поэтому следующим шагом является определение точности пикселей по узлам, которые зарегистрированы как "попадание".Первое, что вы должны сделать, это преобразовать позицию касания в локальные координаты вашего спрайта.

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

Затем с этого момента вам нужно выяснить, какой метод вы хотите использовать.

Если вам нужна скорость, я бы порекомендовал создать двумерный логический массив, который работает как маска, и залить этот массив значениями 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, заполнить текстуру этим контекстом, а затем проверить, является ли точка в контексте прозрачной или нет.

Что-то вроде этого поможет вам:

Как получить значение RGB для пикселя, используя 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)
           }

ИЛИ

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.

...