I have a SKSpriteNode
that I'd like to have a blue glow around it's edges for highlighting purposes. I am guessing that I would need to make my sprite a child of a SKEffectNode
and then create/apply a filter of some sort.
UPDATE : I have investigated this quite a it with the chosen answer's approach, and discovered that SKEffectNode
has a sizeable hit on performance even if you have it set to shouldRasterize
and 'no filter' defined. My conclusion is that if your game requires more than 10 moving objects at one time, they can't involve a SKEffectNode
even if rasterized.
My solution will likely involve pre-rendered glow images/animations, as SKEffectNode is not going to cut it for my requirements.
If someone has insight as to anything I am missing, I'd appreciate hearing whatever you know!
I am accepting an answer because it does achieve what I asked for, but wanted to add these notes to anyone looking to go this route, so you can be aware of some of the issues with using SKEffectNode
.
@rickster's answer is great. Since I have low rep, I'm apparently not allowed to add this code as a comment to his. I hope this doesn't break stackoverflow rules of propriety. I'm not trying to userp his rep in any way.
Here's code that does what he's describing in his answer:
Header:
// ENHGlowFilter.h #import <CoreImage/CoreImage.h> @interface ENHGlowFilter : CIFilter @property (strong, nonatomic) UIColor *glowColor; @property (strong, nonatomic) CIImage *inputImage; @property (strong, nonatomic) NSNumber *inputRadius; @property (strong, nonatomic) CIVector *inputCenter; @end //Based on ASCGLowFilter from Apple
Implementation:
#import "ENHGlowFilter.h" @implementation ENHGlowFilter -(id)init { self = [super init]; if (self) { _glowColor = [UIColor whiteColor]; } return self; } - (NSArray *)attributeKeys { return @[@"inputRadius", @"inputCenter"]; } - (CIImage *)outputImage { CIImage *inputImage = [self valueForKey:@"inputImage"]; if (!inputImage) return nil; // Monochrome CIFilter *monochromeFilter = [CIFilter filterWithName:@"CIColorMatrix"]; CGFloat red = 0.0; CGFloat green = 0.0; CGFloat blue = 0.0; CGFloat alpha = 0.0; [self.glowColor getRed:&red green:&green blue:&blue alpha:&alpha]; [monochromeFilter setDefaults]; [monochromeFilter setValue:[CIVector vectorWithX:0 Y:0 Z:0 W:red] forKey:@"inputRVector"]; [monochromeFilter setValue:[CIVector vectorWithX:0 Y:0 Z:0 W:green] forKey:@"inputGVector"]; [monochromeFilter setValue:[CIVector vectorWithX:0 Y:0 Z:0 W:blue] forKey:@"inputBVector"]; [monochromeFilter setValue:[CIVector vectorWithX:0 Y:0 Z:0 W:alpha] forKey:@"inputAVector"]; [monochromeFilter setValue:inputImage forKey:@"inputImage"]; CIImage *glowImage = [monochromeFilter valueForKey:@"outputImage"]; // Scale float centerX = [self.inputCenter X]; float centerY = [self.inputCenter Y]; if (centerX > 0) { CGAffineTransform transform = CGAffineTransformIdentity; transform = CGAffineTransformTranslate(transform, centerX, centerY); transform = CGAffineTransformScale(transform, 1.2, 1.2); transform = CGAffineTransformTranslate(transform, -centerX, -centerY); CIFilter *affineTransformFilter = [CIFilter filterWithName:@"CIAffineTransform"]; [affineTransformFilter setDefaults]; [affineTransformFilter setValue:[NSValue valueWithCGAffineTransform:transform] forKey:@"inputTransform"]; [affineTransformFilter setValue:glowImage forKey:@"inputImage"]; glowImage = [affineTransformFilter valueForKey:@"outputImage"]; } // Blur CIFilter *gaussianBlurFilter = [CIFilter filterWithName:@"CIGaussianBlur"]; [gaussianBlurFilter setDefaults]; [gaussianBlurFilter setValue:glowImage forKey:@"inputImage"]; [gaussianBlurFilter setValue:self.inputRadius ?: @10.0 forKey:@"inputRadius"]; glowImage = [gaussianBlurFilter valueForKey:@"outputImage"]; // Blend CIFilter *blendFilter = [CIFilter filterWithName:@"CISourceOverCompositing"]; [blendFilter setDefaults]; [blendFilter setValue:glowImage forKey:@"inputBackgroundImage"]; [blendFilter setValue:inputImage forKey:@"inputImage"]; glowImage = [blendFilter valueForKey:@"outputImage"]; return glowImage; } @end
In use:
@implementation ENHMyScene //SKScene subclass -(id)initWithSize:(CGSize)size { if (self = [super initWithSize:size]) { /* Setup your scene here */ [self setAnchorPoint:(CGPoint){0.5, 0.5}]; self.backgroundColor = [SKColor colorWithRed:0.15 green:0.15 blue:0.3 alpha:1.0]; SKEffectNode *effectNode = [[SKEffectNode alloc] init]; ENHGlowFilter *glowFilter = [[ENHGlowFilter alloc] init]; [glowFilter setGlowColor:[[UIColor redColor] colorWithAlphaComponent:0.5]]; [effectNode setShouldRasterize:YES]; [effectNode setFilter:glowFilter]; [self addChild:effectNode]; _effectNode = effectNode; } return self; } -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { /* Called when a touch begins */ for (UITouch *touch in touches) { CGPoint location = [touch locationInNode:self]; SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithImageNamed:@"Spaceship"]; sprite.position = location; [self.effectNode addChild:sprite]; } }
You can create a glow effect in Core Image by creating a CIFilter
subclass that composes multiple built-in filters. Such a filter would involve steps like these:
CIColorMatrix
to create a monochrome version of the input image.CIAffineTransform
+ CIGaussianBlur
).CISourceOverCompositing
).Once you have a CIFilter
subclass that does all that, you can use it with a SKEffectNode
to get a realtime glow around the effect node's children. Here it is running in the "Sprite Kit Game" Xcode template on an iPad 4:
I got this up and running in a few minutes by cribbing the custom filter class used for a similar effect in the Scene Kit presentation from WWDC 2013 -- grab it from the WWDC Sample Code package at developer.apple.com/downloads, and look for the ASCGlowFilter
class. (If you want to use that code on iOS, you'll need to change the NSAffineTransform
part to use CGAffineTransform
instead. I also replaced the centerX
and centerY
properties with an inputCenter
parameter of type CIVector
so Sprite Kit can automatically center the effect on the sprite.)
Did I say "realtime" glow? Yup! That's short for "really eats CPU time". Notice in the screenshot it's no longer pegged at 60 fps, even with only one spaceship -- and with the software OpenGL ES renderer on the iOS Simulator, it runs at slideshow speed. If you're on the Mac, you probably have silicon to spare... but if you want to do this in your game, keep some things in mind:
shouldRasterize
to YES
on the effect node should help a lot. (Actually, in this case, you might get some improvement by rotating the effect node instead of the sprite within it.) If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With