Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How Do I Blur a Scene in SpriteKit?

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 my pause menu. Almost anything would help!

Something like this is what I'm going for: Gaussian pause menu

like image 905
Zane Helton Avatar asked Mar 18 '14 20:03

Zane Helton


People also ask

How do you blur a view in Swift?

You will need to create the blur effect using the UIBlurEffect Class in the viewDidLoad method. After that you'll need to apply the effect to the UIVisualEffectView. The UIVisualEffectView needs to have the container dimensions defined, and it will be the first view of the stack of the view controller.


4 Answers

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.

like image 114
jemmons Avatar answered Oct 19 '22 22:10

jemmons


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)

}
like image 33
Chuck Gaffney Avatar answered Oct 19 '22 22:10

Chuck Gaffney


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;
}
like image 10
Bendegúz Avatar answered Oct 19 '22 21:10

Bendegúz


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.

like image 4
Alex Bailey Avatar answered Oct 19 '22 22:10

Alex Bailey