8 Replies
      Latest reply on Jul 15, 2019 3:45 AM by igorland
      igorland Level 1 Level 1 (0 points)

        Hi. For the past few days, I have been trying to create an SKShapeNode (a rectangle) that will be used as a menu screen moving down from the outside of the screen when a button is tapped. Everything behind the node should be blurred.

         

        Something to this effect: https://stackoverflow.com/questions/49142867/spritekit-blurred-background-sknode

         

        Alternatively, I may have everything in the background blurred with the exception of the SKShapeNode and its children.

         

        Using this:

         

        let  blur = CIFilter(name:"CIGaussianBlur",withInputParameters: ["inputRadius": 10.0])
        self.filter = blur
        self.shouldRasterize = true
        self.shouldEnableEffects = true
                            
        menu.run(moveMenuDown)

         

        where self is the scene, blurs the background and the SKShapeNode. Besides, it is an awful hit on FPS.

         

        I tried the following:

         

        let blurEffect = UIBlurEffect(style: UIBlurEffectStyle.light)
        let blurEffectView = UIVisualEffectView(effect: blurEffect)
        blurEffectView.frame = self.view!.bounds
        blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        self.view!.addSubview(blurEffectView)

         

        This is very fast, but again this blurs everything including the SKShapeNode, which is not intended.

         

        Is there anyone who did something like that?

         

        Thanks a lot!

        • Re: Fast way to blur the background behind a Sprite Node
          moontiger Level 1 Level 1 (0 points)

          Hi, I don't know if this is ideal for what you want, but I was trying to do a similar thing, except the entire background would be blurred. I found the following things key to helping reduce frame rate hit...

           

          1. I think the higher the blur inputRadius, the slower it is. 4 blurs it enough for my situation.

          2. Lower resolution is quicker. My scene is only 200x200 and then scaled to fit, etc. Luckily it suits the graphics style I'm doing.

          3. I rendered the entire scene to a texture (view.texture(from: myscene)) and then applied that texture to a SKSpriteNode which was a child of an SKEffectNode which has the blur filter applied.

           

          With those things applied, I'm still seeing a frame rate of at least 60fps.

          • Re: Fast way to blur the background behind a Sprite Node
            gchiste Apple Staff Apple Staff (210 points)

            moontiger is on the right track, however, it is absolutely possible to blur only a portion of your SpriteKit scene, here is how:

             

            1.  Set up a node (contentNode) within your scene that will hold all of your scene's contents, except for the blur node.

             

            2.  Add an SKEffectNode to the root of your scene, add an SKShapeNode (blurNode) as its child.  Make sure that your blurNode is in front of all of the content in your contentNode by setting its zPosition.

             

            3.  Set the filter of your SKEffectNode to a CIGaussianBlur:

             

            let blurFilter = CIFilter(name: "CIGaussianBlur", parameters: ["inputRadius": 75])
            

             

            4.  Set the initial fillColor of your blurNode to .white

             

            5.  Generate an SKTexture of your content node, crop this texture using the frame of your blurNode, set this cropped texture as the fillTexture of your blurNode (self is the SKScene here):

             

            let fillTexture = self.view?.texture(from: contentNode, crop: blurNode.frame)
              • Re: Fast way to blur the background behind a Sprite Node
                igorland Level 1 Level 1 (0 points)

                Thank you, gchiste.

                 

                I tried to follow your approach, but unfortunately it is not working, nothing is added to the scene. This is my code:

                 

                 

                class ShowCardsView: SKShapeNode
                {
                    /// Button Exit
                    var btnExit:BtnIntroScene!
                   
                    required init(coder: NSCoder)
                    {
                        fatalError("NSCoding not supported")
                    }
                   
                    init(name:String)
                    {
                        super.init()
                       
                        self.name = name
                       
                        self.fillColor = UIColor(red: 120/255, green: 0/255, blue: 0/255, alpha: 0.95)
                        self.strokeColor = UIColor(red: 105/255, green: 0/255, blue: 0/255, alpha: 1)
                        self.lineWidth = 10
                       
                        self.path = UIBezierPath(roundedRect: CGRect(x: -128, y: -128, width: 500, height: 500), cornerRadius: 32).cgPath
                    }
                }
                
                
                class GameScene: SKScene,ConnectionManagerDelegate
                {
                func showPeerCardsTemp()
                    {
                        print("<<<<<<<< EFFECT >>>>>>>>")
                  
                        if let effectNode = SKEffectNode(fileNamed: "ShowCardsEffectNode")
                        {
                            self.addChild(effectNode)
                      
                            let showCardsView = ShowCardsView(name: "ShowCardsView")
                            showCardsView.position = CGPoint(x: frame.midX, y: frame.midY)
                            showCardsView.zPosition = CardLevel.peerName.rawValue
                            effectNode.addChild(showCardsView)
                      
                            let blurFilter = CIFilter(name: "CIGaussianBlur", parameters: ["inputRadius": 75])
                      
                            effectNode.filter = blurFilter
                      
                            effectNode.shouldRasterize = true
                            effectNode.shouldEnableEffects = true
                      
                            showCardsView.fillColor = .white
                      
                            let fillTexture = self.view?.texture(from: background, crop: showCardsView.frame)
                      
                            showCardsView.fillTexture = fillTexture
                      
                            self.addChild(effectNode)
                        }
                    }
                
                }

                 

                Thank you for your help!

                 

                Edit:

                I am actually happy with my second code in the original post. However, as I said, it also blurs all nodes added to the scene. Is there a way to add a node without blurring it (so that only everything below this node is blurred)?

              • Re: Fast way to blur the background behind a Sprite Node
                igorland Level 1 Level 1 (0 points)

                OK. Looks like I've nailed it:

                 

                import SpriteKit
                import GameplayKit
                
                class GameScene: SKScene {
                   
                    private var label : SKLabelNode!
                    private var rectangle : SKShapeNode!
                    private var background : SKSpriteNode!
                    private var rectangle1 : SKShapeNode!
                   
                    override func didMove(to view: SKView)
                    {
                        self.label = SKLabelNode(text: "HELLO WORLD")
                        self.label.fontName = "Noteworthy-Bold"
                        self.label.fontSize = 100
                        self.label.alpha = 1.0
                        self.label.color = .green
                        self.label.position = CGPoint(x: 10, y: 400)
                        addChild(self.label)
                       
                        let w = (self.size.width + self.size.height) * 0.05
                       
                        self.rectangle = SKShapeNode.init(rectOf: CGSize.init(width: w*5, height: w*5), cornerRadius: w * 0.3)
                        rectangle.fillColor = UIColor.red
                        addChild(rectangle)
                       
                        let blurEffect = UIBlurEffect(style: UIBlurEffect.Style.regular)
                        let blurEffectView = UIVisualEffectView(effect: blurEffect)
                        blurEffectView.frame = self.view!.bounds
                        blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
                        self.view!.addSubview(blurEffectView)
                       
                        // https://www.hackingwithswift.com/example-code/media/how-to-render-a-uiview-to-a-uiimage
                        let renderer = UIGraphicsImageRenderer(size: self.view!.bounds.size)
                        let textureImage = renderer.image { ctx in
                            view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
                        }
                       
                        let texture = SKTexture(image: textureImage)
                        let textureSizeWidth = textureImage.size.width * self.view!.contentScaleFactor
                        let textureSizeHeight = textureImage.size.height * self.view!.contentScaleFactor
                        let textureSize = CGSize(width: textureSizeWidth, height: textureSizeHeight)
                
                        self.background = SKSpriteNode.init(texture: texture, size: textureSize)
                        addChild(background)
                
                        self.rectangle1 = SKShapeNode.init(rectOf: CGSize.init(width: w*3, height: w*3), cornerRadius: w * 0.3)
                        self.rectangle1.fillColor = UIColor.blue
                        self.rectangle1.zPosition = 10
                        self.background!.addChild(rectangle1)
                           
                        blurEffectView.removeFromSuperview()
                
                    }
                   
                }

                 

                Any comments?

                  • Re: Fast way to blur the background behind a Sprite Node
                    gchiste Apple Staff Apple Staff (210 points)

                    This seems like a *very* roundabout way of achieving the desired effect.  There are definitely better ways which exclusively use SpriteKit (see my original post in this thread), no UIViews or UIGraphicsImageRenderers required.

                      • Re: Fast way to blur the background behind a Sprite Node
                        igorland Level 1 Level 1 (0 points)

                        gchiste.

                         

                        Thank you. No doubt there are better ways to do it. Unfortunately, no one seems to know them. I tried your approach but to no avail. It would be great to come up with something more direct. Cheers.

                         

                        EDIT:

                         

                        I tried the following:

                         

                        class GameScene: SKScene {
                           
                            private var label : SKLabelNode!
                            private var rectangle : SKShapeNode!
                            private var background : SKSpriteNode!
                            private var rectangle1 : SKShapeNode!
                           
                            override func didMove(to view: SKView)
                            {
                                self.label = SKLabelNode(text: "HELLO WORLD")
                                self.label.fontName = "Noteworthy-Bold"
                                self.label.fontSize = 100
                                self.label.alpha = 1.0
                                self.label.color = .green
                                self.label.position = CGPoint(x: 10, y: 400)
                                addChild(self.label)
                               
                                // Create shape node to use during mouse interaction
                                let w = (self.size.width + self.size.height) * 0.05
                               
                                self.rectangle = SKShapeNode.init(rectOf: CGSize.init(width: w*5, height: w*5), cornerRadius: w * 0.3)
                                rectangle.fillColor = UIColor.red
                                addChild(rectangle)
                               
                                let effectNode = SKEffectNode()
                                let blurFilter = CIFilter(name: "CIGaussianBlur", parameters: ["inputRadius": 75])
                                effectNode.filter = blurFilter
                                effectNode.shouldRasterize = true
                                effectNode.shouldEnableEffects = true
                                self.addChild(effectNode)
                        
                                background = SKSpriteNode()
                                background.position = CGPoint(x: frame.midX, y: frame.midY)
                                background.size = CGSize(width: frame.width, height: frame.height)
                                background.zPosition = 5
                                let fillTexture = self.view?.texture(from: self.scene!)
                                background.normalTexture = fillTexture
                                effectNode.addChild(background)
                        
                                self.rectangle1 = SKShapeNode.init(rectOf: CGSize.init(width: w*3, height: w*3), cornerRadius: w * 0.3)
                                self.rectangle1.fillColor = UIColor.blue
                                self.rectangle1.zPosition = 10
                                self.background!.addChild(rectangle1)
                            }
                        }

                         

                        What I get is a blurred blue rectangle1, whereas I need to blue the label and the red rectange. I could have added the label and the red rectangle to background. However, they have already been added to the scene and, sure enough, the application crashes when I add them again. So, I can't do that.

                         

                        Thoughts?

                    • Re: Fast way to blur the background behind a Sprite Node
                      igorland Level 1 Level 1 (0 points)

                      Thanks to gchiste, I have achieved it by doing the following:

                       

                      import SpriteKit
                      import GameplayKit
                      
                      class GameScene: SKScene {
                         
                          private var label : SKLabelNode!
                          private var rectangle : SKShapeNode!
                          private var container : SKSpriteNode!
                          private var rectangle1 : SKShapeNode!
                          private var button : SKSpriteNode!
                          private var effectNode : SKEffectNode!
                          private var blurNode : SKShapeNode!
                         
                          override func didMove(to view: SKView)
                          {
                              effectNode = SKEffectNode()
                              let blurFilter = CIFilter(name: "CIGaussianBlur", parameters: ["inputRadius": 20])
                              effectNode.filter = blurFilter
                              effectNode.zPosition = 500
                              addChild(effectNode)
                             
                              blurNode = SKShapeNode(rect: self.frame)
                              blurNode.fillColor = .white
                              blurNode.isHidden = true
                              effectNode.addChild(blurNode)
                             
                             
                              self.container = SKSpriteNode()
                              //self.container.position = CGPoint(x: frame.width/2, y: frame.height/2)
                              self.container.size = CGSize(width: frame.width, height: frame.height)
                              self.container.color = UIColor(red: 0/255, green: 100/255, blue: 0/255, alpha: 1)
                              addChild(container)
                             
                      
                              self.label = SKLabelNode(text: "HELLO WORLD")
                              self.label.fontName = "Noteworthy-Bold"
                              self.label.fontSize = 100
                              self.label.alpha = 1.0
                              self.label.color = .red
                              self.label.zPosition = 2
                              self.label.position = self.convert(CGPoint(x: 5, y: 400),to: container)
                              self.container.addChild(self.label)
                                 
                                 
                              self.rectangle = SKShapeNode.init(rectOf: CGSize.init(width: 500, height: 500),
                                                                cornerRadius: 30)
                              self.rectangle.fillColor = UIColor.green
                              self.rectangle.strokeColor = .clear
                              self.rectangle.position = self.convert(self.rectangle.position, to: container)
                              self.container.addChild(self.rectangle)
                             
                             
                             
                              self.button = SKSpriteNode(color: .blue, size: CGSize(width: 300, height: 100))
                              self.button.position = self.convert(CGPoint(x: 0, y: -400), to: container)
                              self.button.zPosition = 2
                              self.button.name = "button"
                              self.container.addChild(self.button)
                             
                          }
                         
                          override func touchesEnded(_ touches: Set, with event: UIEvent?)
                          {
                              let touch = touches.first
                              let positionInScene = touch!.location(in: self)
                              let allNodes = nodes(at: positionInScene)
                              for node in allNodes
                              {
                                  let nodePositionConverted = self.convert(node.position, from: node)
                                  let nodeFrameConverted = CGRect(origin: CGPoint(x:nodePositionConverted.x-node.frame.maxX,
                                                                                  y:nodePositionConverted.y-node.frame.maxY),
                                                                  size:node.frame.size)
                                  if nodeFrameConverted.contains(positionInScene)
                                  {
                                      if(node == button)
                                      {
                                          addMenu()
                                      }
                                      else if(node == rectangle1)
                                      {
                                          hideMenu()
                                      }
                                  }
                              }
                          }
                         
                         
                          func addMenu()
                          {
                              print("POINT A")
                              blurNode.isHidden = false
                              let fillTexture = self.view?.texture(from: container, crop: blurNode.frame)
                              print("POINT B")
                              blurNode.fillTexture = fillTexture
                             
                              self.rectangle1 = SKShapeNode.init(rectOf: CGSize.init(width: 300, height: 300), cornerRadius: 30)
                              self.rectangle1.fillColor = UIColor.blue
                              self.rectangle1.zPosition = 600
                              addChild(rectangle1)
                          }
                         
                         
                          func hideMenu()
                          {
                              blurNode.isHidden = true
                              blurNode.fillTexture = nil
                              rectangle1.removeFromParent()
                          }
                      }

                       

                       

                      The only thing that bothers me is that it takes about a second or two before the blurred image appears. Does it take so long to create fillTexture from the container? Is there a way to do it more efficiently? Can it be done in the background thread?

                       

                      Thanks!

                       

                      EDIT 1: Seems like the delay is obvious in Simulator and not as much on an actual device...

                       

                      EDIT 2: One more issue. The blurring effect does not cover the whole scene. The edges of the scene are not blurred. Any idea why? Thanks!