In SpriteKit, using an Effect node, I apply a simple gradient to a simple rectangle.
That seems to work fine, however adding it to the scene causes a NSException
.
PLEASE: What am I doing wrong?
import SpriteKit
import GameplayKit
import CoreImage
class GameScene: SKScene {
override func didMove(to view: SKView) {
let rectangle = SKSpriteNode(color: .black, size: CGSize(width: 480, height: 80))
let gradientOne = CIFilter(name: "CILinearGradient", withInputParameters:[
"inputPoint0" : CIVector(x: 0, y: 0), "inputColor0" : SKColor.red,
"inputPoint1" : CIVector(x: 200,y: 0), "inputColor1" : SKColor.blue])
//
let effectGradient = SKEffectNode()
effectGradient.filter = gradientOne
effectGradient.addChild(rectangle)
effectGradient.blendMode = .alpha
effectGradient.shouldRasterize = true
addChild(effectGradient) //***** CRASH IS HERE *****
}
}
Here's the output of the crunch:
0 CoreFoundation 0x000000010886ad4b __exceptionPreprocess + 171
1 libobjc.A.dylib 0x000000010565021e objc_exception_throw + 48
2 CoreFoundation 0x000000010886ac99 -[NSException raise] + 9
3 CoreImage 0x0000000105b2f34d -[CIFilter setValue:forUndefinedKey:] + 137
4 Foundation 0x000000010515e9df -[NSObject(NSKeyValueCoding) setValue:forKey:] + 291
5 SpriteKit 0x0000000105efa395 _ZN13SKCEffectNode17addRequistePassesEP13SKCRenderInfoPNSt3__14listINS2_10shared_ptrI13SKCRenderPassEENS2_9allocatorIS6_EEEE + 377
6 SpriteKit 0x0000000105f3fbde _ZN11SKCRenderer18doBuildRenderGroupEP7SKCNode14SKCRenderState15matrix_float4x4 + 488
7 SpriteKit 0x0000000105f3fecb _ZN11SKCRenderer18doBuildRenderGroupEP7SKCNode14SKCRenderState15matrix_float4x4 + 1237
8 SpriteKit 0x0000000105f3ef1c _ZN11SKCRenderer16buildRenderGroupERKNSt3__110shared_ptrI18SKCRenderSortGroupEE + 1264
9 SpriteKit 0x0000000105f3b686 _ZN11SKCRenderer15buildRenderPassERKNSt3__110shared_ptrI13SKCRenderPassEE + 88
10 SpriteKit 0x0000000105f3af7e _ZN11SKCRenderer6renderEP7SKCNodeDv4_fRKNSt3__110shared_ptrI15jet_framebufferEEDv4_j15matrix_float4x4bP12NSDictionaryP8SKCStatsSE_d + 2150
11 SpriteKit 0x0000000105ebec09 __59-[SKView _renderSynchronouslyForTime:preRender:postRender:]_block_invoke + 855
12 SpriteKit 0x0000000105ebe82a -[SKView _renderSynchronouslyForTime:preRender:postRender:] + 249
13 SpriteKit 0x0000000105ec1884 -[SKView layoutSubviews] + 177
14 UIKit 0x0000000106187ab8 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 1237
15 QuartzCore 0x000000010db60bf8 -[CALayer layoutSublayers] + 146
16 QuartzCore 0x000000010db54440 _ZN2CA5Layer16layout_if_neededEPNS_11TransactionE + 366
17 QuartzCore 0x000000010db542be _ZN2CA5Layer28layout_and_display_if_neededEPNS_11TransactionE + 24
18 QuartzCore 0x000000010dae2318 _ZN2CA7Context18commit_transactionEPNS_11TransactionE + 280
19 QuartzCore 0x000000010db0f3ff _ZN2CA11Transaction6commitEv + 475
20 QuartzCore 0x000000010db0fd6f _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv + 113
21 CoreFoundation 0x000000010880f267 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23
22 CoreFoundation 0x000000010880f1d7 __CFRunLoopDoObservers + 391
23 CoreFoundation 0x00000001087f38a6 CFRunLoopRunSpecific + 454
24 UIKit 0x00000001060bcaea -[UIApplication _run] + 434
25 UIKit 0x00000001060c2c68 UIApplicationMain + 159
26 testinGradients 0x000000010506e73f main + 111
27 libdyld.dylib 0x000000010981a68d start + 1
28 ??? 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
Despite Benzi's answer working, I'm confused.
Of his technique, three things confuse:
Confusion 1:
var inputImage: CIImage?
To me, this looks like inputImage is not initialised, not created, only defined as being of type CIImage — OPTIONAL! So it doesn’t yet exist.
Confusion 2:
override var outputImage: CIImage? {
How does this closure work, why is it here?
Confusion 3:
guard let inputImage = inputImage else {
return nil
}
This says make inputImage a CIImage, but where does its contents and definition come from? Size, shape, etc… where from?
We know its getting details of the node instance of SKSpriteNode
passed into here, somehow, because it’s used - right here:
let extent = inputImage.extent
But HOW does the SKSpriteNode
added as a child of SKEffectNode
become the inputImage
value within the CIFilter
subclass?
The Core Image filter
provided to an SKEffectNode
must have a single inputImage
parameter and produce a single outputImage
parameter (in addition to other input parameters to control the actual effect)
CILinearGradient
does not have the inputImage
and outputImage
parameters, and hence cannot be used as an SKEffectNode
filter.
If you wish to use CILinearGradient
, you need to create a custom CIFilter as follows:
class SpriteKitGradient : CIFilter {
var point0 = CIVector.init(x: 0, y: 0)
var point1 = CIVector.init(x: 0, y: 0)
var color0 = CIColor.black()
var color1 = CIColor.white()
var inputImage: CIImage?
override var outputImage: CIImage? {
guard let inputImage = inputImage else {
return nil
}
let gradient = CIFilter(name: "CILinearGradient", withInputParameters:
[
"inputPoint0" : point0, "inputColor0" : color0,
"inputPoint1" : point1, "inputColor1" : color1
]
)
let extent = inputImage.extent
return gradient!.outputImage?.cropping(to: extent)
}
}
class GameScene: SKScene {
override func didMove(to view: SKView) {
let node = SKSpriteNode(color: .green, size: CGSize(width: 100, height: 100))
let gradient = SpriteKitGradient()
gradient.color0 = CIColor.blue()
gradient.color1 = CIColor.red()
gradient.point0 = CIVector(x: 0, y: 0)
gradient.point1 = CIVector(x: 200, y: 0)
let effectNode = SKEffectNode()
effectNode.filter = gradient
effectNode.addChild(node)
effectNode.position = CGPoint(x: 200, y: 200)
addChild(effectNode)
}
}
This should create: