Group Group Group Group Group Group Group Group Group

raywenderlich.com Forums

SpriteKit Swift 3 Tutorial for Beginners

Learn how to make your first iOS game in this SpriteKit Swift 3 tutorial for complete beginners - and yes, there are ninjas!


This is a companion discussion topic for the original entry at https://www.raywenderlich.com/913-spritekit-swift-3-tutorial-for-beginners

@rwenderlich since you’re a pioneer of Cocos2D (I watched a bit of the podcast when you called SpriteKit crap compared to cocos2d :smiley: ), why don’t you start making tutorials (or even a book) on cocos2d-x (C++)? It brings the power of cocos2d made cross platform. It has a lot of UI controls made like buttons or equivalent of tableviews to show some good UI in games.
Even if the projects aren’t created in Xcode anymore, but command line, they can still be opened and compiled/debugged in Xcode… cocos2dx doesn’t get much docs cover around the web (despide it was chosen by a lot of prevalent 2d games included an angry birds and Geometry Dash).

A book from you would get the monopoly in terms of possible quality… Why not think on it? This site covers also Android (Unity team is of 10 people, Android’s one 16 and Apple Game Framework just 12… and still no Android book). With a book about cocos2dx there would be cover for games in Android’s too besides the Unity one (which as you mentioned isn’t free software nor as free beer nor as free speech). cocos2dx is completely free… And it should not be hard too much for your teams convert existing games from SpriteKit in it or newer games. What do you think?

I’ve seen that there are like 2 tutorials in the Archive… but why not something new?

As an ex-C++ programmer I am unsure about teaching C++. It’s a useful and high performance language but not one I’d want to teach because I’m not good enough at it. I think there is probably a small overlap between the Java/Swift/ObjC people we have and C++ experts. If you’re looking into cross-platform development and you have decided to pick up Cocs2d-x maybe you don’t need much help anyway? Just my thought.

As for the lack of Android books, maybe there’s a need for authors to step up and say “hey Ray let’s publish an Android book!” - I understand that’s what happened with a lot of the iOS publications.

The book shouldn’t forcibly teach C++… RW apple games’ book as far as I understood, assume you are already well confortable with Swift. Why a hypothetical cocos2dx book should teach C++ instead of just giving some tips across the road?

If you’re looking into cross-platform development and you have decided to pick up Cocs2d-x maybe you don’t need much help anyway? Just my thought

That may be true, but indeed I think it’s a good thing RW could bring to everyone… Many people fear cocos2dx because learning resources are really few and there isn’t a decent book.

This site has several tutorials in Objective-C, some in C# and even a book in C#. Android tutorials are Java (It would be nice if an Android book from this site actually will make use of Kotlin).
It isn’t all Swift… and Objective-C isn’t really that easy against C++ (at least common patterns).

I think it would be nice, that’s all. C# and C++ are leaders in industry of gaming. And I think C++ is the one on top. They could make a really quality book and I’d buy it for sure even if it’s 50 bucks.

Certainly in mainstream gaming C++ and C# are king. Definitely good skills for anyone who might one day step out of mobile development, and developers should plan on doing something completely different at least every decade (whether they want to or not. that’s just how technology works).

Would you consider writing a tutorial? That might be a first step before a book. It’s not the easiest thing to do but nothing cements your knowledge like teaching another person how to do it, and it pays well. If not you, that’s what we need - someone familiar with Cocs2d-x who would like to make some pocket money and has a little spare time. The first step is to take a look at Write For Us and send off that email to get the ball rolling.

Great tutorial Ray. I am developing a game because of this tutorial!

I was a C/C++ programmer, but I like Swift and the features that come with it to easily create so many casual games. Ofcourse for high powered games C/C++ are worth the complexity.

Xcode 8.1 (8B62)
OS X 10.11.6 (El Capitan)

Having an issue. I have entered the code for the “Shooting Projectiles” part. I get 3 Build Errors:

  1. (the next line) return… scalar… "Binary operator " * " cannot be applied to operands of type ‘CGFloat’ and “CGPoint’”
  2. (next function down - /) return… "Binary operator “/” cannot be applied to operands of type ‘CGFloat’ and “CGPoint’”

CODE:
func * (point: CGPoint, scalar: CGPoint) -> CGPoint {
return CGPoint(x: point.x * scalar, y: point.y * scalar)
}

func / (point: CGPoint, scalar: CGPoint) -> CGPoint {
return CGPoint(x: point.x / scalar, y: point.y / scalar)
}

As far as I can tell I have copied it exactly from the example, but I can’t get rid of these build errors.

@beoxs I just downloaded the final project and it ran fine for me - have you tried that and compared it to your own project? Maybe there is a typo somewhere.

Found it!
I typed in “scalar: CGPoint” instead of “scalar: CGFloat”. Oops.

I had to copy and paste & undo paste, then repeat until I found it.

I am having trouble with sound in this project. It just is not playing. My setup is macOS Sierra Version 10.12.1 Xcode Version 8.1 (8B62)
I followed the tutorial and the graphics part works, just no sound! I even tried running it on my iPhone and still no sound.

I get this in the console when trying to play the game.

2016-11-04 18:43:49.194205 SpriteKitSimpleGame[581:92796] [DYMTLInitPlatform] platform initialization successful
2016-11-04 18:43:49.376121 SpriteKitSimpleGame[581:92637] Metal GPU Frame Capture Enabled
2016-11-04 18:43:49.376842 SpriteKitSimpleGame[581:92637] Metal API Validation Enabled
2016-11-04 18:43:58.740515 SpriteKitSimpleGame[581:92637] SKAction: Error loading sound resource: “pew-pew-lei.caf”
2016-11-04 18:43:58.854880 SpriteKitSimpleGame[581:92637] SKAction: Error loading sound resource: “pew-pew-lei.caf”
2016-11-04 18:43:59.053589 SpriteKitSimpleGame[581:92637] SKAction: Error loading sound resource: “pew-pew-lei.caf”
2016-11-04 18:43:59.220851 SpriteKitSimpleGame[581:92637] SKAction: Error loading sound resource: “pew-pew-lei.caf”
Hit

I have the code in my project just like the code in the download!

Any suggestions?

Never mind. I reloaded the sound directory from the downloaded folder and got it working now.

I added a label to the main GameScene that displays how many monsters you have killed. Also added a new variable to the init in GameOverScene to pass in the number of monsters killed so it shows in the Win or Lose screen.

class GameScene: SKScene, SKPhysicsContactDelegate {

// 1
let player = SKSpriteNode(imageNamed: "player")
**var monstersDestroyed = 0**
**var myLabel = SKLabelNode(fontNamed: "Arial")**


override func didMove(to view: SKView) {
    
    
    
    // 2
    backgroundColor = SKColor.white
    // 3
    player.position = CGPoint(x: size.width * 0.1, y: size.height * 0.5)
    // 4
    addChild(player)
    
    physicsWorld.gravity = CGVector.zero
    physicsWorld.contactDelegate = self
    
    run(SKAction.repeatForever(
        SKAction.sequence([
            SKAction.run(addMonster),
            SKAction.wait(forDuration: 1.0)
            ])
    ))
    
    let backgroundMusic = SKAudioNode(fileNamed: "background-music-aac.caf")
    backgroundMusic.autoplayLooped = true
    addChild(backgroundMusic)
    
    
    **myLabel.text = "Monsters Killed = 0"**

** myLabel.fontSize = 20**
** myLabel.fontColor = SKColor.black**
** myLabel.position = CGPoint(x: size.width * 0.2, y: size.height * 0.9)**
** addChild(myLabel)**

}

func random() -> CGFloat {
    return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}

func random(min: CGFloat, max: CGFloat) -> CGFloat {
    return random() * (max - min) + min
}

func addMonster() {
    
    // Create Sprite
    let monster = SKSpriteNode(imageNamed: "monster")
    
    monster.physicsBody = SKPhysicsBody(rectangleOf: monster.size) // 1
    monster.physicsBody?.isDynamic = true // 2
    monster.physicsBody?.categoryBitMask = PhysicsCategory.Monster // 3
    monster.physicsBody?.contactTestBitMask = PhysicsCategory.Projectile // 4
    monster.physicsBody?.collisionBitMask = PhysicsCategory.None // 5
    
    // Determine where to spawn the monster along the Y axis
    let actualY = random(min: monster.size.height/2, max: size.height - monster.size.height/2)
    
    // Position the monster slightly off-screen along the right edge,
    // and along a random position along the Y axis as calculated above
    monster.position = CGPoint(x: size.width + monster.size.width/2, y: actualY)
    
    // Add the monster to the scene
    addChild(monster)
    
    // Determine speed of the monster
    let actualDuration = random(min: CGFloat(2.0), max: CGFloat(4.0))
    
    // Create the actions
    let actionMove = SKAction.move(to: CGPoint(x: -monster.size.width/2, y: actualY), duration: TimeInterval(actualDuration))
    let actionMoveDone = SKAction.removeFromParent()
    let loseAction = SKAction.run() {
        let reveal = SKTransition.flipHorizontal(withDuration: 0.5)
        **let gameOverScene = GameOverScene(size: self.size, won: false, monstersKilled: self.monstersDestroyed)**
        self.view?.presentScene(gameOverScene, transition: reveal)
    }
    monster.run(SKAction.sequence([actionMove, loseAction, actionMoveDone]))
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    
    run(SKAction.playSoundFileNamed("pew-pew-lei.caf", waitForCompletion: false))
    
    // 1 - Choose one of the touches to work with
    guard let touch = touches.first else {
        return
    }
    
    let touchLocation = touch.location(in: self)
    
    // 2 - Set up initial location of projectile
    let projectile = SKSpriteNode(imageNamed: "projectile")
    projectile.position = player.position
    
    projectile.physicsBody = SKPhysicsBody(circleOfRadius: projectile.size.width/2)
    projectile.physicsBody?.isDynamic = true
    projectile.physicsBody?.categoryBitMask = PhysicsCategory.Projectile
    projectile.physicsBody?.contactTestBitMask = PhysicsCategory.Monster
    projectile.physicsBody?.collisionBitMask = PhysicsCategory.None
    projectile.physicsBody?.usesPreciseCollisionDetection = true
    
    
    // 3 - Determine offset of location to projectile
    let offset = touchLocation - projectile.position
    
    // 4 - Bail out if you are shooting down or backwards
    if (offset.x < 0) { return }
    
    // 5 - OK to add now - you've double checked position
    addChild(projectile)
    
    // 6 - Get the direction of where to shoot
    let direction = offset.normalized()
    
    // 7 - Make it shoot far enough to be guaranteed off screen
    let shootAmount = direction * 1000
    
    // 8 - Add the shoot amount to the current position
    let realDest = shootAmount + projectile.position
    
    // 9 - Create the actions
    let actionMove = SKAction.move(to: realDest, duration: 2.0)
    let actionMoveDone = SKAction.removeFromParent()
    projectile.run(SKAction.sequence([actionMove, actionMoveDone]))
}

func projectileDidCollideWithMonster(projectile: SKSpriteNode, monster: SKSpriteNode) {
    print("Hit")
    projectile.removeFromParent()
    monster.removeFromParent()
    monstersDestroyed += 1
    myLabel.text = "Monsters Killed = " + String(monstersDestroyed)
    if(monstersDestroyed > 30) {
        let reveal = SKTransition.flipHorizontal(withDuration: 0.5)
        **let gameOverScene = GameOverScene(size: self.size, won: true,**

** monstersKilled: self.monstersDestroyed)**
self.view?.presentScene(gameOverScene, transition: reveal)
}
}

func didBegin(_ contact: SKPhysicsContact) {
    // 1
    var firstBody: SKPhysicsBody
    var secondBody: SKPhysicsBody
    if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
        firstBody = contact.bodyA
        secondBody = contact.bodyB
    } else {
        firstBody = contact.bodyB
        secondBody = contact.bodyA
    }
    
    // 2
    if ((firstBody.categoryBitMask & PhysicsCategory.Monster != 0) &&
        (secondBody.categoryBitMask & PhysicsCategory.Projectile != 0)) {
        projectileDidCollideWithMonster(projectile: firstBody.node as! SKSpriteNode, monster: secondBody.node as! SKSpriteNode)
    }
}

}

class GameOverScene: SKScene {

init(size: CGSize, won:Bool, **monstersKilled: Int** ) {
    super.init(size: size)
    
    backgroundColor = SKColor.white
    
    let message = won ? "You Won! :]" : "You Lose! :[ "
    
    let label = SKLabelNode(fontNamed: "Chalkduster")
    label.text = message
    label.fontSize = 40
    label.fontColor = SKColor.black
    label.position = CGPoint(x: size.width/2, y: size.height/2)
    addChild(label)
    **let label2 = SKLabelNode(fontNamed: "Chalkduster")**

** label2.text = "Monsters Killed = " + String(monstersKilled)**
** label2.fontSize = 20**
** label2.fontColor = SKColor.black**
** label2.position = CGPoint(x: size.width/2, y: size.height/3)**
** addChild(label2)**

    run(SKAction.sequence([
        SKAction.wait(forDuration: 3.0),
        SKAction.run() {
            let reveal = SKTransition.flipHorizontal(withDuration: 0.5)
            let scene = GameScene(size: size)
            self.view?.presentScene(scene, transition:reveal)
        }
    ]))
}

required init(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

}

@jnbutler1815 Glad you got it working! And very cool re: your modification to the game, thanks for sharing.

Thank you for this helpful tutorial. I’m trying to add a start screen to the project which loads when the app starts up. Any pointers on where that would go (GameScene vs GameViewController) and how to initiate it?

I would recommend creating a new scene for your startup. Simply create a new class like GameScene, but called StartupScene or some such instead. Then make your View Controller start with he GameScene, and transition to the GameScene when the user taps (or however else you want the game to begin).

We cover this exact thing in the scenes chapter in 2D Apple Games by Tutorials FYI.

Hi Ray, I’ve noticed that if I play the game for a few minutes, win or lose, the memory usage creeps up from about 10mb to say 50mb after a couple of minutes. It never seems to return to a lower figure it always increases. The same thing happens in my own application, except the increase is 20mb at a time, so the app crashes after about 10-15 plays of the scene. Do you have suggestions for correct memory management in this type of app?

Same sound issue here as @jnbutler1815 . Turns out that when drag-dropping the Sounds folder into the project, in the dialogue box that appears you must switch the ‘Added folders’ option from ‘Create folder references’ over to ‘Create groups’. You’ll note the difference in the little folder icon in front: yellow instead of blue.

I also had the same sound issue. I fixed it by putting “Sounds/” in front of the filename.

For super newbie’s, the Moving Monsters section indicates to add code to existing GameScene.swift. Make sure to add it right BEFORE the closing }

Otherwise several compile errors about size and addChild can occur without much context as to why.

Hi

In the collision detection part

    if ((firstBody.categoryBitMask & PhysicsCategory.Monster != 0) &&
        (secondBody.categoryBitMask & PhysicsCategory.Projectile != 0)) {
        projectileDidCollideWithEnemy(projectile: firstBody.node as! SKSpriteNode, monster: secondBody.node as! SKSpriteNode)
        
    }

I think projectile is the secondBody and Monster is the firstBody

hence code will become like below:

    if ((firstBody.categoryBitMask & PhysicsCategory.Monster != 0) &&
        (secondBody.categoryBitMask & PhysicsCategory.Projectile != 0)) {
        projectileDidCollideWithEnemy(projectile: secondBody.node as! SKSpriteNode, monster: firstBody.node as! SKSpriteNode)
        
    }

Let me know if this is correct ? If this is correct. Can you please update the post.

“fatal error: unexpectedly found nil while unwrapping an Optional value” in yet another SpriteKit project.

If I set the “SKAction.wait(forDuration: 1.0)” to a lower number like 0.3 or 0.1 (just to recreate the problem) to have a lot of monsters continuously spawning over time, the project crashes when I fire the projectile (after a few hits). It seems to crash when there are 2 or more monsters near by each other or if the monsters overlap each other.

I’ve seen this type of crash in several other SpriteKit projects (on YouTube and other tutorial sites) with collision detection involved. If I google, there are tons of posts about this problem on the internet but nobody seems to have solved this. Is there a solution to this problem? Otherwise it seems to be a waste of time for me and others continuing to try to make shooter games (and other games too) with SpriteKit.

I’ve been coding with Swift/SpriteKit for about 6-7 months and I like the language and the framework but stumbling on a problems like this takes away my motivation when I can’t find a solution to the problem and when I try to follow a tutorial that has this particular problem “built-in”, it doesn’t help my motivation.

Thanks for an otherwise super tutorial!