Change Texture In Scene Editor Programmatically - sprite-kit

I've added a color sprite, named "arm" to my Scene Editor but for a bunch of reasons I need to access that color sprite and change the texture programmatically. Another way of stating my goal, I want to link the node to my GameScene swift file and change texture using a texture from an array. Does anyone know how to do this?

First things first, you want to link the sprite node from your GameScene.sks file to your GameScene.swift file.
To do this, in GameScene.swift near the top (under the "class GameScene: SKScene {") use this:
var armNode: SKSpriteNode?
In your didMove(to view:) section do this:
armNode = childNode(withName: "arm") as? SKSpriteNode
Now 3 different ways to change the color/texture of the SKSpriteNode depending on what you are doing:
armNode.colour = UIColor.red
armNode.texture = SKTexture(imageNamed: "your arm image here")
armNode.texture = textureArray[2]
The 3rd version uses a texture from an array of textures

Related

How to use SpriteKit Particle File in a project developed with SceneKit?

I'm making a simple ARKit App with SceneKit and need to add some particle effect. Usually I could create a SceneKit Particle File, design some particle effect and use it like this
let smoke = SCNParticleSystem(named: "xxx.scnp", inDirectory: nil)!
let hole = SCNNode()
hole.addParticleSystem(smoke)
But since Xcode12, I can only create a SpritKit Particle File whose suffix is .sks. The code above could not work anymore with this file. I am a newbie in ARKit. May anyone tell me how to integrate the particle effect into my SceneKit project? Thanks a lot.
SpriteKit particles implementation
If you wanna use 2D particles in 3D scene, there are two regular ways to show SpriteKit scenes in SceneKit. The first way is to assign SKScene as material for SceneKit's geometry:
// 2D scene
let skScene = SKScene()
guard let emitter = SKEmitterNode(fileNamed: "sparks.sks")
else { return }
emitter.position = CGPoint(x: 10, y: 25)
emitter.targetNode = skScene
skScene.addChild(emitter)
// 3D scene
let plane = SCNPlane(width: 5, height: 5)
plane.materials.first?.diffuse.contents = skScene
let planeNode = SCNNode(geometry: plane)
sceneView.scene.rootNode.addChildNode(planeNode)
The second way – assign SKScene to virtual surrounding environment of SceneKit.
sceneView.scene.lightingEnvironment.contents = skScene
SceneKit particles implementation
The regular way for working with particles in SceneKit is its native particle system:
let particleSystem = SCNParticleSystem()
particleSystem.birthRate = 50
particleSystem.particleSize = 0.5
particleSystem.particleLifeSpan = 10
particleSystem.particleColor = .green
let particlesNode = SCNNode()
particlesNode.addParticleSystem(particleSystem)
sceneView.scene.rootNode.addChildNode(particlesNode)
P. S.
About deprecated preconfigured SCNP file and its replacement read HERE.
If you want to setup such phenomenon as 3D fire, the best way to do it – do it in Scene graph. For better result use 2 or 3 particle system objects with different parameters.
For better understanding how to setup 3D fire based on png samples (sources) open Apple Motion 5 app scene and click on Inspector button.
Then, try to implement a fire with the same look in SceneKit's Scene graph.
For some unknown reason Apple removed the SCNP (SceneKit particle file) template from the recent XCode editions.
You have the following options:
Download any sample code project from Apple or GitHub that contains one or more .scnp files. Grab the file from there and implement it into your AR-Project. You can still edit the file within XCode as usual. (Particle Systems Editor)
Create your particle system entirely in code.
like so:
let myParticleSystem = SCNParticleSystem()
then you configure everything you want within the instance: myParticleSystem

Merging or flattening background nodes to improve game performance?

I want to be able to improve my game's performance by somehow "flattening" my background nodes.
This youtube video demonstrates how I build up my background graphics.
But, I have my background graphics set up like this.
I use two textures like stamps and repeat then.
In this case... one mountain texture with a snowy top...
One mountain texture without the snow.
I alter the zPosition of these stamps for a "layering" effect.
I then use "fills" which are just SKSpriteNodes of solid grey to layer over parts that need a grey fill.
E.g. Before fill nodes are added
A colour screen with alpha is then added on top to give the mountain a faded looked.
E.g. Before
E.g. After
The game sometimes freezes up... but it doesn't freeze when I remove these graphics.
Is there a way to improve performance by merging or flattening my background graphics nodes based on zPosition as a scene is loading up?
How would I do this?
Would this improve performance?
Or what would be the best way to improve performance in my case?
I think this question is similar...
Merge all SKSpriteNode children into a single SKSpriteNode
But, how do I do this in Swift and take zPosition and alpha into account so I don't lose the layering effects?
Somethings you may want to do:
Separate your foreground and background into separate SKS files, and load them
into your main SKS file via SKReferenceNode
Convert these SKS files into textures instead of SKReferenceNodes via code using view!.texture(from: node)
Ensure your atlas is not being broken up in a way that adds to the draw count
Design your code in a way that not all nodes are on the scene, the more nodes the slower your code becomes.
If you have SKPhysicsBody's, try to use as few as possible. This can be done my merging multiple bodies together with SKPhysicBody(bodies:) or creating 1 body with a polygon that goes around multple objects.
Also, if your bodies are not moving via physics (Not by SKActions) then make sure that isDynamic is set to false
If you are running SKActions, make sure you use as few of these as possible. For example, if you have 4 different nodes travelling left at 10 points per second, you can put these 4 nodes into a parent node, and run the action on the parent node.
Ok so here is an example of what you have now.. all your nodes here like this:
How to execute the bitblit easily is to move all these under a new empty node called "background"
Now head to your swift file, and set up an placeholder variable for the new node we will create in code
class GameScene: SKScene {
var blitBackground = SKSpriteNode()
override func didMove(to view: SKView) {
}
}
Now , add this handy function that you can use in all of your projects if desired:
func blit(from node: SKNode) -> SKSpriteNode {
return SKSpriteNode(texture: SKView().texture(from: node))
}
and here is the method we will use to initialize our bitblit background, and remove the laggy one from the game:
func blitTheBackground() { // use in didMove
let laggyBackground = childNode(withName: "background")!
blitBackground = blit(from: laggyBackground)
laggyBackground.removeFromParent()
addChild(blitBackground)
}
Here is the completed scene:
class GameScene: SKScene {
var blitBackground = SKSpriteNode()
func blit(from node: SKNode) -> SKSpriteNode {
return SKSpriteNode(texture: SKView().texture(from: node))
}
func blitTheBackground() {
let laggyBackground = childNode(withName: "background")!
blitBackground = blit(from: laggyBackground)
laggyBackground.removeFromParent()
addChild(blitBackground)
}
override func didMove(to view: SKView) {
blitTheBackground()
}
}
and it reduced the node count from 10 to 4... hopefully, for you it will help with your draw count too!!
NOTE, this method is EXTREMELY effective with shapenodes!!
If you need to do this in a loop, be sure to place it in an autorelease pool to be sure that the memory is freed up after it is used. Creating textures from the view uses a lot of memory, so you can expect to get errors if you are not careful with it.
for i in 0..<5
{
autoreleasepool{
let laggyBackground = childNode(withName: "background\(i)")!
blitBackground = blit(from: laggyBackground)
laggyBackground.removeFromParent()
addChild(blitBackground)
}
}

Create SKSpriteNode with an Asset Programmatically

I'm attempting to programmatically create an SKSpriteNode and give it a texture that is in my Assets.xcassets folder. I figured this would be a basic procedure, but I seem to be having a lot of troubles.
For a reference, here is my asset catalog with the ground asset I'm trying to set to a SKSpriteNode:
Here's the code to make the sprite node:
sprite.texture = SKTexture(image: UIImage(named: spriteName)!)
sprite.anchorPoint = CGPoint(x: 0, y: 1)
sprite.position = CGPoint(x: 0, y: 0)
referenceNode.addChild(sprite)
The variable "spriteName" in the first line is having the String "ground" passed into it. However, when I load the game, the ground is missing. Just to make sure the nodes were actually there, I commented out the texture code and just gave them size and a visible color. This worked out so I know the problem is definitely in the asset reference, not in the code logic.
Any point in the right direction would be greatly appreciated. If more information is needed, please do ask as specifically as possible :)
Thanks,
Matthew
EDIT:
My original question may have been a bit too complicated. To rephrase in simpler terms:
I have an asset in my asset library called ground. However, when I set the texture of a SKSpriteNode using this code...
sprite.texture = SKTexture(image: UIImage(named: "ground")!)
the texture doesn't show up in app. My question: how do I properly reference the image in my asset library?
It seems like you need to explicitly set the size too:
sprite.texture = SKTexture(imageNamed: "myImage1")
sprite.size = sprite.texture!.size()
..or you can specify the image in init:
let sprite = SKSpriteNode(imageNamed: "myImage1")

Q: How do I move objects down the y axis as my player moves up?

Creating a game like Doodle Jump where my player is constantly being moved up by bouncing off of obstacles. Ive tried every trick in the book but nothing seems to be working/doing exactly what I want. Can anyone give me some tips?
iOS 9 introduced the Camera Node. Use SKCameraNode, which is a subclass of SKNode, and can be translated and rotated in same way.
So, instead of moving all of your background elements in the opposite direction of your hero/player, you can simply attach your scene's camera node to your hero/player and the rest is taken care of.
PS. You can also do cool stuff like scaling the camera size.
EDIT.
Happy to include an example.
First, make a camera constant in your scene.
import SpriteKit
class myFirstScene: SKScene {
let myCamera: SKCameraNode = SKCameraNode()
...
}
Then in your didMoveToView() function, assign the scene's built-in camera variable to the camera constant we made earlier.
override func didMoveToView( view: SKView ) {
camera = myCamera
...
}
Now, there are a few different ways to "attach" your camera to your hero/player. The first is to attach your camera node to your hero.
hero.addChild( myCamera )
I don't even know if it works that easily because my game uses something different, a simpler version is below.
update(){
camera!.zRotation = hero.zRotation
camera!.position = hero.position
}

Changing parent node in SpriteKit

I'm new to SpriteKit. When I drag and drop "Color Sprite" from object library to make a new node. It gives the correct size (as you can see in the image below). Here the parent is the main scene not the blue one.
But when I change the parent node from the main scene to any other node (like blue one in my case) the new node gets larger however it's width and height remain same (shown below).
EDIT:
I'm running Xcode 7 beta 3.
I prefer you to do everything programmatically in sprite kit. I didn't understand what is going on there actually but if you want to make a shape like this, you should use SKShapeNode. SKSpriteNode is generally for making a node with a texture and animating that stuff etc...
//for example
let node = SKShapeNode(rectOfSize:CGSize(width:100,height:100))
node.position = CGPoint(x:500,y:350)
node.fillColor = UIColor.redColor()
self.addChild(node)
if you still have problems with the size, you can play with;
// for example;
node.xScale = 469
node.yScale = 300