Как вы можете создать свечение вокруг спрайта через SKEffectNode

У меня есть SKSpriteNode что я хотел бы иметь синий свет по краям для подсветки. Я предполагаю, что мне нужно было бы сделать мой спрайт ребенком SKEffectNode а затем создать / применить какой-то фильтр.

ОБНОВЛЕНИЕ: я исследовал это довольно с подходом выбранного ответа, и обнаружил, что SKEffectNode имеет значительный удар по производительности, даже если он установлен на shouldRasterize и "фильтр не определен". Мой вывод таков: если вашей игре требуется более 10 движущихся объектов одновременно, они не могут включать SKEffectNode даже если растеризован.

Мое решение, вероятно, будет включать в себя предварительно визуализированные изображения / анимацию свечения, так как SKEffectNode не собирается сокращать его для моих требований.

Если у кого-то есть понимание того, что мне не хватает, я был бы рад услышать все, что вы знаете!

Я принимаю ответ, потому что он достигает того, о чем я просил, но хотел добавить эти заметки всем, кто хочет пойти по этому пути, чтобы вы могли знать о некоторых проблемах с использованием SKEffectNode,

3 ответа

Решение

Ответ @ Рикстера великолепен. Поскольку у меня низкая репутация, мне явно не разрешено добавлять этот код в качестве комментария к его. Я надеюсь, что это не нарушает правила уместности stackru. Я не пытаюсь использовать его репутацию.

Вот код, который делает то, что он описывает в своем ответе:

Заголовок:

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

Вы можете создать эффект свечения в Core Image, создав CIFilter подкласс, который составляет несколько встроенных фильтров. Такой фильтр будет включать такие шаги:

  1. Создайте изображение, которое будет использоваться в качестве синего свечения. Вероятно, есть несколько достойных способов сделать это; один должен использовать CIColorMatrix создать монохромную версию входного изображения.
  2. Масштабировать и размыть изображение свечения (CIAffineTransform + CIGaussianBlur).
  3. Композицию исходного входного изображения поверх изображения свечения (CISourceOverCompositing).

Когда у вас есть CIFilter подкласс, который делает все это, вы можете использовать его с SKEffectNode чтобы получить в реальном времени свечение вокруг детей узла эффекта. Здесь он запускается в шаблоне Xcode "Sprite Kit Game" на iPad 4:

Светящийся космический корабль в Sprite Kit

Я получил это и запустил через несколько минут, набрав пользовательский класс фильтра, используемый для аналогичного эффекта в презентации Scene Kit из WWDC 2013 - возьмите его из пакета примера кода WWDC на http://developer.apple.com/downloads и посмотрите для ASCGlowFilter учебный класс. (Если вы хотите использовать этот код на iOS, вам нужно изменить NSAffineTransform часть для использования CGAffineTransform вместо. Я также заменил centerX а также centerY свойства с inputCenter параметр типа CIVector поэтому Sprite Kit может автоматически центрировать эффект на спрайте.)

Я сказал "свечение в реальном времени"? Ага! Это сокращение от "действительно ест процессорное время". Обратите внимание, что на снимке экрана он больше не привязан к 60 кадрам в секунду, даже с одним космическим кораблем - и с программным рендерером OpenGL ES на iOS Simulator он работает со скоростью слайд-шоу. Если вы на Mac, у вас, вероятно, есть запас кремния, но если вы хотите сделать это в своей игре, помните о некоторых вещах:

  • Вероятно, есть несколько способов повысить производительность самого фильтра. Поиграйте с разными фильтрами CI, и вы можете увидеть некоторые улучшения (в Core Image есть несколько фильтров размытия, некоторые из которых, безусловно, будут быстрее, чем Gaussian). Также обратите внимание, что эффекты размытия обычно связаны с фрагментами-шейдерами, поэтому чем меньше изображение и меньше радиус свечения, тем лучше.
  • Если вы хотите, чтобы в сцене было несколько свечений, подумайте о том, чтобы сделать все светящиеся спрайты дочерними для одного и того же узла эффекта - это отобразит их все в одном изображении, а затем примените фильтр один раз.
  • Если светящиеся спрайты не сильно меняются (например, если наш космический корабль не вращался), установите shouldRasterize в YES на узле эффекта должно сильно помочь. (На самом деле, в этом случае вы можете получить некоторое улучшение, вращая узел эффекта вместо спрайта внутри него.)
  • Вам действительно нужно свечение в реальном времени? Как и во многих изящных графических эффектах в играх, вы получите гораздо лучшую производительность, если подделаете ее. Создайте размытый синий корабль в вашем любимом графическом редакторе и поместите его на сцену как еще один спрайт.

Вы могли бы использовать SKShapeNode позади спрайта и определить свечение, используя его glowWidth а также strokeColor свойства. Если вы установите размер и поместите его правильно, это должно создать видимость свечения. Это не дает вам много возможностей для настройки, но я думаю, что это гораздо проще, чем использовать CIFilter с SKEffectNode что, вероятно, другой логический вариант, который у вас есть для этого.

Другие вопросы по тегам