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

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)
}

Related

How to create image of SKSpriteNode?

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.

Cropping an SKReferenceNode

I have setup a SKScene to be used as an SKReferenceNode. The sled is an SKSpriteNode with a custom class defined in the reference node scene and all the dogs are children of the sled sprite. The custom class and using the reference node is all fine, my problem is i'm unable to "crop" the scene to only show N number of dogs. Its as if after dropping the reference node into another scene, that parent scene is ignoring the reference node's width/height parameters and just displaying everything in it. So the question is, is this possible? Or do SKReferenceNodes not adhere to the scene width and heights properties when used in a parent scene?
The first image is of the full view reference scene (70x425). Second image is of what the frame should look like when i change the height programmatically if I only want the bottom 2 dogs to be shown.
class SledTeam: SKSpriteNode {
var dogTeam = [Int]()
required init?(coder aDecoder: NSCoder) {
switch dogTeam.count {
case 7,8:
self.scene?.size.height = 425
break
case 5,6:
self.scene?.size.height = 335
break
case 3,4:
self.scene?.size.height = 260
break
case 1,2:
self.scene?.size.height = 190
break
default:
break
}
}
}
My gues is to use a invisible sprite node in your sks reference file with the size to contains all nodes.
After call this invisible "base" node and crop it.
To get the invisible node, you can use this extension:
extension SKReferenceNode {
func getBasedChildNode () -> SKNode? {
if let child = self.children.first?.children.first {return child}
else {return nil}
}
}
for other details see my old post:
Add SKReferenceNode/SKScene to another SKScene in SpriteKit

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

How to add sub-child to a child of self in swift

I am trying to add two SKSpriteNodes with their respective textures in swift, however the "Blade" does not show up on screen during the simulation. I am having no trouble getting the "Handle" to appear though. So my question is, what is the proper way to add a child of a child of self in swift so that everything works as intended?
var Handle = SKSpriteNode(imageNamed: "Handle.png")
var Blade = SKSpriteNode(imageNamed: "Blade.png")
override func didMoveToView(view: SKView) {
Handle.position = CGPointMake(self.size.width / 2, self.size.height / 14)
Blade.position = CGPointMake(Handle.position.x, Handle.position.y + 124)
self.addChild(Handle)
Handle.addChild(Blade)
}
This should get you close to where you expected it.
Blade.position = CGPointMake(0, 124)
Keep in mind when you add a sprite to another sprite the position is the position "within" that sprite. If you don't change the anchor that starts at the center of its parent. So you were starting in the center of Handle and adding half the width of the scene. Because Handle was already centered in the scene blade was off the scene.
Hopefully that makes sense and is helpful.
Also as lchamp mentioned you should lowerCamelCase
var Blade = SKSpriteNode(imageNamed: "Blade.png")
This will help you in the future identifying Classes vs Variables.

How to copy SKSpriteNode with SKPhysicsBody?

I am curious about a situation that I came across today when trying to unarchive and copy a SKSpriteNode from one SKScene to another. In the output from the playground below you can see that both linearDamping and angularDamping or not being maintained after the copy (they seem to be dropping back to default values)
// PLAYGROUND_SW1.2 - SKSpriteNode Copy
import UIKit
import SpriteKit
// ORIGINAL
let spriteNode = SKSpriteNode()
spriteNode.name = "HAPPY_NODE"
let size = CGSize(width: 55.0, height: 150.0)
let physics = SKPhysicsBody(rectangleOfSize: size)
physics.linearDamping = 0.123
physics.angularDamping = 0.456
spriteNode.physicsBody = physics
// COPY
let spriteCopy = spriteNode.copy() as! SKSpriteNode
// ORIGINAL
spriteNode.name
spriteNode.physicsBody?.linearDamping
spriteNode.physicsBody?.angularDamping
spriteNode.physicsBody?.area
// COPY
spriteCopy.name
spriteCopy.physicsBody?.linearDamping
spriteCopy.physicsBody?.angularDamping
spriteCopy.physicsBody?.area
PLAYGROUND OUTPUT
I am not sure that I am copying this correctly, both SKSpriteNode and SKPhysicsBody conform to NSCopying If you look at the output above the area property is maintained after the copy and to my knowledge this is based on the size specified when the SKPhysicsBody was created.
Can anyone cast some light on this and maybe provide me with a pointer as to how I should be deep copying an SKSpriteNode?
I take one way to resolve your problem, probably is not the best way, but
//COPY
let spriteCopy = spriteNode.copy() as SKSpriteNode
let physicsCopy:SKPhysicsBody = spriteNode.physicsBody!;
...
//COPY PHYSICS BODY HARD MODE
spriteCopy.physicsBody = physicsCopy;
To fix this problem, I created one extension, and #mogelbuster suggested override default copy(), ant it sounds great.
extension SKSpriteNode
{
override open func copy() -> Any {
let node = super.copy() as! SKSpriteNode;
node.physicsBody = super.physicsBody;
return node;
}
}
With this extension you can do it, the default copy() method return Any because this you need cast to SKSpriteNode.
// COPY
let spriteCopy = spriteNode.copy() as! SKSpriteNode;
// ORIGINAL
spriteNode.name
spriteNode.physicsBody?.linearDamping
spriteNode.physicsBody?.angularDamping
spriteNode.physicsBody?.area
// COPY
spriteCopy.name
spriteCopy.physicsBody?.linearDamping
spriteCopy.physicsBody?.angularDamping
spriteCopy.physicsBody?.area