Как вы можете создать свечение вокруг спрайта через 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
подкласс, который составляет несколько встроенных фильтров. Такой фильтр будет включать такие шаги:
- Создайте изображение, которое будет использоваться в качестве синего свечения. Вероятно, есть несколько достойных способов сделать это; один должен использовать
CIColorMatrix
создать монохромную версию входного изображения. - Масштабировать и размыть изображение свечения (
CIAffineTransform
+CIGaussianBlur
). - Композицию исходного входного изображения поверх изображения свечения (
CISourceOverCompositing
).
Когда у вас есть CIFilter
подкласс, который делает все это, вы можете использовать его с SKEffectNode
чтобы получить в реальном времени свечение вокруг детей узла эффекта. Здесь он запускается в шаблоне Xcode "Sprite Kit Game" на iPad 4:
Я получил это и запустил через несколько минут, набрав пользовательский класс фильтра, используемый для аналогичного эффекта в презентации 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
что, вероятно, другой логический вариант, который у вас есть для этого.