How Do I Blur a Scene in SpriteKit?

后端 未结 7 830
北恋
北恋 2020-11-28 06:00

How would I add a gaussian blur to all nodes (there\'s no fixed number of nodes) in an SKScene in SpriteKit? A label will be added on top of the scene later, this will be m

相关标签:
7条回答
  • 2020-11-28 06:25

    What you're looking for is an SKEffectNode. It applies a CoreImage filter to itself (and thus all subnodes). Just make it the root view of your scene, give it one of CoreImage's blur filters, and you're set.

    For example, I set up an SKScene with an SKEffectNode as it's first child node and a property, root that holds a weak reference to it:

    -(void)createLayers{
      SKEffectNode *node = [SKEffectNode node];
      [node setShouldEnableEffects:NO];
      CIFilter *blur = [CIFilter filterWithName:@"CIGaussianBlur" keysAndValues:@"inputRadius", @1.0f, nil];
      [node setFilter:blur];
      [self setRoot:node];
    }
    

    And here's the method I use to (animate!) the blur of my scene:

    -(void)blurWithCompletion:(void (^)())handler{
      CGFloat duration = 0.5f;
      [[self root] setShouldRasterize:YES];
      [[self root] setShouldEnableEffects:YES];
      [[self root] runAction:[SKAction customActionWithDuration:duration actionBlock:^(SKNode *node, CGFloat elapsedTime){
        NSNumber *radius = [NSNumber numberWithFloat:(elapsedTime/duration) * 10.0];
        [[(SKEffectNode *)node filter] setValue:radius forKey:@"inputRadius"];
      }] completion:handler];
    }
    

    Note that, like you, I'm using this as a pause screen, so I rasterize the scene. If you want your scene to animate while blurred, you should probably setShouldResterize: to NO.

    And if you're not interested in animating the transition to the blur, you could always just set the filter to an initial radius of 10.0f or so and do a simple setShouldEnableEffects:YES when you want to switch it on.

    See also: SKEffectNode class reference

    UPDATE:
    See Markus's comment below. He points out that SKScene is, in fact, a subclass of SKEffectNode, so you really ought to be able to call all of this on the scene itself rather than arbitrarily inserting an effect node in your node tree.

    0 讨论(0)
  • 2020-11-28 06:25

    Swift 3 Update: This is @Chuck Gaffney's answer updated for Swift 3. I know this question is tagged objective-c, but this page ranked 2nd in Google for "swift spritekit blur". I changed currentScene to self.

        func getBluredScreenshot() -> SKSpriteNode{
    
        //create the graphics context
        UIGraphicsBeginImageContextWithOptions(CGSize(width: self.view!.frame.size.width, height: self.view!.frame.size.height), true, 1)
    
        self.view!.drawHierarchy(in: self.view!.frame, afterScreenUpdates: true)
    
        // retrieve graphics context
        _ = UIGraphicsGetCurrentContext()
    
        // query image from it
        let image = UIGraphicsGetImageFromCurrentImageContext()
    
        // create Core Image context
        let ciContext = CIContext(options: nil)
        // create a CIImage, think of a CIImage as image data for processing, nothing is displayed or can be displayed at this point
        let coreImage = CIImage(image: image!)
        // pick the filter we want
        let filter = CIFilter(name: "CIGaussianBlur")
        // pass our image as input
        filter?.setValue(coreImage, forKey: kCIInputImageKey)
    
        //edit the amount of blur
        filter?.setValue(3, forKey: kCIInputRadiusKey)
    
        //retrieve the processed image
        let filteredImageData = filter?.value(forKey: kCIOutputImageKey) as! CIImage
        // return a Quartz image from the Core Image context
        let filteredImageRef = ciContext.createCGImage(filteredImageData, from: filteredImageData.extent)
        // final UIImage
        let filteredImage = UIImage(cgImage: filteredImageRef!)
    
        // create a texture, pass the UIImage
        let texture = SKTexture(image: filteredImage)
        // wrap it inside a sprite node
        let sprite = SKSpriteNode(texture:texture)
    
        // make image the position in the center
        sprite.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
    
        let scale:CGFloat = UIScreen.main.scale
    
        sprite.size.width  *= scale
    
        sprite.size.height *= scale
    
        return sprite
    
    
    }
    
    func loadPauseBGScreen(){
    
        let duration = 1.0
    
        let pauseBG:SKSpriteNode = self.getBluredScreenshot()
    
        pauseBG.alpha = 0
        pauseBG.zPosition = self.zPosition + 1
        pauseBG.run(SKAction.fadeAlpha(to: 1, duration: duration))
    
        self.addChild(pauseBG)
    
    }
    
    0 讨论(0)
  • 2020-11-28 06:33

    To add to this by using @Bendegúz's answer and code from http://www.bytearray.org/?p=5360

    I was able to get this to work in my current game project that's being done in IOS 8 Swift. Done a bit differently by returning an SKSpriteNode instead of a UIImage. Also note that my unwrapped currentScene.view! call is to a weak GameScene reference but should work with self.view.frame based on where you are calling these methods. My pause screen is called in a separate HUD class hence why this is the case.

    I would imagine this could be done more elegantly, maybe more like @jemmons's answer. Just wanted to possibly help out anyone else trying to do this in SpriteKit projects written in all or some Swift code.

    func getBluredScreenshot() -> SKSpriteNode{
    
        create the graphics context
        UIGraphicsBeginImageContextWithOptions(CGSize(width: currentScene.view!.frame.size.width, height: currentScene.view!.frame.size.height), true, 1)
    
        currentScene.view!.drawViewHierarchyInRect(currentScene.view!.frame, afterScreenUpdates: true)
    
        // retrieve graphics context
        let context = UIGraphicsGetCurrentContext()
    
        // query image from it
        let image = UIGraphicsGetImageFromCurrentImageContext()
    
        // create Core Image context
        let ciContext = CIContext(options: nil)
        // create a CIImage, think of a CIImage as image data for processing, nothing is displayed or can be displayed at this point
        let coreImage = CIImage(image: image)
        // pick the filter we want
        let filter = CIFilter(name: "CIGaussianBlur")
        // pass our image as input
        filter.setValue(coreImage, forKey: kCIInputImageKey)
    
        //edit the amount of blur
        filter.setValue(3, forKey: kCIInputRadiusKey)
    
        //retrieve the processed image
        let filteredImageData = filter.valueForKey(kCIOutputImageKey) as CIImage
        // return a Quartz image from the Core Image context
        let filteredImageRef = ciContext.createCGImage(filteredImageData, fromRect: filteredImageData.extent())
        // final UIImage
        let filteredImage = UIImage(CGImage: filteredImageRef)
    
        // create a texture, pass the UIImage
        let texture = SKTexture(image: filteredImage!)
        // wrap it inside a sprite node
        let sprite = SKSpriteNode(texture:texture)
    
        // make image the position in the center
        sprite.position = CGPointMake(CGRectGetMidX(currentScene.frame), CGRectGetMidY(currentScene.frame))
    
        var scale:CGFloat = UIScreen.mainScreen().scale
    
        sprite.size.width  *= scale
    
        sprite.size.height *= scale
    
        return sprite
    
    
    }
    
    
    func loadPauseBGScreen(){
    
        let duration = 1.0
    
        let pauseBG:SKSpriteNode = self.getBluredScreenshot()
    
        //pauseBG.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame))
        pauseBG.alpha = 0
        pauseBG.zPosition = self.zPosition + 1
        pauseBG.runAction(SKAction.fadeAlphaTo(1, duration: duration))
    
        self.addChild(pauseBG)
    
    }
    
    0 讨论(0)
  • 2020-11-28 06:36

    Swift 4:

    add this to your gameScene if you want to blur everything in the scene:

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

    change self.shouldEnableEffects = true when you want to use it.

    0 讨论(0)
  • 2020-11-28 06:38

    This is my solution for the pause screen. It will take a screenshot, blur it and after that show it with animation. I think you should do it if you don't wanna waste to much fps.

    -(void)pause {
        SKSpriteNode *pauseBG = [SKSpriteNode spriteNodeWithTexture:[SKTexture textureWithImage:[self getBluredScreenshot]]];
        pauseBG.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
        pauseBG.alpha = 0;
        pauseBG.zPosition = 2;
        [pauseBG runAction:[SKAction fadeAlphaTo:1 duration:duration / 2]];
        [self addChild:pauseBG];
    }
    

    And this is the helper method:

    - (UIImage *)getBluredScreenshot {
        UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, NO, 1);
        [self.view drawViewHierarchyInRect:self.view.frame afterScreenUpdates:YES];
        UIImage *ss = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
    
        CIFilter *gaussianBlurFilter = [CIFilter filterWithName:@"CIGaussianBlur"];
        [gaussianBlurFilter setDefaults];
        [gaussianBlurFilter setValue:[CIImage imageWithCGImage:[ss CGImage]] forKey:kCIInputImageKey];
        [gaussianBlurFilter setValue:@10 forKey:kCIInputRadiusKey];
    
        CIImage *outputImage = [gaussianBlurFilter outputImage];
        CIContext *context   = [CIContext contextWithOptions:nil];
        CGRect rect          = [outputImage extent];
        rect.origin.x        += (rect.size.width  - ss.size.width ) / 2;
        rect.origin.y        += (rect.size.height - ss.size.height) / 2;
        rect.size            = ss.size;
        CGImageRef cgimg     = [context createCGImage:outputImage fromRect:rect];
        UIImage *image       = [UIImage imageWithCGImage:cgimg];
        CGImageRelease(cgimg);
        return image;
    }
    
    0 讨论(0)
  • 2020-11-28 06:40

    This is another example of getting this done in swift 2 without the layers:

    func blurWithCompletion() {
    let duration: CGFloat = 0.5
    let filter: CIFilter = CIFilter(name: "CIGaussianBlur", withInputParameters: ["inputRadius" : NSNumber(double:1.0)])!
    scene!.filter = filter
    scene!.shouldRasterize = true
    scene!.shouldEnableEffects = true
    scene!.runAction(SKAction.customActionWithDuration(0.5, actionBlock: { (node: SKNode, elapsedTime: CGFloat) in
        let radius = (elapsedTime/duration)*10.0
        (node as? SKEffectNode)!.filter!.setValue(radius, forKey: "inputRadius")
    
    }))
    

    }

    0 讨论(0)
提交回复
热议问题