How to create image of SKSpriteNode? - sprite-kit

I have a SKSpriteNode with a lot of child nodes.
I use that node with all the children very frequently in my game.
A better approach would be to build this node once with all the children, create an image (SKTexture) and use the created texture, as opposed to recreating the SKSpriteNode and all its children.
Is there any way to do this?

There is actually a very easy way to do this.. I do this all the time to convert ShapeNodes to SpriteNodes (because shapes need 1 draw call per shape, and sprites dont)
let texture = SKView().texture(from: desiredNode)
let newSprite = SKSpriteNode(texture: texture)
or, in your gamescene for brevity:
let newSprite = SKSpriteNode(texture: view!.texture(from: desiredNode))

SKEffectNode & shouldRasterize
You can us SKEffectNode and set shouldRasterize to true.
let effectNode = SKEffectNode()
effectNode.shouldRasterize = true
scene.addChild(effectNode)
The SKEffectNode's output is rasterized and cached internally. This cache is reused when rendering.
Now just add your sprites to effectNode
effectNode.addChild(sprite0)
effectNode.addChild(sprite1)
effectNode.addChild(sprite2)
The first time the effectNode is rendered, the produced image will be automatically cached.
And the cached version will be used for the next frames.

Related

SkSpritenode normal texture not restoring even with restore on?

Ok. I am having a very odd problem with SKTextures and animating through - I have created a sprite node like this and set the normal texture to the last image in the array of sktextures I will eventually animate through. The texture (not normal texture) is set to the first image in the array.
theSprite = SKSpriteNode()
theSprite.texture = atlas[0]
theSprite.normalTexture = atlas[atlas.count-1]
theSprite.size = CGSize(width: 500, height: 1491)
theSprite.position = CGPoint(x: basePos.x+1, y: basePos.y+2)
superScene.addChild(theSprite)
when clicked it runs this func and as I read here https://developer.apple.com/reference/spritekit/skaction/1417810-animate I have restore set to TRUE so it SHOULD end on the last frame of the array:
let anim = SKAction.animate(with: atlas, timePerFrame: animSpeed, resize: false, restore: true)
theSprite.run(anim, completion: {() -> Void in
})
It should go back to normal texture (which is a blank image), but it doesn't. The only way it goes back to this is if I don't set the texture to begin with, which I need to do. How can I make it restore?
Suggested Solutions
Try setting your sprite to the last texture in the atlas like this: theSprite.texture = atlas[atlas.count-1]. Then run your code again. You should see that it restores to back to atlas[atlas.count-1].
If your animation stops at a texture other than your last atlas texture, then you could try this. Set theSprite.texture = atlas[atlas.count-1] within your animation's completion block. You would also set restore to false in this case. This will animate your sprite then set the final texture to whatever you want, in this case it will be set to the last atlas texture.
Explanation
Restore will return your sprite back to the texture that it had before the animation began. So in your case with your current code, it should end the animation with the texture atlas[0]. It is not meant to end on the last frame of the array as you stated.
According to the Apple dev reference, if restore is true:
When the action completes, the sprite’s texture is restored to the
texture it had before the action completed.
https://developer.apple.com/reference/spritekit/skaction/1417810-animate

Swift 3 (SpriteKit): Create a texture from a node tree

I have been trying to render a node tree into an SKTexture as a possible solution to another question here. I tried searching for an answer to this, and I came across one of Apple's Developer pages (view it here). It says the command which converts a node tree into an SKTexture is this:
func texture(from node: SKNode) -> SKTexture?
Although, the code is incomplete since there is no code which executes the conversion of a node tree to an SKTexture. Am I doing something wrong or what is the complete code that I should use to render a node tree into an SKTexture.
Thanks for any advice, help, and answers!
texture(from:) is an instance method of SKView that takes a node (with zero or more children) as a parameter and returns an optional SKTexture. Since the returned value is an optional, you'll need to unwrap it before using it.
From didMove(to:), you can use the view parameter to create a texture
if let texture = view.texture(from: node) {
let sprite = SKSpriteNode(texture:texture)
addChild(sprite)
}
From other methods in the SKScene subclass, use optional chaining
if let texture = self.view?.texture(from: node) {
let sprite = SKSpriteNode(texture:texture)
addChild(sprite)
}
You can also render the portion of the node-tree's contents inside of a rectangle with
let rect = CGRect(x:-width/2, y:-height/2, width:width, height:height)
if let texture = self.view?.texture(from: node, crop: rect) {
let sprite = SKSpriteNode(texture:texture)
addChild(sprite)
}

Particle emitter not showing over image (swift spritekit)

This is a pretty basic question (Im new with swift and spritekit).
So anyway Im adding a particle emitter to a spritenode(call it ball) and adding the ball to my scene. This ball is on top of another spritenode that represents the background. When I run the app, the ball appears fine but the particle emitter doesn't show. The emitter shows fine when I remove the background, but not when I add the background. I will post a code example...
//Background
let background = SKSpriteNode(imageNamed:"Background")
background.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame))
background.zPosition = 0
self.addChild(background)
//Ball
let ball = SKSpriteNode(imageNamed: "ball")
ball.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame))
ball.zPosition = 1
ball.physicsBody = SKPhysicsBody(rectangleOfSize: ball.frame.size)
ball.physicsBody?.usesPreciseCollisionDetection = true
//Emitter
let emitter = SKEmitterNode(fileNamed: "emitter")
emitter.targetNode = self
ball.addChild(emitter)
self.addChild(ball)
Like I said, this seems to be a pretty basic question. I just don't know what Im doing wrong. Just to reiterate, when I run this, the emitter doesn't show. But if I take away the background, the emitter shows.
Thanks For the help :)
Simple answer - you just need to set your emitter's zPosition to be higher than the background's (but less than the ball). zPosition determines the order in which things are rendered, and the higher the number the later it is rendered (thus putting it on top).
It's also possible that your emitter nodes and your background are the same color so it appears as though it isn't there.
So I tried changing the zPosition of emitter and it didn't change anything. But when I changed the zPosition of the background to -1 the emitter showed. I wish I knew why this works haha. But thanks for the suggestions :)
I had the same thing happen but when you said it doesn't happen when you comment out the background, I remembered that there is a command in GameViewController that you have to set false.
skView.ignoresSiblingOrder = false

SpriteKit - Adding a blur to the entire Scene

I am trying to blur my entire GameScene when my pause button is pressed. I have a method called blurSceen() but it doesn't seem to be adding the effect to the scene. Is there a way I can accomplish this or am I doing something wrong? I have viewed other posts about this topic but haven't been able to accomplish the effect.
func blurScreen() {
let effectsNode = SKEffectNode()
let filter = CIFilter(name: "CIGaussianBlur")
let blurAmount = 10.0
filter!.setValue(blurAmount, forKey: kCIInputRadiusKey)
effectsNode.filter = filter
effectsNode.position = self.view!.center
effectsNode.blendMode = .Alpha
// Add the effects node to the scene
self.addChild(effectsNode)
}
From the SKEffectNode docs:
An SKEffectNode object renders its children into a buffer and optionally applies a Core Image filter to this rendered output.
The effect node applies a filter only to its child nodes. Your effect node has no children, so there's nothing to apply a filter to.
Probably what you want is to add an effect node to your scene early on--but don't set the filter on it yet--and put all the nodes that you'll later want to blur in as its children. When it comes time to apply a blur, set the filter on the (already existing, already with children) effect node.
I had the same issue trying to blur the whole SKScene and it just wasn't working. The missing piece of the puzzle was this line:
shouldEnableEffects = true
Swift 4:
from gameScene:
let blur = CIFilter(name:"CIGaussianBlur",withInputParameters: ["inputRadius": 10.0])
self.filter = blur
self.shouldRasterize = true
self.shouldEnableEffects = true

Frame of SKShapeNode is offset

I am trying to add a physicsBody to an SKShapeNode that is a rectangle. I built my PhysicsBody using fromEdgesWithLoop using the Node's frame as the CGRect to build it.
Using the debug option to show PhysicsBody, I see that it is actually not centered on my Sprite.
I searched the web to find that I should use this:
scene.scaleMode = .ResizeFill
Sadly, I tried all scaleModes and there are no differences.
I also verified my GameScene's size and it fits the iPad2 screen (my target), 1024x720.
The SKShapeNode is a child of the Scene.
I tried to set the position of my node in the center of the Scene before adding a PhysicsBody with its frame, but this changed nothing. The node was already in the center of the scene.
myShape.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame))
myShape.physicsBody = SKPhysicsBody(edgeLoopFromRect: myShape.frame)
The position of an Edge Loop is relative to the node it's attached to. In general, for an edgeLoopFromRect, the position is given by
edge_loop_position = node.position + rect.frame.origin
In this case, your physics body is offset by the position of myShape. One way to fix this issue is to attach the edge loop to the scene instead of the shape node:
self.physicsBody = SKPhysicsBody(edgeLoopFromRect: myShape.frame )
Alternatively, you can subtract the node's position from the frame's position with
let rect = CGRect(origin:CGPoint(x:myShape.frame.origin.x-myShape.position.x, y:myShape.frame.origin.y-myShape.position.y), size:myShape.frame.size)
myShape.physicsBody = SKPhysicsBody(edgeLoopFromRect:rect)