Unexpectedly found nil when using variable outside of declared function - swift

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.

Related

Duplicate image with touch

Im trying to duplicate an image in Swift 4 when the user touches the screen
var positionArray = Array(repeating: Array(repeating: 0, count: 2), count: 50)
var counter = 0
var pointNum = 0
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let position = touch.location(in: self.view)
let locx = Int(position.x)
let locy = Int(position.y)
positionArray[counter] = [locx, locy]
print(positionArray[counter])
counter = counter + 1
point.center = position
pointNum = pointNum + 1
}
}
As you can see this is what I use to register touch, but I only have a single image (point) that moves to where the user touches.
Your code saves your points to an array of points, and then moves the center of something called point. Is point an image view?
If you want to create a new copy of an image everywhere the user taps then you need to create a new copy of the image. Something like this:
let bounds = point.bounds
let newImageView = UIImageView(frame: bounds)
newImageView.translatesAutoresizingMaskIntoConstraints = true
newImageView.image = UIImage(named: "MyImageViewName")
self.view.addSubView(newImageView)
newImageView.center = position
Note that it would really be better to add auto layout anchors that anchor the new image view's center to the superview's origin, but the above code should work. (I think. I've rarely used newImageView.translatesAutoresizingMaskIntoConstraints = true.)

SKScene returns nil when transition method is called

In my little Xcode Project I am trying to transition scenes by when my SKLabelNode is touched it presents the next scene. For some reason in my scene transition method it makes me store it as an optional. And the optional returns false. My file names are correct and no grammatical errors in the references would be causing the problem here is my code. When I click my SKLabelNode my ios platform that is running it on recognizes the touch then the app crashes, in console saying that an optional value returned nil. How could I resolve this problem? Thanks
import SpriteKit
class GameScene: SKScene {
let next = SKScene(fileNamed: "nextScene")
override func didMoveToView(view: SKView) {
let backgroundimage = SKSpriteNode(imageNamed: "ipbg")
backgroundimage.size = CGSize(width: self.frame.size.width, height: self.frame.size.height)
backgroundimage.position = CGPoint(x: self.frame.size.width / 2, y: self.frame.size.height / 2)
addChild(backgroundimage)
let playButton = SKLabelNode(fontNamed: "")
playButton.name = "play"
playButton.position = CGPoint(x: self.frame.size.width / 2, y: self.frame.size.height / 2 + 100)
playButton.text = "Play"
let wait = SKAction.waitForDuration(2)
let run = SKAction.runBlock({
let randomNumber = Int(arc4random_uniform(UInt32(4)))
switch(randomNumber){
case (0):
playButton.fontColor = UIColor.blueColor()
case 1:
playButton.fontColor = UIColor.yellowColor()
case 2:
playButton.fontColor = UIColor.purpleColor()
case 3:
playButton.fontColor = UIColor.orangeColor()
default: print("default")
}
})
addChild(playButton)
var repeatActionForever = SKAction.repeatActionForever(SKAction.sequence([wait, run]))
runAction(repeatActionForever)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let trans = SKTransition.crossFadeWithDuration(1)
let touch = touches.first! as UITouch
let touchLocation = touch.locationInNode(self)
let touchedNode = nodeAtPoint(touchLocation)
if touchedNode.name == "play"{
scene!.view?.presentScene(next!, transition: trans)
}
}
}
func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
The code you posted does not compile so it's hard to find the error.
However I would try replacing the touchesBegan method with the following
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let trans = SKTransition.crossFadeWithDuration(1)
let touch = touches.first! as UITouch
let touchLocation = touch.locationInNode(self)
let touchedNode = nodeAtPoint(touchLocation)
if touchedNode.name == "play"{
guard let nextScene = SKScene(fileNamed: "nextScene")
else { fatalError("Could not load nextScene") }
self.view?.presentScene(nextScene, transition: trans)
}
}
There is no need to store the scene as a property just add it in the touches began method.
Also you should create the corresponding swift file and class for each scene instead of trying to initialising a .sks file with a generic SKScene object. The line ...(fileNamed: "nextScene") looks for an .sks file (Xcode visual level editor file), if you haven't created one your property will be nil.
To make it work do this:
First make sure you created a .sks file called NextScene (right click a file in your project -> New -> Resource -> SpriteKit Scene)
Secondly create a new swift file and create a new class called NextScene
class NextScene: SKScene {
}
Than the loading code in your touches label function would look like this.
override func touchesBegan... {
...
if let nextScene = NextScene(fileNamed: "NextScene") //actually init the corresponding class with the corresponding .sks file
/// scene presenting code
}
}
As you mentioned your scene property returns an optional because the fileNamed (.sks file) you specified might not exists, so check it is there. In general its better practice to safely unwrap like I do above or the default loading code in GameViewController does, instead of just force unwrapping with a ! .
If you don't use the visual level editor than just create your scene class and ignore creating the .sks file and load your scene like so
let nextScene = NextScene(size: self.size) // use current scene size
/// scene presenting code
Note: Your file names should start with capital letters

how to appoint a UItapGesture recognizer to a CGpoint for a button

I am creating a game ad i am having a hard time creating a jump button. I have created the jump up and fall down SKaction sequence which works perfect here is how it works.
func JumpArrow () {
self.addChild(jumpArrow)
jumpArrow.position = CGPointMake(60, 145)
jumpArrow.xScale = 1
jumpArrow.yScale = 1
}
func heroJumpMovement () {
let heroJumpAction = SKAction.moveToY(hero.position.y + 85,
duration: 0.5)
let heroFallAction = SKAction.moveToY(hero.position.y , duration:
0.5)
let jumpWait:SKAction = SKAction.waitForDuration(CFTimeInterval(1))
let heroMovementSequence:SKAction =
SKAction.sequence([heroJumpAction, heroFallAction ,jumpWait])
hero.runAction(heroMovementSequence)
}
override func touchesBegan(touches: Set<UITouch>, withEvent
event:UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
let node = nodeAtPoint(location)
if node == jumpArrow {
heroJumpMovement()
}
however, I have a problem. if you quickly tap the button the player will fly off the screen. I hope that i can create a UItapGestureRecognizer and create a delay for the tap so you can't tap the button 2-4 times per second you will only be able to tap it once. If this is the wrong way to go about this please tell me
Adding a delay would be the wrong way to go about it.
Instead, in your touchesBegan function, before you call heroJumpMovement() you should check to see if your player is on the ground or not.
Another alternative would be to check if the last jump SKActionSequence has completed or not.
To do the above, you would have something like this (code not checked):
var canJump = true; // Variable will be true if we can jump
func JumpArrow () {
self.addChild(jumpArrow)
jumpArrow.position = CGPointMake(60, 145)
jumpArrow.xScale = 1
jumpArrow.yScale = 1
}
func heroJumpMovement () {
let heroJumpAction = SKAction.moveToY(hero.position.y + 85,
duration: 0.5)
let heroFallAction = SKAction.moveToY(hero.position.y , duration:
0.5)
let jumpWait:SKAction = SKAction.waitForDuration(CFTimeInterval(1))
let heroMovementSequence:SKAction =
SKAction.sequence([heroJumpAction, heroFallAction ,jumpWait])
canJump = false; // We are about to jump so set this to false
hero.runAction(heroMovementSequence, completion: {canJump = true;}) // Set the canJump variable back to true after we have landed
}
override func touchesBegan(touches: Set<UITouch>, withEvent
event:UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
let node = nodeAtPoint(location)
if node == jumpArrow {
if (canJump) { // Make sure we are allowed to jump
heroJumpMovement()
}
}
Notice the canJump variable.

Can not access an SKSpriteNode from within a function

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.

swift - Jump only when landed

I'm looking to restrict my character (cat), to only jump when it's either on the ground (dummy SKNode), or when on the tree (treeP SKNode).
Currently I don't have any restrictions to touchesBegan and as a result the cat is able to fly through the air if the user clicks in quick succession, whilst this could be useful in other games it's not welcome here.
If anyone could help me I'd be really happy.
What i would like to do but have no experience would be to enable a click (jump), if the cat was in contact with either dummy or tree and likewise disable clicks if not in contact with either dummy or tree.
Here is everything that may be of use....
class GameScene: SKScene, SKPhysicsContactDelegate {
let catCategory: UInt32 = 1 << 0
let treeCategory: UInt32 = 1 << 1
let worldCategory: UInt32 = 1 << 1
override func didMoveToView(view: SKView) {
// part of cat code
cat = SKSpriteNode(texture: catTexture1)
cat.position = CGPoint(x: self.frame.size.width / 2.2, y: self.frame.size.height / 7.0 )
cat.physicsBody?.categoryBitMask = catCategory
cat.physicsBody?.collisionBitMask = crowCategory | worldCategory
cat.physicsBody?.contactTestBitMask = crowCategory | contact2Category
// part of the ground code
var dummy = SKNode()
dummy.position = CGPointMake(0, groundTexture.size().height / 2)
dummy.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(self.frame.size.width, groundTexture.size().height))
dummy.physicsBody!.dynamic = false
dummy.physicsBody?.categoryBitMask = worldCategory
dummy.physicsBody?.collisionBitMask = 0
dummy.physicsBody?.contactTestBitMask = 0
moving.addChild(dummy)
// part of the tree code
func spawnTrees() {
var treeP = SKNode()
treeP.position = CGPointMake( self.frame.size.width + treeTexture1.size().width * 2, 0 );
treeP.zPosition = -10;
var height = UInt32( self.frame.size.height / 4 )
var y = arc4random() % height;
var tree1 = SKSpriteNode(texture: treeTexture1)
tree1.position = CGPointMake(0.0, CGFloat(y))
tree1.physicsBody = SKPhysicsBody(rectangleOfSize: tree1.size)
tree1.physicsBody?.dynamic = false
tree1.physicsBody?.categoryBitMask = treeCategory;
tree1.physicsBody?.collisionBitMask = 0
tree1.physicsBody?.contactTestBitMask = 0
treeP.addChild(tree1)
treeP.runAction(moveAndRemoveTrees)
trees.addChild(treeP)
}
// all of touchesBegan
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
/* Called when a touch begins */
if (moving.speed > 0){
cat.physicsBody!.velocity = CGVectorMake(0, 0)
cat.physicsBody!.applyImpulse(CGVectorMake(0, 20))
} else if (canRestart) {
self.resetScene()
}
}
You can use some custom boolean variable called isOnTheGround in order to restrict jumping while in the air. This variable need to be updated by you.Note that character is not necessarily on the ground just if it is in contact with "platform" (eg. side contact can occur). So, it's up to you to define what means "on the ground". When you define that, then it's easy:
Hint: You can compare character's y position with platform's y position to see is character is really on the platform.
Assuming that both character's and platform's anchor point are unchanged (0.5,0.5), to calculate character's legs y position you can do something like this (pseudo code):
characterLegsY = characterYPos - characterHeight/2
And to to get platform's surace:
platformSurfaceYPos = platformYPos + platformHeight/2
Preventing jumping while in the air
In touchesBegan:
if isOnTheGround -> jump
In didEndContact:
here you set isOnTheGround to false (character ended contact with ground/platform)
In didBeginContact:
check if character is really on the ground
set isOnTheGround variable to true
Here you can find an example of something similar...
I recently had this problem. This is how I fixed it. In the game scene create this variable:
var ableToJump = true
Then in the update method put this code:
if cat.physicsBody?.velocity.dy == 0 {
ableToJump = true
}
else {
ableToJump = false
}
The above code will work because the update method is ran every frame of the game. Every frame it checks if your cat is moving on the y axis. If you do not have an update method, just type override func update and it should autocomplete.
Now the last step is put this code in the touchesBegan:
if ableToJump == true {
cat.physicsBody!.applyImpulse(CGVectorMake(0,40))
}
Note: You may have to tinker with the impulse to get desired jump height