Обратная маскировка Swift SpriteKit не работает на устройстве, но работает в симуляторе
Код для выполнения этого довольно прост:
var cropNode = SKCropNode()
var shape = SKShapeNode(rectOf: CGSize(width:100,height:100))
shape.fillColor = SKColor.orange
var shape2 = SKShapeNode(rectOf: CGSize(width:25,height:25))
shape2.fillColor = SKColor.red
shape2.blendMode = .subtract
shape.addChild(shape2)
cropNode.addChild(shape)
cropNode.position = CGPoint(x:150,y:170)
cropNode.maskNode=shape
container.addChild(cropNode)
Тот же код, та же iOS, разные результаты = нет Буэно
2 ответа
Вот метод, который сгенерирует maskNode для вас, используя шейдеры:
func generateMaskNode(from mask:SKNode) -> SKNode
{
var returningNode : SKNode!
autoreleasepool
{
let view = SKView()
//First let's flatten the node
let texture = view.texture(from: mask)
let node = SKSpriteNode(texture:texture)
//Next apply the shader to the flattened node to allow for color swapping
node.shader = SKShader(fileNamed: "shader.fsh")
let texture2 = view.texture(from: node)
returningNode = SKSpriteNode(texture:texture2)
}
return returningNode
}
Требуется создать файл с именем shader.fsh, код внутри выглядит так:
void main() {
// Find the pixel at the coordinate of the actual texture
vec4 val = texture2D(u_texture, v_tex_coord);
// If the color value of that pixel is 0,0,0
if (val.r == 0.0 && val.g == 0.0 && val.b == 0.0) {
// Turn the pixel off
gl_FragColor = vec4(0.0,0.0,0.0,0.0);
}
else {
// Otherwise, keep the original color
gl_FragColor = val;
}
}
Чтобы использовать его, требуется, чтобы у вас были черные пиксели вместо альфа-канала в качестве средства определения того, что будет обрезано, поэтому вот как должен выглядеть ваш код:
var cropNode = SKCropNode()
var shape = SKShapeNode(rectOf: CGSize(width:100,height:100))
shape.fillColor = SKColor.orange
var shape2 = SKShapeNode(rectOf: CGSize(width:25,height:25))
shape2.fillColor = SKColor.orange
shape2.blendMode = .subtract
shape.addChild(shape2)
let mask = generateMaskNode(from:shape)
cropNode.addChild(shape)
cropNode.position = CGPoint(x:150,y:170)
cropNode.maskNode=mask
container.addChild(cropNode)
Причина, по которой вычитание работает на симуляторе, а не на устройстве, заключается в том, что симулятор вычитает альфа-канал, а устройство - нет. Устройство на самом деле ведет себя правильно, так как альфа не предполагается вычитать, она должна игнорироваться.
Обратите внимание, вам не нужно выбирать черный цвет для обрезки, вы можете изменить шейдер, чтобы выбрать любой цвет по вашему выбору, просто измените строку:
if (val.r == 0.0 && val.g == 0.0 && val.b == 0.0)
в цвет, который вы хотите. (Как и в вашем случае. Вы можете сказать r = 0 g = 1 b = 0, чтобы обрезать только на зеленом)
Результат приведенного выше кода на устройстве
Изменить: я хотел бы отметить, что вычитание смешивания не является необходимым, это также будет работать:
var cropNode = SKCropNode()
var shape = SKShapeNode(rectOf: CGSize(width:100,height:100))
shape.fillColor = SKColor.orange
var shape2 = SKShapeNode(rectOf: CGSize(width:25,height:25))
shape2.fillColor = SKColor.black
shape2.blendMode = .replace
shape.addChild(shape2)
let mask = generateMaskNode(from:shape)
cropNode.addChild(shape)
cropNode.position = CGPoint(x:150,y:170)
cropNode.maskNode=mask
container.addChild(cropNode)
Теперь возникает вопрос, что я не могу проверить, нужна ли мне даже моя функция. Следующий код в теории должен работать, так как он заменяет нижележащие пиксели на приведенный выше, поэтому теоретически альфа должна передаваться. Если кто-нибудь может проверить это, пожалуйста, дайте мне знать, если это работает.
var cropNode = SKCropNode()
var shape = SKShapeNode(rectOf: CGSize(width:100,height:100))
shape.fillColor = SKColor.orange
var shape2 = SKShapeNode(rectOf: CGSize(width:25,height:25))
shape2.fillColor = SKColor(red:0,green:0,blue:0,alpha:0)
shape2.blendMode = .replace
shape.addChild(shape2)
cropNode.addChild(shape)
cropNode.position = CGPoint(x:150,y:170)
cropNode.maskNode= shape.copy() as! SKNode
container.addChild(cropNode)
заменить только заменяет цвет не альфа
Поскольку обратная маскировка, по-видимому, изначально недоступна в SpriteKit (таким образом, что она работает на устройствах), я думаю, что следующее является наиболее близким ответом:
let background = SKSpriteNode(imageNamed:"stocksnap")
background.position = CGPoint(x:65, y:background.size.height/2)
addChild(background)
let container = SKNode()
let cropNode = SKCropNode()
let bgCopy = SKSpriteNode(imageNamed:"stocksnap")
bgCopy.position = background.position
cropNode.addChild(bgCopy)
let cover = SKShapeNode(rect: CGRect(x:0,y:0,width:200,height:200))
cover.position = CGPoint(x:80,y:150)
cover.fillColor = SKColor.orange
container.addChild(cover)
let highlight = SKShapeNode(rectOf: CGSize(width:100,height:100))
highlight.position = CGPoint(x:cover.position.x+cover.frame.size.width/2,y:cover.position.y+cover.frame.size.height/2)
highlight.fillColor = SKColor.red
cropNode.maskNode = highlight
container.addChild(cropNode)
addChild(container)
Вот скриншот с устройства, используя вышеописанную технику
Это просто использует дубликат фона, маскирует его и накладывает на него в том же положении, чтобы создать эффект обратной маскировки. В ситуациях, когда вы хотите дублировать все, что находится на экране, вы можете использовать что-то вроде этого:
func captureScreen() -> SKSpriteNode {
var image = UIImage()
if let view = self.view {
UIGraphicsBeginImageContextWithOptions(view.bounds.size, false, UIScreen.main.scale)
view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
if let imageFromContext = UIGraphicsGetImageFromCurrentImageContext() {
image = imageFromContext
}
UIGraphicsEndImageContext()
}
let texture = SKTexture(image:image)
let sprite = SKSpriteNode(texture:texture)
//scale is applicable if using fixed screen sizes that aren't the actual width and height
sprite.scale(to: CGSize(width:size.width,height:size.height))
sprite.anchorPoint = CGPoint(x:0,y:0)
return sprite
}
Надеюсь, кто-нибудь найдет лучший способ или сделано обновление для SpriteKit для поддержки обратного маскирования, но в то же время это отлично работает для моего варианта использования.