3

SKEffectionNodes have a shouldRasterise "switch" that bakes them into a bitmap, and doesn't update them until such time as the underlying nodes that are impacted by the effect are changed.

However I can't find a way to create an SKTexture from this rasterised "image".

Is it possible to get a SKTexture from a SKEffectNode?

Bhavin Ramani
  • 3,221
  • 5
  • 30
  • 41
Confused
  • 6,048
  • 6
  • 34
  • 75
  • Just what are you trying to do? A lot of game assets are built non-game time. So the question is can you create this outside of the game using other means versus generating them in game? – Mobile Ben Oct 19 '16 at 15:22
  • I'm making sexy shadows and glows for a bunch of buttons, many of them are differently coloured, and I want to change their colours dynamically, using the SKSpriteNode.color and blend amounts. Have figured out how to do it. There was a second problem, after the conceptual one, of the blurs of glows and shadows going outside of the bounds of the sprite and getting cropped in an ugly way. Have solved that, too. Bit hacky, but it works. Performance now much better, and have saved a bunch of nodes, too. – Confused Oct 19 '16 at 16:16
  • 1
    @MobileBen I couldn't have done it without your factory tips. That was the big step, having a separate space with an SKView to play with to do the rendering in a space away from the "screen". Not necessary, but gave me the mental headroom to figure out how to do it and not get the ugly crops – Confused Oct 19 '16 at 16:18
  • @MobileBen added answer, complete with designer consideration of coding indentation patterns – Confused Oct 19 '16 at 16:54

2 Answers2

2

I think you could try a code like this (it's just an example):

if let effect = SKEffectNode.init(fileNamed: "myeffect") {
    effect.shouldRasterize = true
    self.addChild(effect)   
    ...         
    let texture = SKView().texture(from: self)
}

Update:

After you answer, hope I understood better what do you want to achieve.

This is my point of view: if you want to make a shadow of a texture, you could simply create an SKSpriteNode with this texture:

let shadow = SKSpriteNode.init(texture: <yourTexture>)
shadow.blendMode = SKBlendMode.alpha
shadow.colorBlendFactor = 1
shadow.color = SKColor.black
shadow.alpha = 0.25

What I want to say is that you could proceed step by step:

  • get your texture
  • elaborate your texture (add filters, make some other effect..)
  • get shadow

This way of working produces a series of useful methods you could use in your project to build other kind of elements. Maybe, by separating the tasks you don't need to use texture(from:)

Alessandro Ornano
  • 34,887
  • 11
  • 106
  • 133
  • you may have to capture the texture at a later point, the effect may not happen at time of creation, since it may be done on the GPU, so no guarantees as to the time of execution. – Knight0fDragon Oct 19 '16 at 15:19
  • Correct, I was thinking about a sort of dispatch delay to wait rendering but is not secure we have the correct results and its ugly – Alessandro Ornano Oct 19 '16 at 15:42
  • You could always capture it on the next update I suppose, I am unaware of any "did finish drawing" methods – Knight0fDragon Oct 19 '16 at 15:44
0

I've figured this out, in a way that solves my problems, using a Factory.

Read more on how to make a factory, from BenMobile's patient and clear articulation, here: Factory creation and use for making Sprites and Shapes

There's an issue with blurring a SKTexture or SKSpriteNode in that it's going to run out of space. The blur/glow goes beyond the edges of the sprite. To solve this, in the below, you'll see I've created a "framer" object. This is simply an empty SKSpriteNode that's double the size of the texture to be blurred. The texture to be blurred is added as a child, to this "framer" object.

It works, regardless of how hacky this is ;)

Inside a static factory class file:

import SpriteKit

class Factory {

    private static let view:SKView = SKView()  // the magic. This is the rendering space

    static func makeShadow(from source: SKTexture, rgb: SKColor, a: CGFloat) -> SKSpriteNode {
        let shadowNode = SKSpriteNode(texture: source)
            shadowNode.colorBlendFactor = 0.5  // near 1 makes following line more effective
            shadowNode.color = SKColor.gray // makes for a darker shadow. White for "glow" shadow
        let textureSize = source.size()
        let doubleTextureSize = CGSize(width: textureSize.width * 2, height: textureSize.height * 2)
        let framer = SKSpriteNode(color: UIColor.clear, size: doubleTextureSize)
            framer.addChild(shadowNode)
        let blurAmount = 10
        let filter = CIFilter(name: "CIGaussianBlur")
            filter?.setValue(blurAmount, forKey: kCIInputRadiusKey)
        let fxNode = SKEffectNode()
            fxNode.filter = filter
            fxNode.blendMode = .alpha
            fxNode.addChild(framer)
            fxNode.shouldRasterize = true
        let tex = view.texture(from: fxNode) // ‘view’ refers to the magic first line
        let shadow = SKSpriteNode(texture: tex)  //WHOOPEE!!! TEXTURE!!!
            shadow.colorBlendFactor = 0.5
            shadow.color = rgb
            shadow.alpha = a
            shadow.zPosition = -1

        return shadow
    }
}

Inside anywhere you can access the Sprite you want to make a shadow or glow texture for:

shadowSprite = Factory.makeShadow(from: button, rgb: myColor, a: 0.33)
shadowSprite.position = CGPoint(x: self.frame.midX, y: self.frame.midY - 5)
addChild(shadowSprite)

- button is a texture of the button to be given a shadow. a: is an alpha setting (actually transparency level, 0.0 to 1.0, where 1.0 is fully opaque) the lower this is the lighter the shadow will be.

The positioning serves to drop the shadow slightly below the button so it looks like light is coming from the top, casting shadows down and onto the background.

Community
  • 1
  • 1
Confused
  • 6,048
  • 6
  • 34
  • 75