How do I save the user's level on my game? - swift

I have a maze game that I coded with swift that does not save levels. Currently, when the user closes the app, nothing is saved and the user has to start playing the game from level 1 again. I have a different .swift and .sks file for every level, so I am not sure how to save level data across classes/files.
I have tried to use UserDefaults, however, I am not sure how to save the "file path" to my level. I know that I need to implement something in my AppDelegate (using the applicationWillTerminate or applicationDidEnterBackground func), however, I am not sure what code (if any) I need to put in the scenes themselves to save this data. I have not worked with saving user data before, so help would be greatly appreciated!
import SpriteKit
import GameplayKit
import CoreMotion
import SceneKit
import UIKit
class GameScene: SKScene, SKPhysicsContactDelegate {
let gameScene = SKScene()
var button: SKNode! = nil
var playerSprite = SKSpriteNode()
var nextNode = SKSpriteNode()
lazy var countdownLabel: SKLabelNode = {
var label = SKLabelNode(fontNamed: "Helvetica")
label.horizontalAlignmentMode = .center
label.verticalAlignmentMode = .center
label.color = UIColor.red
label.text = "\(counter)"
return label
}()
var counter = 30
var counterTimer = Timer()
var counterStartValue = 30
let motionManager = CMMotionManager()
override func didMove(to view: SKView) {
self.scene?.scaleMode = SKSceneScaleMode.aspectFit
button = SKSpriteNode(imageNamed: "redoButton")
button.position = CGPoint(x: 350, y: 585);
self.addChild(button)
countdownLabel.position = CGPoint(x: 0.5, y: 0.5)
addChild(countdownLabel)
counter = counterStartValue
runTimer()
self.physicsWorld.contactDelegate = self
playerSprite = self.childNode(withName: "playerSprite") as! SKSpriteNode
nextNode = self.childNode(withName: "nextNode") as! SKSpriteNode
motionManager.startAccelerometerUpdates()
motionManager.accelerometerUpdateInterval = 0.1
motionManager.startAccelerometerUpdates(to: OperationQueue.main) {
(data,error) in
self.physicsWorld.gravity = CGVector(dx: CGFloat((data?.acceleration.x)!) * 10, dy: CGFloat((data?.acceleration.y)!) * 10)
}
}
func didBegin(_ contact: SKPhysicsContact) {
let bodyA = contact.bodyA
let bodyB = contact.bodyB
if bodyA.categoryBitMask == 1 && bodyB.categoryBitMask == 2 || bodyA.categoryBitMask == 2 && bodyB.categoryBitMask == 1{
playerSprite.removeFromParent()
nextNode.removeFromParent()
self.removeFromParent()
let transition = SKTransition.fade(withDuration: 0.5)
let gameScene2 = GameScene2(fileNamed: "GameScene2")
gameScene2!.scaleMode = SKSceneScaleMode.aspectFit
self.view?.presentScene(gameScene2!, transition: transition)
}
}
func runTimer() {
counterTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(decrementCounter), userInfo: nil, repeats: true)
}
#objc func decrementCounter() {
counter -= 1
countdownLabel.text = "\(counter)"
if countdownLabel.text! < "0" as String {
countdownLabel.text = "Game Over"
playerSprite.removeFromParent()
nextNode.removeFromParent()
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
if touch.view == self.button {
print("Yay!!!")
} else {
playerSprite.isHidden = false
nextNode.isHidden = false
let gameScene = GameScene(fileNamed: "GameScene")
gameScene!.scaleMode = SKSceneScaleMode.aspectFit
let transition = SKTransition.fade(withDuration: 0.5)
self.view?.presentScene(gameScene!, transition: transition)
}
}
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
}

save it with a string
UserDefaults.standard.set(StateOftheGame, forKey: "levelPass")
then on app delegate check if the user 'passLevel' when the game starts
in
didFinishLaunchingWithOptions -> add function passLevel()
func passLevel() {
if UserDefaults.standard.value(forKey: "levelPass") != nil
{
let VC = UIStoryboard(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier: "YOURVIEWCONTROLLERWITHSAVESTATE")
let navVC = UINavigationController(rootViewController: VC)
let share = UIApplication.shared.delegate as? AppDelegate
share?.window?.rootViewController = navVC
share?.window?.makeKeyAndVisible()
}
}

Related

How to fix a Fatal error: Unexpectedly found nil while unwrapping an Optional value? [duplicate]

This question already has answers here:
What does "Fatal error: Unexpectedly found nil while unwrapping an Optional value" mean?
(16 answers)
Closed 1 year ago.
I’m creating a 3D ball that when the user clicks on it should move. However, at my line that says ballNode = scene.rootNode.childNode(withName: "Ball", recursively: true)!. I get an error saying Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value. The issue is when I create the node for the ball. I have never gotten this error before in swift.
Here is my code
import UIKit
import SceneKit
class GameViewController: UIViewController {
let CategoryLamp = 2
var sceneView:SCNView!
var scene:SCNScene!
var ballNode:SCNNode!
var selfieStickNode:SCNNode!
var motion = MotionHelper()
var motionForce = SCNVector3(0, 0, 0)
var sounds:[String:SCNAudioSource] = [:]
override func viewDidLoad() {
setupScene()
setupNodes()
}
func setupScene(){
sceneView = self.view as! SCNView
sceneView.delegate = self
//sceneView.allowsCameraControl = true
scene = SCNScene(named: "art.scnassets/MainScene.scn")
sceneView.scene = scene
scene.physicsWorld.contactDelegate = self
let tapRecognizer = UITapGestureRecognizer()
tapRecognizer.numberOfTapsRequired = 1
tapRecognizer.numberOfTouchesRequired = 1
tapRecognizer.addTarget(self, action: #selector(GameViewController.sceneViewTapped(recognizer:)))
sceneView.addGestureRecognizer(tapRecognizer)
}
func setupNodes() {
ballNode = scene.rootNode.childNode(withName: "Ball", recursively: true)! // Where the error is occurring
ballNode.physicsBody?.contactTestBitMask = CategoryLamp
selfieStickNode = scene.rootNode.childNode(withName: "selfieNode", recursively: true)!
}
#objc func sceneViewTapped (recognizer:UITapGestureRecognizer) {
let location = recognizer.location(in: sceneView)
let hitResults = sceneView.hitTest(location, options: nil)
if hitResults.count > 0 {
let result = hitResults.first
if let node = result?.node {
if node.name == "Ball" {
ballNode.physicsBody?.applyForce(SCNVector3(x: 0, y:4, z: -2), asImpulse: true)
}
}
}
}
override var shouldAutorotate: Bool {
return false
}
override var prefersStatusBarHidden: Bool {
return true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
extension GameViewController : SCNSceneRendererDelegate {
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
let ball = ballNode.presentation
let ballPosition = ball.position
let targetPosition = SCNVector3(x: ballPosition.x, y: ballPosition.y + 5, z:ballPosition.z + 5)
var cameraPosition = selfieStickNode.position
let camDamping:Float = 0.3
let xComponent = cameraPosition.x * (1 - camDamping) + targetPosition.x * camDamping
let yComponent = cameraPosition.y * (1 - camDamping) + targetPosition.y * camDamping
let zComponent = cameraPosition.z * (1 - camDamping) + targetPosition.z * camDamping
cameraPosition = SCNVector3(x: xComponent, y: yComponent, z: zComponent)
selfieStickNode.position = cameraPosition
motion.getAccelerometerData { (x, y, z) in
self.motionForce = SCNVector3(x: x * 0.05, y:0, z: (y + 0.8) * -0.05)
}
ballNode.physicsBody?.velocity += motionForce
}
}
extension GameViewController : SCNPhysicsContactDelegate {
func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact) {
var contactNode:SCNNode!
if contact.nodeA.name == "Ball" {
contactNode = contact.nodeB
}else{
contactNode = contact.nodeA
}
if contactNode.physicsBody?.categoryBitMask == CategoryLamp {
contactNode.isHidden = true
let waitAction = SCNAction.wait(duration: 15)
let unhideAction = SCNAction.run { (node) in
node.isHidden = false
}
let actionSequence = SCNAction.sequence([waitAction, unhideAction])
contactNode.runAction(actionSequence)
}
}
}
scene.rootNode.childNode(withName: "Ball", recursively: true) returning a nil value while running. You should debug and identify why you are getting this nil value.
You can provide a default value if it founds a nil value as-
func setupNodes() {
ballNode = scene.rootNode.childNode(withName: "Ball", recursively: true) ?? "provide a default value"
ballNode.physicsBody?.contactTestBitMask = CategoryLamp
selfieStickNode = scene.rootNode.childNode(withName: "selfieNode", recursively: true)!
}
OR
You can provide a guard statement as
func setupNodes() {
guard let tmpballNode = scene.rootNode.childNode(withName: "Ball", recursively: true) else {return}
ballNode = tmpballNode
ballNode.physicsBody?.contactTestBitMask = CategoryLamp
selfieStickNode = scene.rootNode.childNode(withName: "selfieNode", recursively: true)!
}

Contact Delegate missing

I am trying to make a new level for my app. I am honestly just copy and pasting my code from the view controllers and gamescenes to other files that I changed the name of. Yes, I did change the names INSIDE the files too so It'll match the new level. However, my contact delegate won't work on my new level now. How can I fix this?
Cannot assign value of type 'Level1' to type 'SKPhysicsContactDelegate?'
What am I doing wrong? I just checked my main file that I had before creating a new level and it now has errors. I have't changed any of the coding.
import SpriteKit
import GameplayKit
class Level1: SKScene {
var ball = SKSpriteNode()
var danger1 = SKSpriteNode()
var danger2 = SKSpriteNode()
var goal = SKSpriteNode()
over
ride func didMove(to view: SKView) {
physicsWorld.contactDelegate = self
ball = self.childNode(withName: "ball") as! SKSpriteNode
danger1 = self.childNode(withName: "danger1") as! SKSpriteNode
danger2 = self.childNode(withName: "danger2") as! SKSpriteNode
goal = self.childNode(withName: "goal") as! SKSpriteNode
let border = SKPhysicsBody(edgeLoopFrom: self.frame)
border.friction = 0
border.restitution = 0
danger1.physicsBody = SKPhysicsBody(rectangleOf: danger1.size)
danger1.physicsBody?.categoryBitMask = PhysicsCategories.dangerCategory
danger1.physicsBody?.isDynamic = false
danger2.physicsBody = SKPhysicsBody(rectangleOf: danger2.size)
danger2.physicsBody?.categoryBitMask = PhysicsCategories.dangerCategory
danger2.physicsBody?.isDynamic = false
ball.physicsBody = SKPhysicsBody(circleOfRadius: ball.size.width/2)
ball.physicsBody?.categoryBitMask = PhysicsCategories.ballCategory
ball.physicsBody?.contactTestBitMask = PhysicsCategories.dangerCategory | PhysicsCategories.goalCategory
ball.physicsBody?.collisionBitMask = PhysicsCategories.none
ball.physicsBody?.isDynamic = true
ball.physicsBody!.affectedByGravity = false
goal.physicsBody = SKPhysicsBody(rectangleOf: goal.size)
goal.physicsBody?.categoryBitMask = PhysicsCategories.goalCategory
goal.physicsBody?.isDynamic = false
setupPhysics()
startGame()
}
func setupPhysics() {
physicsWorld.gravity = CGVector(dx: 0.0, dy: 0.0)
physicsWorld.contactDelegate = self
}
func startGame() {
ball.position = CGPoint(x: 0, y: 550)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
ball.position.x = location.x
ball.position.y = location.y
}
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
}
extension GameScene: SKPhysicsContactDelegate {
func didBegin(_ contact: SKPhysicsContact) {
let contactMask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
if contactMask == PhysicsCategories.ballCategory | PhysicsCategories.dangerCategory {
print("Contact")
} else if contactMask == PhysicsCategories.ballCategory | PhysicsCategories.goalCategory {
print("goal contact")
}
}
}
Instead of creating extension of GameScene, create extension of Level1
extension Level1: SKPhysicsContactDelegate

Create a Login Page for SKSprite Game

I am in the process of creating a game (Swift) in xcode using a number of SKScene and Sprite objects. I want to create a Scene (settings scene) that captures the player's name, email, gender etc. How can I go about this? How can I capture input from user. SKScenes do not allow input fields/values in the UI?
Thanks
You can build a custom login page that is conform with your game layout without try to rebuild in UIKit the same graphic assets.
Few days ago I've written an answer about SKSceneDelegate to communicate between the scene(SpriteKit) and the viewController (UIKit), take present this answer if you want to call other viewControllers because its the same concept of this answer..
Starting with this GameViewController we can develop some useful methods to handle the login form buttons and show some alerts:
import UIKit
import SpriteKit
class GameViewController: UIViewController, TransitionDelegate {
override func viewDidLoad() {
super.viewDidLoad()
guard let view = self.view as! SKView? else { return }
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
let scene = GameScene(size:view.bounds.size)
scene.scaleMode = .fill
scene.delegate = self as TransitionDelegate
scene.anchorPoint = CGPoint.zero
view.presentScene(scene)
}
func showAlert(title:String,message:String) {
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Ok", style: .default) { action in
print("handle Ok action...")
})
alertController.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.cancel, handler: nil))
self.present(alertController, animated: true)
}
func handleLoginBtn(username:String,password:String) {
print("handleLoginBtn")
print("username is: \(username) and password: \(password)")
}
func handleFacebookBtn() {
print("handleFacebookBtn")
}
func handleTwitterBtn() {
print("handleTwitterBtn")
}
}
Then we can make our scene trying to take the advantage of SpriteKit elements:
import SpriteKit
import UIKit
protocol TransitionDelegate: SKSceneDelegate {
func showAlert(title:String,message:String)
func handleLoginBtn(username:String,password:String)
func handleFacebookBtn()
func handleTwitterBtn()
}
class GameScene: SKScene,UITextFieldDelegate {
var usernameTextField:UITextField!
var passwordTextField:UITextField!
var loginBtn:SKShapeNode!
var facebookBtn:SKShapeNode!
var twitterBtn:SKShapeNode!
override func didMove(to view: SKView) {
//bg
let bg = SKSpriteNode(imageNamed: "appleWallpaper")
addChild(bg)
bg.position = CGPoint(x:self.size.width/2,y:self.size.height/2)
//title
let title = SKLabelNode.init(fontNamed: "AppleSDGothicNeo-Bold")
title.text = "xyzGame"; title.fontSize = 25
title.fontColor = .orange
addChild(title)
title.zPosition = 1
title.position = CGPoint(x:self.size.width/2,y:self.size.height-80)
//textfields
guard let view = self.view else { return }
let originX = (view.frame.size.width - view.frame.size.width/1.5)/2
usernameTextField = UITextField(frame: CGRect.init(x: originX, y: view.frame.size.height/4.5, width: view.frame.size.width/1.5, height: 30))
customize(textField: usernameTextField, placeholder: "Enter your username")
view.addSubview(usernameTextField)
usernameTextField.addTarget(self, action:#selector(GameScene.textFieldDidChange(textField:)), for: UIControlEvents.editingChanged)
passwordTextField = UITextField(frame: CGRect.init(x: originX, y: view.frame.size.height/4.5+60, width: view.frame.size.width/1.5, height: 30))
customize(textField: passwordTextField, placeholder: "Enter your password", isSecureTextEntry:true)
view.addSubview(passwordTextField)
//buttons
let myBlue = SKColor(colorLiteralRed: 59/255, green: 89/255, blue: 153/255, alpha: 1)
loginBtn = getButton(frame: CGRect(x:self.size.width/4,y:self.size.height/2,width:self.size.width/2,height:30),fillColor:myBlue,title:"Login",logo:nil,name:"loginBtn")
addChild(loginBtn)
loginBtn.zPosition = 1
let label = SKLabelNode.init(fontNamed: "AppleSDGothicNeo-Regular")
label.text = "or connect with"; label.fontSize = 15
label.fontColor = .gray
addChild(label)
label.zPosition = 1
label.position = CGPoint(x:self.size.width/2,y:self.size.height/2-30)
let logoFb = SKSpriteNode.init(imageNamed: "facebook-icon")
logoFb.setScale(0.5)
facebookBtn = getButton(frame: CGRect(x:self.size.width/4,y:self.size.height/2-80,width:self.size.width/4.5,height:30),fillColor:myBlue,logo:logoFb,name:"facebookBtn")
addChild(facebookBtn)
facebookBtn.zPosition = 1
let myCyan = SKColor(colorLiteralRed: 85/255, green: 172/255, blue: 239/255, alpha: 1)
let logoTw = SKSpriteNode.init(imageNamed: "twitter-icon")
logoTw.setScale(0.5)
twitterBtn = getButton(frame: CGRect(x:self.size.width/2,y:self.size.height/2-80,width:self.size.width/4.5,height:30),fillColor:myCyan,logo:logoTw,name:"twitterBtn")
addChild(twitterBtn)
twitterBtn.zPosition = 1
}
func customize(textField:UITextField, placeholder:String , isSecureTextEntry:Bool = false) {
let paddingView = UIView(frame:CGRect(x:0,y: 0,width: 10,height: 30))
textField.leftView = paddingView
textField.keyboardType = UIKeyboardType.emailAddress
textField.leftViewMode = UITextFieldViewMode.always
textField.attributedPlaceholder = NSAttributedString(string: placeholder,attributes: [NSForegroundColorAttributeName: UIColor.gray])
textField.autocapitalizationType = .none
textField.autocorrectionType = .no
textField.layer.borderColor = UIColor.gray.cgColor
textField.layer.borderWidth = 0.5
textField.layer.cornerRadius = 4.0
textField.textColor = .white
textField.isSecureTextEntry = isSecureTextEntry
textField.delegate = self
}
func getButton(frame:CGRect,fillColor:SKColor,title:String = "",logo:SKSpriteNode!,name:String)->SKShapeNode {
let btn = SKShapeNode(rect: frame, cornerRadius: 10)
btn.fillColor = fillColor
btn.strokeColor = fillColor
if let l = logo {
btn.addChild(l)
l.zPosition = 2
l.position = CGPoint(x:frame.origin.x+(frame.size.width/2),y:frame.origin.y+(frame.size.height/2))
l.name = name
}
if !title.isEmpty {
let label = SKLabelNode.init(fontNamed: "AppleSDGothicNeo-Regular")
label.text = title; label.fontSize = 15
label.fontColor = .white
btn.addChild(label)
label.zPosition = 3
label.position = CGPoint(x:frame.origin.x+(frame.size.width/2),y:frame.origin.y+(frame.size.height/4))
label.name = name
}
btn.name = name
return btn
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let positionInScene = touch!.location(in: self)
let touchedNode = self.atPoint(positionInScene)
if let name = touchedNode.name {
switch name {
case "loginBtn":
self.run(SKAction.wait(forDuration: 0.1),completion:{[unowned self] in
guard let delegate = self.delegate else { return }
(delegate as! TransitionDelegate).handleLoginBtn(username:self.usernameTextField.text!,password: self.passwordTextField.text!)
})
case "facebookBtn":
self.run(SKAction.wait(forDuration: 0.1),completion:{[unowned self] in
guard let delegate = self.delegate else { return }
(delegate as! TransitionDelegate).handleFacebookBtn()
})
case "twitterBtn":
self.run(SKAction.wait(forDuration: 0.1),completion:{[unowned self] in
guard let delegate = self.delegate else { return }
(delegate as! TransitionDelegate).handleTwitterBtn()
})
default:break
}
}
}
func textFieldDidChange(textField: UITextField) {
//print("everytime you type something this is fired..")
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
return true
}
func textFieldDidEndEditing(_ textField: UITextField) {
if textField == usernameTextField { // validate email syntax
let emailRegEx = "[A-Z0-9a-z._%+-]+#[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"
let emailTest = NSPredicate(format:"SELF MATCHES %#", emailRegEx)
let result = emailTest.evaluate(with: textField.text)
let title = "Alert title"
let message = result ? "This is a correct email" : "Wrong email syntax"
if !result {
self.run(SKAction.wait(forDuration: 0.01),completion:{[unowned self] in
guard let delegate = self.delegate else { return }
(delegate as! TransitionDelegate).showAlert(title:title,message: message)
})
}
}
}
deinit {
print("\n THE SCENE \((type(of: self))) WAS REMOVED FROM MEMORY (DEINIT) \n")
}
}
Output:
Animated output:
As you can see we can handle both framework with their delegate methods, I've tested this page with iPhone 5 and iPhone 7 plus.

Nodes are colliding but not responding to didBeginContact function

My nodes are colliding when i run it. The coin bounces of the player node. However, when I want to call the didBeginContact function it is not responding...
I want eventually a label to display the score +1 every time the coin hits the player. Also the coin should disappear when colliding with the player. But my contact is not working so I can't make any collision rules make the label display the score.
import SpriteKit
import GameplayKit
// Collision categories
struct physicsCategory {
static let playerCat : UInt32 = 1
static let coinCat : UInt32 = 2
}
class GameScene: SKScene, controls, SKPhysicsContactDelegate {
let player = SKSpriteNode(imageNamed:"trump")
let points = SKLabelNode()
let buttonDirLeft = SKSpriteNode(imageNamed: "left")
let buttonDirRight = SKSpriteNode(imageNamed: "right")
let background = SKSpriteNode(imageNamed: "background")
var pressedButtons = [SKSpriteNode]()
let popUpMenu = SKSpriteNode(imageNamed: "popupmenu")
var score = 0
var gameOver = false
var startGame = false
var rules = false
override func didMove(to view: SKView) {
self.physicsWorld.contactDelegate = self
//score label
points.position = CGPoint(x: 530, y: 260)
points.text = ("\(score)")
points.zPosition = 6
points.fontColor = UIColor.black
points.fontSize = 50
addChild(points)
//Set Background
background.zPosition = 1
background.position = CGPoint(x: frame.size.width / 2, y: frame.size.height / 2)
background.size.width = 580
background.size.height = 320
addChild(background)
// Player
player.position = CGPoint(x: 250, y: 40)
player.zPosition = 2
player.size.width = 40
player.size.height = 60
player.physicsBody = SKPhysicsBody(rectangleOf: player.size)
player.physicsBody?.affectedByGravity = false
player.physicsBody!.categoryBitMask = physicsCategory.playerCat
player.physicsBody!.contactTestBitMask = physicsCategory.coinCat
player.physicsBody?.collisionBitMask = 0
player.physicsBody?.isDynamic = false
self.addChild(player)
//contact has started
func didBeginContact(contact: SKPhysicsContact){
let firstBody: SKPhysicsBody = contact.bodyA
let secondBody: SKPhysicsBody = contact.bodyB
if ((firstBody.categoryBitMask == physicsCategory.playerCat) && (secondBody.categoryBitMask == physicsCategory.coinCat)){
CollisionWithCoin(player: firstBody.node as! SKSpriteNode, coins: secondBody.node as! SKSpriteNode)
}
}
func CollisionWithCoin(player: SKSpriteNode, coins:SKSpriteNode){
NSLog("Hello")
}
//repeat coing spawning
run(SKAction.repeatForever(
SKAction.sequence([
SKAction.run(spawnCoins),
SKAction.wait(forDuration: 1.0)])))
}
//coin settings
func random() -> CGFloat {
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
func random(min: CGFloat, max: CGFloat) -> CGFloat {
return random() * (max - min) + min
}
//spawn coins
func spawnCoins() {
// 2
let coins = SKSpriteNode(imageNamed: "coins")
coins.zPosition = 2
coins.size.width = 25
coins.size.height = 25
coins.physicsBody = SKPhysicsBody(rectangleOf: coins.size )
coins.physicsBody!.categoryBitMask = physicsCategory.coinCat
coins.physicsBody!.contactTestBitMask = physicsCategory.playerCat
coins.physicsBody?.collisionBitMask = 1
coins.position = CGPoint(x: frame.size.width * random(min: 0, max: 1), y: frame.size.height + coins.size.height/2)
let action = SKAction.moveTo(y: -350, duration: TimeInterval(random(min: 1, max: 5)))
let remove = SKAction.run({coins.removeFromParent()})
let sequence = SKAction.sequence([action,remove])
coins.run(sequence)
addChild(coins)
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
/* Called before each frame is rendered */
if pressedButtons.index(of: buttonDirLeft) != nil {
player.position.x -= 4.0
}
if pressedButtons.index(of: buttonDirRight) != nil {
player.position.x += 4.0
}
}
//MOVEMENT FUNCTIONS START HERE
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches {
let location = touch.location(in: self)
let previousLocation = touch.previousLocation(in: self)
for button in [buttonDirLeft, buttonDirRight] {
// I check if they are already registered in the list
if button.contains(location) && pressedButtons.index(of: button) == nil {
pressedButtons.append(button)
}
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches {
let location = touch.location(in: self)
let previousLocation = touch.previousLocation(in: self)
for button in [buttonDirLeft, buttonDirRight] {
// if I get off the button where my finger was before
if button.contains(previousLocation)
&& !button.contains(location) {
// I remove it from the list
let index = pressedButtons.index(of: button)
if index != nil {
pressedButtons.remove(at: index!)
}
}
// if I get on the button where I wasn't previously
else if !button.contains(previousLocation)
&& button.contains(location)
&& pressedButtons.index(of: button) == nil {
// I add it to the list
pressedButtons.append(button)
}}}}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches {
let location = touch.location(in: self)
let previousLocation = touch.previousLocation(in: self)
for button in [buttonDirLeft, buttonDirRight] {
if button.contains(location) {
let index = pressedButtons.index(of: button)
if index != nil {
pressedButtons.remove(at: index!)
}
}
else if (button.contains(previousLocation)) {
let index = pressedButtons.index(of: button)
if index != nil {
pressedButtons.remove(at: index!)
}
}
}
}
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches {
let location = touch.location(in: self)
let previousLocation = touch.previousLocation(in: self)
for button in [buttonDirLeft, buttonDirRight] {
if button.contains(location) {
let index = pressedButtons.index(of: button)
if index != nil {
pressedButtons.remove(at: index!)
}
}
else if (button.contains(previousLocation)) {
let index = pressedButtons.index(of: button)
if index != nil {
pressedButtons.remove(at: index!)
}
}
}
}
}
}
Can you try defining your bit masks like this.
enum PhysicsCategory {
static let playerCat: UInt32 = 0x1 << 0
static let coinCat: UInt32 = 0x1 << 1
}
and can you try this code in your contact method. Also note that if you are using Swift 3 the name of the contact method name has changed.
//contact has started
func didBegin(_ contact: SKPhysicsContact) {
let firstBody: SKPhysicsBody = contact.bodyA
let secondBody: SKPhysicsBody = contact.bodyB
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if ((firstBody.categoryBitMask == physicsCategory.playerCat) && (secondBody.categoryBitMask == physicsCategory.coinCat)){
CollisionWithCoin(player: firstBody.node as! SKSpriteNode, coins: secondBody.node as! SKSpriteNode)
}
}
}
You are also using a few ! in your code which makes it less safe. Try using ? and "if let" whenever possible when dealing with optionals. So for example write your physics bodies like this even though you know you just created it. You are doing it sometimes and other times you are using !, be consistent.
player.physicsBody?.categoryBitMask...
etc
If that physics body for some reason is/becomes nil and you are using ! you will crash.
I would also write your contact method like this, to ensure you also dont crash if the contact methods fires more than once for the same collision.
func collisionWithCoin(player: SKSpriteNode?, coins:SKSpriteNode?){
guard let player = player, let coins = coins else { return }
print("Hello")
}
and than call it like so in the didBeginContact method
collisionWithCoin(player: firstBody.node as? SKSpriteNode, coins: secondBody.node as? SKSpriteNode)
Finally I would also try to follow the swift guidelines, your methods should start with small letters and classes, structs should start with capital letters.
Hope this helps

Sprite kit contact problems

I am making a game that requires you to dodge falling objects. I have tried to make it it so that when the falling object hits the player certain code runs. But it is not working please show me what is wrong with my code.
import SpriteKit
struct physicsCatagory {
static let person : UInt32 = 0x1 << 1
static let Ice : UInt32 = 0x1 << 2
}
class GameScene: SKScene, SKPhysicsContactDelegate {
var person = SKSpriteNode(imageNamed: "Person")
override func didMoveToView(view: SKView) {
physicsWorld.contactDelegate = self
person.position = CGPointMake(self.size.width/2, self.size.height/12)
person.setScale(0.4)
person.physicsBody = SKPhysicsBody(rectangleOfSize: person.size)
person.physicsBody?.affectedByGravity = false
person.physicsBody?.categoryBitMask = physicsCatagory.person
person.physicsBody?.contactTestBitMask = physicsCatagory.Ice
person.physicsBody?.dynamic = false
var IceTimer = NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: Selector("spawnFirstIce"), userInfo: nil, repeats: true)
self.addChild(person)
func didBeginContact(contact: SKPhysicsContact) {
let firstBody = contact.bodyA
let secondBody = contact.bodyB
if firstBody.categoryBitMask == physicsCatagory.person && secondBody.categoryBitMask == physicsCatagory.Ice || firstBody.categoryBitMask == physicsCatagory.Ice && secondBody.categoryBitMask == physicsCatagory.person{
NSLog ("Test Test") }
}
}
func spawnFirstIce(){
let Ice = SKSpriteNode(imageNamed: "FirstIce")
Ice.setScale(0.5)
Ice.physicsBody = SKPhysicsBody(rectangleOfSize: Ice.size)
Ice.physicsBody?.categoryBitMask = physicsCatagory.Ice
Ice.physicsBody?.contactTestBitMask = physicsCatagory.person
Ice.physicsBody?.affectedByGravity = false
Ice.physicsBody?.dynamic = true
let MinValue = self.size.width / 8
let MaxValue = self.size.width - 20
let SpawnPoint = UInt32(MaxValue - MinValue)
Ice.position = CGPoint(x: CGFloat(arc4random_uniform(SpawnPoint)), y: self.size.height)
self.addChild(Ice)
let action = SKAction.moveToY(-85, duration: 3.0)
let actionDone = SKAction.removeFromParent()
Ice.runAction(SKAction.sequence([action,actionDone]))
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
person.position.x = location.x
}
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
person.position.x = location.x
}
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
You have defined didBeginContact inside of didMoveToView method :)
Place didBeginContact outside of that method and make it a member of GameScene class.