Search code examples
iosswiftsprite-kittexturesskspritenode

Superimpose two textures on an SKSpriteNode


I would like to achieve the effect shown in this gif.

Shiny Sprite

Currently I do this with a series of ~7 png images with a red background and a white line, these are animated through the sprite with an SKAction.

There are a few others additions I would like to make to the sprite, that can change depending on situation, and also I would like to repeat this with a number of colours. This results in: 6 colours, 7 shine effects, 5 edge effects and 4 corner effects resulting in 136 combinations of textures I would need to create and store.

I feel like there has to be a way to superimpose png's with transparent backgrounds when setting the texture of a sprite but I cannot seem to find a way to do this anywhere.

Is this possible so that I can reduce the number of assets to 22 or do I have to make all 136 and build in logic to the class to decide which to use?


Solution

  • I wanted an effect like this for my game, I tried a lot of options. I tried using particles for performance but couldn't even get close. I know you can accomplish it with Shaders, but i didn't go that route and in iOS 12 Shaders won't be support Open GL anyway. In the end I opted to go with CropNodes.

    This is my glare image, it is hard to see because it slightly transparent whiteish image.

    enter image description here

    This is the results I achieved using CropNodes

    enter image description here

    class Glare: SKSpriteNode {
    
        var glare = SKSpriteNode()
        private var cropNode = SKCropNode()
    
        init(color: UIColor, size: CGSize) {
    
            super.init(texture: nil, color: color, size: size)
    
            alpha = 0.7
            zPosition = 10
    
            setupGlare()
        }
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
        func setupGlare() {
    
            let buffer: CGFloat = 10
    
            let mask = SKSpriteNode(texture: nil, color: .black, size: self.size)
    
            let texture = SKTextureAtlas(named: "Sprites").textureNamed("glare")
            glare = SKSpriteNode(texture: texture)
            glare.position = CGPoint(x: 0 - (self.size.width / 2 + buffer), y: self.size.height / 2 + buffer)
            glare.setScale(3.50)
            glare.zPosition = 1
    
            cropNode.zPosition = 2
            cropNode.maskNode = mask
            cropNode.addChild(glare)
    
            let random = Double(CGFloat.random(min: 0, max: 1))
    
            let pause = SKAction.wait(forDuration: random)
            let move = SKAction.moveBy(x: self.size.width + buffer * 2, y: 0 - (self.size.height + buffer * 2), duration: 0.5)
            let wait = SKAction.wait(forDuration: 1.0)
            let reset = SKAction.moveBy(x: 0 - (self.size.width + buffer * 2), y: self.size.height + buffer * 2, duration: 0.0)
            let seq = SKAction.sequence([move, wait, reset])
            let repeater = SKAction.repeatForever(seq)
            let repeaterSeq = SKAction.sequence([pause, repeater])
    
            glare.run(repeaterSeq)
        }
    
        func showGlare(texture: SKTexture) {
    
            let mask = SKSpriteNode(texture: texture)
    
            cropNode.maskNode = mask
            glare.isHidden = false
            if cropNode.parent == nil { self.addChild(cropNode)}
        }
    
        func hideGlare() {
    
            glare.isHidden = true
            //remove cropnode from the node tree
            cropNode.removeFromParent()
        }
    }
    

    and then in my GameScene...

    I add my glares to a glare layer but that isn't necessary. I also go through when the game loads and create my glares for all 15 slots ahead of time and put them in an array. This way I do not have to create them on the fly and I can just turn on slot 10 any time I want and turn it off as well.

    private var glares = [Glare]()
    
    let glare = Glare(color: .clear, size: CGSize(width: kSymbolSize, height: kSymbolSize))
    glare.position = CGPoint(x: (CGFloat(x - 1) * kSymbolSize) + (kSymbolSize / 2), y: 0 - (CGFloat(y) * kSymbolSize) + (kSymbolSize / 2))
    glare.zPosition = 100
    glareLayer.addChild(glare)
    glares.append(glare)
    

    When I want to show the glare on a slot

    EDIT texture here for you would just be a blank square texture the size of your tile.

    glares[index].showGlare(texture: symbol.texture!)
    

    When I want to hide it

    glares[index].hideGlare()