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!
如何在SpriteKit中的SKScene中为所有节点添加高斯模糊(没有固定数量的节点)?稍后将在场景顶部添加标签,这将是我的暂停菜单。几乎任何事都有帮助!
Something like this is what I'm going for:
这样的事情就是我想要的:
6 个解决方案
#1
30
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.
您正在寻找的是SKEffectNode。它将CoreImage过滤器应用于自身(以及所有子节点)。只需将它作为场景的根视图,给它一个CoreImage的模糊滤镜,然后就可以了。
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:
例如,我设置了一个带有SKEffectNode的SKScene作为它的第一个子节点和一个属性,root用于保存对它的弱引用:
-(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
.
请注意,像你一样,我将它用作暂停屏幕,所以我光栅化了场景。如果你希望你的场景在模糊的情况下进行动画制作,你应该将theShouldResterize:设置为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.
如果你对动画转换到模糊不感兴趣,你可以随时将过滤器设置为初始半径10.0f左右,并且当你想要打开它时,做一个简单的setShouldEnableEffects:YES。
See also: SKEffectNode class reference
另请参见:SKEffectNode类引用
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.
更新:见下面Markus的评论。他指出SKScene实际上是SKEffectNode的子类,所以你真的应该能够在场景中调用所有这些,而不是在节点树中任意插入效果节点。
#2
11
To add to this by using @Bendegúz's answer and code from http://www.bytearray.org/?p=5360
使用@Bendegúz的答案和代码从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.
我能够在我目前正在IOS 8 Swift中完成的游戏项目中使用它。通过返回SKSpriteNode而不是UIImage来完成一些不同的操作。另请注意我的unwrapped currentScene.view! call是一个弱的GameScene引用,但应该根据你调用这些方法的位置使用self.view.frame。我的暂停屏幕在一个单独的HUD类中调用,因此为什么会这样。
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.
我想这可以做得更优雅,也许更像是@ jemmons的回答。只是想在可能用SpriteKit项目编写的全部或部分Swift代码中帮助其他人。
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)
}
#3
9
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.
这是我暂停屏幕的解决方案。它会截取屏幕,模糊它,然后用动画显示它。我想你应该这样做,如果你不想浪费很多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;
}
#4
2
This is another example of getting this done in swift 2 without the layers:
这是在没有图层的情况下在swift 2中完成此操作的另一个示例:
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")
}))
}
}
#5
1
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.
Swift 3更新:这是@Chuck Gaffney对Swift 3更新的答案。我知道这个问题被标记为objective-c,但是这个页面在Google中排名第二,因为“swift spritekit模糊”。我把currentScene改成了自己。
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)
}
#6
1
Swift 4:
斯威夫特4:
add this to your gameScene if you want to blur everything in the scene:
如果要模糊场景中的所有内容,请将其添加到gameScene中:
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.
如果要使用它,请更改self.shouldEnableEffects = true。
#1
30
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.
您正在寻找的是SKEffectNode。它将CoreImage过滤器应用于自身(以及所有子节点)。只需将它作为场景的根视图,给它一个CoreImage的模糊滤镜,然后就可以了。
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:
例如,我设置了一个带有SKEffectNode的SKScene作为它的第一个子节点和一个属性,root用于保存对它的弱引用:
-(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
.
请注意,像你一样,我将它用作暂停屏幕,所以我光栅化了场景。如果你希望你的场景在模糊的情况下进行动画制作,你应该将theShouldResterize:设置为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.
如果你对动画转换到模糊不感兴趣,你可以随时将过滤器设置为初始半径10.0f左右,并且当你想要打开它时,做一个简单的setShouldEnableEffects:YES。
See also: SKEffectNode class reference
另请参见:SKEffectNode类引用
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.
更新:见下面Markus的评论。他指出SKScene实际上是SKEffectNode的子类,所以你真的应该能够在场景中调用所有这些,而不是在节点树中任意插入效果节点。
#2
11
To add to this by using @Bendegúz's answer and code from http://www.bytearray.org/?p=5360
使用@Bendegúz的答案和代码从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.
我能够在我目前正在IOS 8 Swift中完成的游戏项目中使用它。通过返回SKSpriteNode而不是UIImage来完成一些不同的操作。另请注意我的unwrapped currentScene.view! call是一个弱的GameScene引用,但应该根据你调用这些方法的位置使用self.view.frame。我的暂停屏幕在一个单独的HUD类中调用,因此为什么会这样。
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.
我想这可以做得更优雅,也许更像是@ jemmons的回答。只是想在可能用SpriteKit项目编写的全部或部分Swift代码中帮助其他人。
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)
}
#3
9
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.
这是我暂停屏幕的解决方案。它会截取屏幕,模糊它,然后用动画显示它。我想你应该这样做,如果你不想浪费很多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;
}
#4
2
This is another example of getting this done in swift 2 without the layers:
这是在没有图层的情况下在swift 2中完成此操作的另一个示例:
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")
}))
}
}
#5
1
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.
Swift 3更新:这是@Chuck Gaffney对Swift 3更新的答案。我知道这个问题被标记为objective-c,但是这个页面在Google中排名第二,因为“swift spritekit模糊”。我把currentScene改成了自己。
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)
}
#6
1
Swift 4:
斯威夫特4:
add this to your gameScene if you want to blur everything in the scene:
如果要模糊场景中的所有内容,请将其添加到gameScene中:
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.
如果要使用它,请更改self.shouldEnableEffects = true。