Can not access an SKSpriteNode from within a function - swift

I faced a problem, when accessing an object I added to my scene within my collision detection:
Within my GamScene.swift on the top I declared
var passenger: PassengerNode!
And I trigger a spawnPassenger method, which is looking like this
func spawnPassenger(x: CGFloat, y: CGFloat){
let passenger = SKSpriteNode(imageNamed: "passenger")
passenger.position = CGPoint(x: platformArray[2].position.x, y: platformArray[2].position.y)
passenger.position.x = x
passenger.position.y = y
//passenger.physicsBody!.categoryBitMask = PhysicsCategory.Passenger
passenger.zPosition = 5
passenger.anchorPoint = CGPointZero
//print("passenger spawned")
self.addChild(passenger)
}
The App builds fine, but as soon as the collision is fired and this Action is triggered:
func actionPassengerOnboarding(){
let moveToTaxi = SKAction.moveTo(CGPoint(x: taxiNode.position.x, y: platformNode3.position.y), duration: 2);
let removePassenger = SKAction.removeFromParent()
let setPassengerToOnBoard = SKAction.runBlock({ () -> Void in
self.passengerOnBoard = true
})
let onBoardActionSequence = SKAction.sequence([moveToTaxi, removePassenger, setPassengerToOnBoard])
self.passenger.runAction(onBoardActionSequence, withKey: "isOnboarding")
}
is triggered I get a crash and a fatal error printed out fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb)
Can anyone point me to my error? I just cant figure it out

Your error is the first line of your spawnPassenger function. You're making a new constant with let that has scope to that function. Instead of re-declaring a passenger variable, I think you're intending to set it to the class variable you made before. Remove the let and just say:
passenger = SKSpriteNode(imageNamed: "passenger")
This way, you're only referencing that one passenger variable and not making a new one with local scope.

Related

Unexpectedly found nil when using variable outside of declared function

I am trying to make a kind of maths games where when an enemy is shot the user is prompted with a maths question, I made it so when an enemy is shot a keypad with numbers pops up where the user enters the answer. But whenever the keypad pops up I get the error:
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
At the top of the code I have declared the the variables I am using:
var button1:SKLabelNode?
var answerDisplay:SKLabelNode!
This is the function that runs when the enemy is shot:
func runMathsProblem(){
//This is the label that is meant to display what the user enters
let answerDisplay = SKLabelNode(fontNamed: "Bulky Pixels")
answerDisplay.name = "AnswerDisplay"
answerDisplay.text = "= "
answerDisplay.fontSize = 110
answerDisplay.fontColor = SKColor.white
answerDisplay.horizontalAlignmentMode = SKLabelHorizontalAlignmentMode.left
answerDisplay.zPosition = 110
answerDisplay.position = CGPoint(x: self.size.width * 0.38, y: self.size.height * 0.63)
self.addChild(answerDisplay)
//This is the button the user presses to type "1" on screen
let button1 = SKLabelNode(fontNamed: "Bulky Pixels")
button1.name = "Button1"
button1.text = "1"
button1.fontSize = 110
button1.fontColor = SKColor.white
button1.zPosition = 120
button1.position = CGPoint(x: self.size.width * 0.3, y: self.size.height * 0.32)
self.addChild(button1)
}
This is where it detects if the user has pressed a button and is meant to to add the number pressed to the variable answerDisplay to display on screen(this is also where I get the error):
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches{
let pointOfTouch = touch.location(in: self)
if button1!.contains(pointOfTouch){ **//This is where I get the error**
answerDisplay!.text = answerDisplay.text! + "1"
}
}
}
I think it is something to do with the way I have declared the variable
At the top of the code I have declared the the variables I am using
But you never end up using them, because...
let answerDisplay = SKLabelNode(fontNamed: "Bulky Pixels")
.
.
.
let button1 = SKLabelNode(fontNamed: "Bulky Pixels")
.
.
.
This creates new instances of SKLabelNode instead of using the ones you already created. So, what you need to do is remove the keyword let so that the compiler understands that you are referring to the variables that you already declared.

Entity-Component in Swift

I am trying to build a simple iOS game using entity-component architecture similar to what is described here.
What I would like to achieve in my game is when a user touches the screen, detect where the touch occurred and move all entities of one type towards a specific direction (direction depends on where the user touched, right of screen = up, left of screen = down).
So far, the game is really simple and I am only getting started, but I am stuck in this simple functionality:
My issue is that an SKAction is supposed to run on all entities of a type, but happens at all.
Before I redesigned my game to an ECS approach, this worked fine.
Here is the GKEntity subclass that I declared in Lines.swift:
class Lines: GKEntity {
override init() {
super.init()
let LineSprite = SpriteComponent(color: UIColor.white, size: CGSize(width: 10.0, height: 300))
addComponent(LineSprite)
// Set physics body
if let sprite = component(ofType: SpriteComponent.self)?.node {
sprite.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: sprite.size.width, height: sprite.size.height))
sprite.physicsBody?.isDynamic = false
sprite.physicsBody?.restitution = 1.0
sprite.physicsBody?.friction = 0.0
sprite.physicsBody?.linearDamping = 0.0
sprite.physicsBody?.angularDamping = 0.0
sprite.physicsBody?.mass = 0.00
sprite.physicsBody?.affectedByGravity = false
sprite.physicsBody?.usesPreciseCollisionDetection = true
sprite.physicsBody?.categoryBitMask = 0b1
sprite.zPosition = 10
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
In TouchesBegan I am calling the function Move(XAxisPoint: t.location(in: self)) which is declared in GameScene and here is what Move() does:
///Determines direction of movement based on touch location, calls MoveUpOrDown for movement
func move(XAxisPoint: CGPoint){
let Direction: SKAction
let Key: String
if XAxisPoint.x >= 0 {
Direction = SKAction.moveBy(x: 0, y: 3, duration: 0.01)
Key = "MovingUp"
} else {
Direction = SKAction.moveBy(x: 0, y: -3, duration: 0.01)
Key = "MovingDown"
}
moveUpOrDown(ActionDirection: Direction, ActionKey: Key)
}
///Moves sprite on touch
func moveUpOrDown(ActionDirection: SKAction, ActionKey: String) {
let Line = Lines()
if let sprite = Line.component(ofType: SpriteComponent.self)?.node {
if sprite.action(forKey: ActionKey) == nil {
stopMoving()
let repeatAction = SKAction.repeatForever(ActionDirection)
sprite.run(repeatAction, withKey: ActionKey)
}
}
}
///Stops movement
func stopMoving() {
let Line = Lines()
if let sprite = Line.component(ofType: SpriteComponent.self)?.node {
sprite.removeAllActions()
}
}
I am guessing there is some issue with this line of code Line.component(ofType: SpriteComponent.self)?.node but the compiler doesn't throw any errors and I am not sure where my mistake is.
Any help/guidance will be greatly appreciated!
The issue is the following line in MoveUpOrDown and StopMoving
let Line = Lines()
It's creating a new Lines object then telling it to run an action. Since it's new, it hasn't been added to the scene so it isn't drawn or acted on.
You should be getting an existing Lines object and modifying that instead of creating a new one.
As a side note, the common convention for naming methods and variables is to use camelCase which means MoveUpOrDown should be moveUpOrDown. On the other hand SnakeCase is used For classes structs and protocols so SpriteComponent is current. That allows you to know at a glance whether your working with a type or a variable.

Smart KeyPaths in Swift 4 not working properly

I am trying to create an custom action block for an SKSpriteNode, I have the following code:
let sprite = SKSpriteNode(color: SKColor.red, size: CGSize(width: 50, height: 50))
sprite.position = CGPoint(x: 320, y: 240)
self.addChild(sprite)
let animation = SKAction.customAction(withDuration: 0, actionBlock: {
(node, elapsedTime) in
var initialValue : CGFloat?
initialValue = node[keyPath: \SKSpriteNode.position.x] //Extraneous argument label 'keyPath:' in subscript
node[keyPath: \SKSpriteNode.position.x] = 10 //Ambiguous reference to member 'subscript'
})
sprite.run(animation)
I am getting two errors, the first is that the compiler is thinking I have an Extraneous argument of 'keyPath', which is not the case, because if I were to remove it like it suggests, I get this error:
Cannot convert value of type 'ReferenceWritableKeyPath' to expected argument type 'String'
The second error I get is:
Ambiguous reference to member 'subscript'
I am not exactly sure why I am getting all of these errors, and I am not sure exactly what the errors mean. If somebody could explain them to me and propose a solution, that would be great. Thanks in advance.
The keyPath isn't working because node has type SKNode and not SKSpriteNode. You can use a conditional cast to establish that the node is an SKSpriteNode:
let animation = SKAction.customAction(withDuration: 0, actionBlock: {
(node, elapsedTime) in
var initialValue : CGFloat?
if let spritenode = node as? SKSpriteNode {
initialValue = spritenode[keyPath: \SKSpriteNode.position.x]
spritenode[keyPath: \SKSpriteNode.position.x] = 10
}
})

nil exception when initializing an SKSpriteNode Object

I get a nil exception error during runtime and I can not explain myself why there is a problem.
First I declare a SKSpriteNode Object like this
var destinationSign: SKSpriteNode?
and later I define a function that creates this SpriteNode like this
func showDestination( x: CGFloat, y: CGFloat){
destinationSign = SKSpriteNode(fileNamed: "sign_1")
destinationSign?.position.x = x
destinationSign?.position.y = y
addChild(destinationSign!)
}
after that I have a different function and triggers this showDestination function
showDestination(x: 20 , y: 40)
Does anyone have an explanation for this behaviour?
Thanks in advance
Looks like destinationSign is nil when addChild is called. You should double check the name of the image used to init it, capital letters, underscore, etc…
Try also:
if let node = destinationSign as SKSpriteNode {
self.addChild(node)
}
to prevent the nil exception (but remember the node will not be added)
You should also consider to use a more generalized function, that could be used in other contexts, as:
func show (_ node: SKSpriteNode, at point: CGPoint) {
node = SKSpriteNode(fileNamed: "sign_1")
if let node = node as SKSpriteNode {
node.position.x = point.x
node.position.y = point.y
addChild(node)
}
}
But these solution are merely theoretical, given the fact you actually want an error if your sprite is not added to the scene.

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