Cropping an SKReferenceNode - swift

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

Related

How do I load a sprite from .sks file to display in my ARSKView?

I have an ARKit app that uses SpriteKit to display SKNodes that correspond to ARAnchors in the AR session. I am using the ARSKViewDelegate method
func view(_ view: ARSKView, nodeFor anchor: ARAnchor) -> SKNode?
to display the nodes. This works for nodes I create and return in that method, but I have some more complicated nodes that I would like to design in an .sks file and then display for some anchors. When I return a node instantiated from an .sks file in that method the app crashes with an error saying the node is already in the scene (if the node is in the .sks file for the current scene), or already has a parent (if it is in an .sks file for a different scene).
How can I display sprites in ARKit that are drawn in an .sks file?
Try this approach:
if let view = self.view as! SKView? {
if let scene = SKScene(fileNamed: "mySpriteKitScene") {
guard let clone = scene.childNode(withName: "myRedSprite")?
.copy() as? SKSpriteNode
else { return }
clone.name = "mySecondRedSprite"
clone.position.x += 250
clone.physicsBody = SKPhysicsBody()
scene.addChild(clone)
view.presentScene(scene)
}
}

SKEmitterNode doesn't work when used as a reference node within another reference node

I'm having an issue with using a particle emitter with reference nodes.
It works when it's referenced directly in the scene (for example, inside the GameScene.sks file), but it doesn't work when it's referenced within another .sks file, and that file/node is then referenced in GameScene.sks, although other referenced nodes appear correctly.
Here's the hierarchy:
GameScene.sks
- Reference Node to RocketFire.sks (emitter) <-- works
- Reference Node to Rocket.sks
Rocket.sks
- Reference Node to RocketFire.sks (emitter) <-- doesn't work in GameScene
- Reference Node to Engine.sks (non-emitter nodes) <-- works in GameScene
RocketFire.sks (SpriteKit particle file)
Engine.sks (SpriteKit scene file with non-emitter nodes)
Is there the wrong way to use Reference Nodes or particle emitters?
(Let me know if more info is needed)
EDIT:
I actually made it work some of the time. Sometimes it would suddenly start working when I remove and re-add the referenced nodes in all the files, or if I change the node hierarchy. But still, I can't identify a consistent reason for when it works and when it doesn't, because since changing it and putting it back to when it worked, doesn't fix it. (Feels like a bug, honestly)
The problem is that since iOS 10 all sprite nodes are by default paused. This includes your scene. It is not enough to just make your scene not paused (isPaused = false) you have to make the children not paused as well.
for your example I added to smoke emitters to my plane.sks file which is dragged onto my GameScene.sks file as an SKReferenceNode
and in my Plane.swift file, note I've put isPaused = false on one of the emitters and it still doesn't work
var leftSmoke: SKEmitterNode!
var rightSmoke: SKEmitterNode!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
if let leftSmoke = self.childNode(withName: "//leftSmoke") as? SKEmitterNode {
self.leftSmoke = leftSmoke
leftSmoke.isPaused = false
}
if let rightSmoke = self.childNode(withName: "//rightSmoke") as? SKEmitterNode {
self.rightSmoke = rightSmoke
}
}
the plane with the scene running (no emitters)
set the plane object to not paused (inside Plane.swift)
func setup() {
self.isPaused = false
if let leftSmoke = self.childNode(withName: "//leftSmoke") as? SKEmitterNode {
self.leftSmoke = leftSmoke
leftSmoke.isPaused = false
}
if let rightSmoke = self.childNode(withName: "//rightSmoke") as? SKEmitterNode {
self.rightSmoke = rightSmoke
}
}
and voila
FYI
It probably works intermittently for you because you might have a break point set. Whenever you have a breakpoint set and the code stops it unPauses nodes automatically

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

Reuse same sprite node within multiple scenes in sprite kit using Swift

I create sprite node in my GameScene as the following. I would like to reuse createNodeA1 or nodeA1 in other SKScene. How can I do that?
import SpriteKit
class GameScene: SKScene {
var nodeA1: SKNode!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(size: CGSize) {
super.init(size: size)
// Add sprite node to the scene
nodeA1 = createNodeA1()
addChild(nodeA1)
}
}
// Create dot 1
func createNodeA1() -> SKNode {
let spriteNode = SKNode()
spriteNode.position = CGPointMake(CGRectGetMidX(self.frame)/1.5, CGRectGetMidY(self.frame)/2.0)
let sprite = SKSpriteNode(imageNamed: "dot_1")
sprite.zPosition = 3.0
sprite.name = "A1_Broj"
spriteNode.addChild(sprite)
return spriteNode
}
}
There is a few ways to do this.
You could subclass your other scenes to be subclass of the scene with the loadNode function which gives those scenes access to that function.
I asked a question about this last year
Swift multiple level scenes
Another way that might be a bit easier if you are not comfortable with scene subclassing is to just create a subclass of the node itself.
So you create a class
enum EnemyType {
case Normal
case Special
}
class NodeA1: SKSpriteNode {
init(imageNamed: String, enemyType: EnemyType) {
let texture = SKTexture(imageNamed: imageNamed)
if enemyType == .Normal {
super.init(texture: texture, color: SKColor.clearColor(), size: texture.size())
else {
// other init
}
self.zPosition = 1
self.name = ""
// add physics body, other properties or methods for the node
}
}
Than in your SKScenes you can add the node in the init method like so
nodeA1 = NodeA1(imageNamed: "ImageName", enemyType: .Normal)
nodeA1.position = ....
addChild(nodeA1)
this way ever scene where you add the node will use the subclass and therefore include all the properties, set up etc for that node. Another benefit with subclassing is that you could loop through all your nodes using
self.enumerateChildNodesWithName...
and than call custom methods on all nodes.
If you want to subclass your scenes than you would create your baseScene
class BaseScene: SKScene {
// set up all shared stuff in didMoveToView
// have your node function here
// touches began
// physics word and contact collision
// all other stuff that needs to be shared between all level scenes
}
Than your subsequent level scenes would look something like this
class Level1Scene: BaseScene {
override func didMoveToView(view: SKView) {
super.didMoveToView(view) // This lines imports all stuff in BaseScene didMoveToView
// do level 1 specific setUps.
// you can call any function or property from BaseScene, e.g the loadNode function.
}
You than load you level scenes as usual, e.g you transition to level 1 scene and it will automatically use/have access to all the superclass methods and sprites (BaseScene).
So you never call baseScene directly, its gets called automatically.
This applies for other methods in baseScene too, so say you have a Update method in BaseScene.
override func update(currentTime: CFTimeInterval) {.... }
This will work across all your level scenes which are subclasses of BaseScene.
But what happens if you need to add some specific stuff to the update method only relevant in 1 level scene and not all level scenes?
It would be the same process, you create a new update func in the LevelScene and call super.
override func update(currentTime: CFTimeInterval) {
super.update(currentTime) // this calls the baseScene Update method
/// specific stuff for that level only
}
Super simply means the super class of the currentScene, which is BaseScene if the scene is a subclass of it.
Is this helping?
This is additional answer information in terms of subclass of the baseScene. We can create node1thru node10 all in baseScene. Then in Leve1Scene which is subclass of the baseScene, all we have to do is in didMoveToView function state node1.position = CGPointMake(....) for each node that we need in Level1Scene where we would specify node's position.
If we do not need to load all of the 10 nodes in Level1Scene, for example, let's say we don't need to load to the scene node10 we can simply in didMoveToView function just state node10.removeFromParent() and this node will not be loaded to Level1Scene but rest of 9 nodes will.
Note that this example uses only 10 nodes, but you can go with any number of nodes in your baseScene.
This way of subclassing will save you a lot repeatable code in subclasses.

Detecting scene from viewcontroller

I am working on a game in Xcode with swift/spritekit.
I want to add the background from the GameViewController but I have different scenes with each different backgrounds: GameScene / MenuScene / MapScene.
Now how do I detect in gameviewcontroller which scene is running so that I can add the right background to it(as UIImage)?
And how do I set Z-position(Z-index) of an UIImage?
You'll have to track which scene is present yourself. Something like this:
class GameViewController {
var mapScene: SKScene?
var menuScene: SKScene?
var gameScene: SKScene?
var currentScene: SKScene?
func addBackground() {
if currentScene === mapScene {
// ...
} else if currentScene === menuScene {
// ...
} else if currentScene === gameScene {
// ...
}
}
}
If the UIImage is inside an SKNode, then you can set its zPosition property. If it's just a UIImage in normal UIKit elements, then it's implicit based on when it was added as a subview. If you want it on top, remove it from its parent and re-add it:
view.removeFromSuperview()
self.parentView.addSubview(view)
You don't need to detect which scene is running if you simply add the appropriate background image in the scene's implementation. Also, I recommend you avoid adding a UIImage to your view, since you will need to manually remove it when you transition to a new scene. I suggest you create a sprite node with the background image as its texture and add that to your scene with a negative value for its zPosition. Also, you should consider setting the "background" sprite's anchor point and position to CGPointZero.
For example, in 'didMoveToView'
let backgroundNode = SKSpriteNode(imageNamed:"MenuSceneBackground")
backgroundNode.position = CGPointZero
backgroundNode.anchorPoint = CGPointZero
backgroundNode.zPosition = -1000
addChild(backgroundNode)
EDIT: Add the following to remove the gesture recognizers as needed
override func willMoveFromView(view: SKView) {
view.removeGestureRecognizer(swipeGesture)
}