Found nil when unwrapping texture value which I have previously set - swift

so I am trying to add an SKSpritenode to my scene level2, and I want to give it a physics body with a texture that comes from an image I have uploaded into the assets folder of my project. the image is there and the name is right, but when launch the app I get a fatal error. I don't get where I'm going wrong as the app used to run smoothly until I added that last line of code.
import UIKit
import SpriteKit
import GameplayKit
class level2: SKScene {
var entities = [GKEntity]()
var graphs = [String : GKGraph]()
var perno = SKSpriteNode(fileNamed: "perno")
override func sceneDidLoad () {
print ("view2 Loaded")
self.perno = self.childNode(withName: "perno") as? SKSpriteNode
self.perno?.texture = SKTexture(imageNamed: "perno")
self.perno?.size.width = self.perno!.size.width * 5
self.perno!.physicsBody = SKPhysicsBody(texture: (perno?.texture)!, size: (perno?.texture?.size())!)
}
any hints?
Thanks!

Your optional handling is really inconsistent. Sometimes you use optional casting and optional chaining just to then use force unwrapping on the same variable in the same line. You should force unwrap the return value of SKSpriteNode(fileNamed: "perno"), since if that returns nil that's a programmer error and should be caught ASAP.
Your immediate issue seems to be that you try to overwrite perno in sceneDidLoad:
self.perno = self.childNode(withName: "perno") as? SKSpriteNode
There's no childNode added to your scene, so you just set perno to nil instead of its previously set value using var perno = SKSpriteNode(fileNamed: "perno"). Simply remove that line from sceneDidLoad, since it makes no sense, perno is already loaded during initialisation, since you assign a value to it.
class Level2: SKScene {
var entities = [GKEntity]()
var graphs = [String : GKGraph]()
let perno = SKSpriteNode(fileNamed: "perno")!
override func sceneDidLoad () {
print ("view2 Loaded")
self.perno.texture = SKTexture(imageNamed: "perno")
self.perno.size.width = self.perno.size.width * 5
self.perno.physicsBody = SKPhysicsBody(texture: perno.texture!, size: perno.texture!.size())
}
}
A better way to protect yourself is to use the guard statement. guard allows you to safely check if a variable is not nil without having to nest your code.
class Level2: SKScene {
var entities = [GKEntity]()
var graphs = [String : GKGraph]()
let perno = SKSpriteNode(fileNamed: "perno")!
override func sceneDidLoad () {
print ("view2 Loaded")
guard let texture = SKTexture(imageNamed: "perno")
else{
fatalError("Unable to find texture "perno")
}
self.perno.texture = texture
self.perno.size.width = self.perno.size.width * 5
self.perno.physicsBody = SKPhysicsBody(texture: texture, size: texture.size())
}
}
As you can see, we are not excessively unwrapping self.perno.texture, which means there will be less chances for us to accidentally use a ? instead of a !, and we have a clear cut error as to why our code is crashing.

Related

Why is init(coder:) being called when I provide an init() function

I am using SpriteKit and I am loading a SceneKit file that contains a number of sprites with custom classes. The scene never actually loads though because it reaches the first custom class and throws the fatalerror from the required init?(coder:) initializer. The custom class implements an initializer though and I am having trouble pinning down why it is choosing that initializer over the one I provided.
Custom Class:
class Bat: SKSpriteNode, GameSprite {
var initialSize: CGSize = CGSize(width: 44, height: 24)
var textureAtlas: SKTextureAtlas = SKTextureAtlas(named: "Enemies")
var flyAnimation = SKAction()
init() {
super.init(texture: nil, color: .clear, size: initialSize)
self.physicsBody = SKPhysicsBody(circleOfRadius: size.width / 2)
self.physicsBody?.affectedByGravity = false
createAnimations()
self.run(flyAnimation)
}
required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }
func createAnimations() {
let flyFrames: [SKTexture] = [textureAtlas.textureNamed("bat"),
textureAtlas.textureNamed("bat-fly")]
let flyAction = SKAction.animate(with: flyFrames, timePerFrame: 0.12)
flyAnimation = SKAction.repeatForever(flyAction)
}
func onTap() {}
}
And here is the code attempting to load the scene and then loop through the children and initialize them:
Encounter Manager:
class EncounterManager {
// Store encounter file names
let encounterNames: [String] = [
"EncounterA"
]
// Each encounter is a node, store an array
var encounters: [SKNode] = []
init() {
// Loop through each encounter scene and create a node for the encounter
for encounterFileName in encounterNames {
let encounterNode = SKNode()
// Load the scene file into a SKScene instance and loop through the children
if let encounterScene = SKScene(fileNamed: encounterFileName) {
for child in encounterScene.children {
// Create a copy of the scene's child node to add to our encounter node
// Copy the position, name, and then add to the encounter
let copyOfNode = type(of: child).init()
copyOfNode.position = child.position
copyOfNode.name = child.name
encounterNode.addChild(copyOfNode)
}
}
// Add the populated encounter node to the array
encounters.append(encounterNode)
}
}
// This function will be called from the GameScene to add all the encounter nodes to the world node
func addEncountersToScene(gameScene: SKNode) {
var encounterPosY = 1000
for encounterNode in encounters {
// Spawn the encounters behind the action, with increasing height so they do not collide
encounterNode.position = CGPoint(x: -2000, y: encounterPosY)
gameScene.addChild(encounterNode)
// Double Y pos for next encounter
encounterPosY *= 2
}
}
}
What I have noticed using breakpoints though is that it never gets past loading the scene. It fails on the line if let encounterScene = SKScene(fileNamed: encounterFileName) and the error is the fatal error in the initializer from the Bat class.
Any help understanding why it picks one initializer over the other would be greatly appreciated!
You are doing:
if let encounterScene = SKScene(fileNamed: encounterFileName)
Which calls SKScene's init(fileNamed:) which loads a file and decodes it with SKScene's coder init. That init loads the file and decodes each element in it with the node's coder init.
If you want to load from a file, you need to implement the coder init.

Get child node of SKReferenceNode in SpriteKit SWIFT

I create a scene Case.sks (using Level Editor), inside one SKSpriteNode (name : square), and one SKLabel (name : label).
In my main scene, GameScene.sks, I use a SKReferenceNode with "Case" for reference.
I need to access to the "square" sprite from my main scene.
My first idea was to call directly the child node:
let firstSquare = childNode(withName: "square") as! SKSpriteNode
But I got :
Fatal error: unexpectedly found nil while unwrapping an Optional value
So I tried :
let caseRef = childNode(withName: "Case") as! SKReferenceNode
let firstSquare = caseRef.childNode(withName: "square") as! SKSpriteNode
But I got on the firstSquare line :
Fatal error: unexpectedly found nil while unwrapping an Optional value
How can get a child node of a reference scene ?
Try to call it with this code:
override func sceneDidLoad() {
if let sprite = self.childNode(withName: "//square") as? SKSpriteNode {
// do whatever you want with the sprite
}
...
}

Repeating an action forever with a global function

I have a rectangle that needs to be constantly moving up, but is also declared globally like so so that I can call it in multiple places:
var obstacle = SKNode!
override func didMoveToView {
obstacle = rectangle()
}
func rectangle() -> SKNode {
let rect = SKSpriteNode(imageNamed: "Rectangle#x2")
rect.size = CGSizeMake(30, 30)
rect.position = CGPointMake(210, -250)
rect.physicsBody?.categoryBitMask = PhysicsCatagory.littleRect
rect.physicsBody?.contactTestBitMask = PhysicsCatagory.bigRect
rect.physicsBody?.collisionBitMask = 0
rect.physicsBody = SKPhysicsBody(rectangleOfSize: rect.size)
rect.physicsBody?.dynamic = true
rect.physicsBody?.affectedByGravity = false
rect.runAction(
SKAction.moveByX(0, y: 1200,
duration: NSTimeInterval(6.5)))
addChild(rect)
return rect
}
When I attempt to run it as an action repeating forever like so, i get the error "cannot convert value of type SKNode to argument runBlock" :
runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.runBlock(rectangle),
SKAction.waitForDuration(4.0)])))
So is there a way to declare this sort of action for a function set up like this? Thank you in advance.
First of all, this var obstacle = SKNode! will produce an error. You should declare an implicitly unwrapped optional like this:
var obstacle:SKNode!
About the main question (without analyzing the logic of what code actually does,)...You are passing an instance of SKNode class to +runBlock: method (which accepts a closure), thus the error. To fix this, you have to pass a closure, like this:
override func didMoveToView(view: SKView) {
runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.runBlock({[unowned self] in self.rectangle()}),
SKAction.waitForDuration(4.0)])))
}

How to randomly choose a SKSpriteNode?

I want to choose from 4 enemies using random and present it on scene. For that purpose I've made this:
func enemyPicker() -> SKSpriteNode {
var enemyArray = [mouse, robot, drone, block, bird]
var countArray = UInt32(enemyArray.count)
var pickOneEneny = arc4random_uniform(countArray)
var randomElement = Int(pickOneEnemy)
return enemyArray.randomElement
}
But Xcode says to me that SKSpriteNode does not have a member named randomElement. And it surely doesn't, but how would I say to my function that I need it to pick and assign that random Int to an actual enemy from array?
I tried to use this answer but it's not working for me. I also tried to change -> SKSpriteNode to SKTexture, String and "T" and had not any luck with it.
My SpriteNodes are declared like:
var mouse = SKSpriteNode()
let mouseAtlas = SKTextureAtlas(named: "mouse")
var mouseArray = [SKTexture]()
mouseArray.append(mouseAtlas.textureNamed("mouse_0"));
mouseArray.append(mouseAtlas.textureNamed("mouse_1"));
mouseArray.append(mouseAtlas.textureNamed("mouse_2"));
mouse = SKSpriteNode(texture: mouseArray[0]);
self.mouse.position = CGPointMake(CGRectGetMaxX(self.frame), CGRectGetMidY(self.frame) - 138)
self.mouse.size = CGSizeMake(self.mouse.size.width, self.mouse.size.height + mouse.size.height / 2)
self.mouse.name = "mouse"
self.addChild(mouse)
func enemyPicker() -> SKSpriteNode {
let enemyArray = [mouse, robot, drone, block, bird]
return enemyArray[Int(arc4random_uniform(UInt32(enemyArray.count)))]
}

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