如何通过SKEffectNode在精灵周围创建一个光晕

时间:2013-10-06 21:13:55

标签: ios iphone sprite-kit skeffectnode

我有一个SKSpriteNode我希望在它的边缘有蓝色光晕以突出显示目的。我猜我需要让我的精灵成为SKEffectNode的孩子,然后创建/应用某种过滤器。

更新:我已经使用所选答案的方法调查了这一点,并发现SKEffectNode即使您将其设置为shouldRasterize并且未定义“过滤器”,也会对性能产生相当大的影响。我的结论是,如果你的游戏一次需要10个以上的移动物体,即使光栅化它们也不会涉及SKEffectNode

我的解决方案可能涉及预渲染的发光图像/动画,因为SKEffectNode不会根据我的要求剪切它。

如果有人对我失踪的任何事情有所了解,我会很感激你所知道的一切!

我接受了一个答案,因为它确实实现了我的要求,但是想将这些笔记添加到任何想要走这条路线的人,这样你就可以了解使用SKEffectNode的一些问题。 / p>

3 个答案:

答案 0 :(得分:27)

@ rickster的答案很棒。由于我有低代表,我显然不允许将此代码作为评论添加到他的。我希望这不会破坏stackoverflow规则。我并没有试图以任何方式使用他的代表。

以下代码在他的回答中执行了他所描述的内容:

部首:

//  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

实现:

#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

使用中:

@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];
    }
}

答案 1 :(得分:22)

您可以通过创建组成多个内置过滤器的CIFilter子类,在Core Image中创建一个发光效果。这样的过滤器将包括以下步骤:

  1. 创建一个用作蓝色光晕的图像。可能有一些不错的方法可以做到这一点;一种是使用CIColorMatrix创建输入图像的单色版本。
  2. 放大并模糊发光图像(CIAffineTransform + CIGaussianBlur)。
  3. 将原始输入图像合成在发光图像上(CISourceOverCompositing)。
  4. 一旦你有一个CIFilter子类来完成所有这些,你可以将它与SKEffectNode一起使用,以获得效果节点的子节点周围的实时光晕。它在iPad 4上的“Sprite Kit Game”Xcode模板中运行:

    Glowing spaceship in Sprite Kit

    我通过在WWDC 2013的Scene Kit演示文稿中使用类似效果的自定义过滤器类来在几分钟内完成并运行 - 从developer.apple.com/downloads的WWDC示例代码包中获取它,并且寻找ASCGlowFilter类。 (如果您想在iOS上使用该代码,则需要更改NSAffineTransform部分以使用CGAffineTransform。我还将centerXcenterY属性替换为类型为inputCenter的{​​{1}}参数,因此Sprite Kit可以自动将效果置于精灵中心。)

    我说过“实时”发光吗?对!这是“真正吃掉CPU时间”的缩写。请注意,在屏幕截图中,它不再固定在60 fps,即使只有一个太空飞船 - 并且在iOS模拟器上使用软件OpenGL ES渲染器,它以幻灯片速度运行。如果你在Mac上,你可能还有硅片......但是如果你想在你的游戏中做到这一点,请记住一些事情:

    • 可能有一些方法可以从过滤器本身获得更好的性能。使用不同的CI滤镜,您可能会看到一些改进(Core Image中有几个模糊滤镜,其中一些肯定会比高斯滤镜更快)。另请注意,模糊效果往往是片段着色器绑定,因此图像越小,发光半径越小越好。
    • 如果您想在场景中有多个发光,请考虑将所有发光精灵制作为同一效果节点的子节点 - 将它们全部渲染到一个图像中,然后应用滤镜一次。
    • 如果要发光的精灵没有太大变化(例如,如果我们的飞船没有旋转),在效果节点上将CIVector设置为shouldRasterize应该会有很大帮助。 (实际上,在这种情况下,您可以通过旋转效果节点而不是其中的精灵来获得一些改进。)
    • 你真的需要实时发光吗?与游戏中的许多漂亮的图形效果一样,如果你伪造它,你会获得更好的性能。在你最喜欢的图形编辑器中制作一个模糊的蓝色太空船,并将其作为另一个精灵放在场景中。

答案 2 :(得分:3)

您可以在精灵后面使用SKShapeNode,并使用glowWidthstrokeColor属性定义光晕。如果你的尺寸和位置正确,这应该给你一个发光的外观。这并没有为您提供很多自定义选项,但我认为这比使用CIFilterSKEffectNode要容易得多,这可能是您拥有的另一个逻辑选项。