Saturday, July 25, 2015

Creating the Xcode Project (Swift/SpriteKit) -- Epic Color Crash Step 2

At the end of this project we will have three versions of this game, one is Swift and one in Objective-C using SpriteKit, and a cross platform version written in C++ using Cocos2d-x. This will give us a good feel for the differences in these options.

Preview: This is what you will have at the end of this step:
video

First the Swift/SpriteKit version

I initially wrote this in Swift using SpriteKit just because Swift and SpriteKit didn't exist when I did my last iOS programming projects and so it seemed like a great way to get to know the new language and the new framework.

I'm using Xcode version 6.4.

First I'm going to create an Xcode project, so I start up Xcode and select Create a new Xcode Project
If you don't get this dialog, then go to File->New->Project...


This is a game so we select... you guessed it "Game" and then click Next
In the next dialog we will select the name and environment for the game. For product name enter "Epic Color Crash". Then enter your Organization Name. I use my name here. Then your Organization Identifier. This needs to be unique from anyone else. The way to do this is to use a domain name that you own. Have several domain names that I own. I will be using epicbiking.com for promoting and supporting my applications so I will use epic biking.com as my Identifier except the convention is to put the domain name in reverse order so com.epicbiking. Then Xcode automatically selects com.epicbiking.Epic-Color-Crash as my Bundle Identifier. This is the unique name for my app and since I own epicbiking.com I don't have to worry that someone is already using that name.
Select the language: Swift
Game Technology: SpriteKit
Devices: Universal
So it should look like this with the com.epicbiking replaced with your own domain.
Make sure you select SpriteKit not SceneKit the names are close enough that it is easy to select the wrong one.

Select Next and it will ask you where to save the projects. I'll create a new directory in my home called Projects and put it there. My home resides on the new hard drive I installed so it will not eat up my startup disk space.
(Yes, my home is called pichu766, my son was into Pokemon and when I inherited his computer I didn't bother changing it.)
After creating the projects I get the shell of a project created. It is actually a functioning app that can be run in the simulator. However the first thing I notice is a little warning sign. In order to be able to distribute the game and get it on actual devices you need to have it signed. This will require that you have a Apple Developers license. I'm not going to cover getting a license set up. If you don't want to pay to be a developer than you will have to just run the app on the simulator. If you register as a developer or already are registered as a developer you can click the "Fix Issue" button.
This is great, we now have a project with a bunch of stuff already set up for us.

  • AppDelegate.swift file:  This is required for any app, but for what we are doing we won't need to be changing the defaults so we will ignore it.
  • GameScene.swift file: This is where we will do most of our work.
  • GameScene.sks file: This allows us to layout our scene without having to write code to create elements of the scene. We will modify this adding some of the interface items for our scene.
  • GameViewController.swift file: We will do a little work here. However we will be using SpriteKit scenes and will end up with a MenuScene that will end up doing a lot of what would normally be done in the main view controller.
  • Main.storyboard file: Again we will be using SpriteKit scenes and won't be changing this file.
  • Images.xcassets group: this contains the icons for our game and a spaceship image. Delete the spaceship image, we are not writing a spaceship game. In a future step we will create the icons and put them in here.
  • LaunchScreen.xib file: For now we will leave this alone.
  • In the Supporting Files there is a Info.plist file: Ignore this and the remaining files for now.

First thing we will do is delete out the hello world spaceship code from GameScene.swift.
In the didMoveToView function delete these lines:
        let myLabel = SKLabelNode(fontNamed:"Chalkduster")
        myLabel.text = "Hello, World!";
        myLabel.fontSize = 65;
        myLabel.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame));
        

        self.addChild(myLabel)
In the touchesBegan function remove these line:
            let location = touch.locationInNode(self)
            
            let sprite = SKSpriteNode(imageNamed:"Spaceship")
            
            sprite.xScale = 0.5
            sprite.yScale = 0.5
            sprite.position = location
            
            let action = SKAction.rotateByAngle(CGFloat(M_PI), duration:1)
            
            sprite.runAction(SKAction.repeatActionForever(action))
            
            self.addChild(sprite)
OK, we now have an empty SpriteKit game that we can start building on.
Now we want to add our colored balls we created in the previous step to our game. SpriteKit can us image atlases that store multiple images in one file which reduces the overall size of the images and makes the rendering of a scene faster. We want to use this so I will move the images into a directory that only has the .png files in it. I'll call it images.atlas by putting .atlas at the end of the directory name Xcode will automatically do the work to create an image atlas. It really is cool all the things SpriteKit does for you so you can concentrate on your game.
Now I want to add these files to my project, so I select File->Add Files to "Epic Color Crash"...
I will select Copy Items if needed to create a copy of my images in my project, and select Create Groups so that it will set it up as a image atlas group.

Select Add and we now when we build the app it will create an image atlas for us and SpriteKit will know how to get the images we want out of the atlas. 
Later we will want to create a few other atlases so I am going to create a group called images and put the image atlas into that group. So I right click on the images.atlas group and select New Group from Selection and then name the new group images.
Now we are ready to start writing some code.

First I'm going to create a class called ColoredBall. To start with it is simply a subclass of SKSpriteNode with nothing added. But by creating it as a new class we can add functionality to it later.
So in GameScene.sprite just below the import SpriteKit we will add the empty class for our balls.

class ColoredBall: SKSpriteNode {
}

Now let's create a function that will create a new ColoredBall and add it to our scene. I'm going to add it as the first function in the GameScene class but really the order of the functions doesn't matter. I want to pass in the location and color of the new ball. So right after class GameScene: SKScene { I'll add:
    func addColorBall(location: CGPoint, withColor color:String) {

Next I want to create the actual ColoredBall object. It is really easy to create a colored ball using one of the images from our images.atlas, we simply create a new sprite (or ColoredBall which is a subclass of Sprite) and tell it the name of the image, between Xcode and SpriteKit all the work is done to find and extract the image from the atlas.
        let sprite = ColoredBall(imageNamed: color)

I set the position to the location passed in:
       sprite.position = location

And I'm going to set the name of the sprite to be the color of the ball this will make it easy to find all the blue balls or red or whatever.
        sprite.name = color

Now I want to use the SpriteKit's physics engine to make the balls fall bounce around etc. So I create a physics body that is a circle that is the same size as the images I created. I created the balls with a highlight at the top of the ball if it rotates around it will just look wrong, so I will tell the physics body not to rotate, also I want the balls to slip around and fill in the available space so I am going to give them no friction:
        let body = SKPhysicsBody(circleOfRadius: 25)
        body.allowsRotation = false
        body.friction = 0.0
Finally I attach the physics body to the set the sprite to have that physicsBody, and add the sprite to the scene.
        sprite.physicsBody = body
        self.addChild(sprite)
    }
Here is the new function:
    func addColorBall(location: CGPoint, withColor color:String) {
        let sprite = ColoredBall(imageNamed: color)
        
        sprite.position = location
        sprite.name = color
        let body = SKPhysicsBody(circleOfRadius: 25)
        sprite.physicsBody = body
        body.allowsRotation = false
        body.friction = 0.0
        self.addChild(sprite)
    }
Now I want to make it so balls start falling into my scene. SpriteKit uses a rendering loop for each frame before it is rendered. These are the steps in the loop: 

Call the scenes update. This is where we will do most of our work like adding sprites.
Then the scene evaluates and actions and then calls didEvaluateActions. The scene then simulates any physics and the calls didSimulatePhysics. Then constraints are applied and didApplyConstraints is called, didFinishUpdate is called, and finally the scene is rendered.

We can do all of our work in the update function and in the event handling functions. I don't want to drop a new ball on every update, instead I want a have a delay between ball drops, and as the levels increase we will make that delay shorter. So I am going to add to properties to my GameScene class, lastBallDropped and newBallDelay. After the class GameScene: SKScene { add properties for the time the last ball was dropped, and the delay between drops.
    var lastBallDropped: CFTimeInterval = 0
    var newBallDelay: CFTimeInterval = 1.5

Now we can add the code to drop balls during the update. The current time is passed into update. We will compare that time with the last time a ball was dropped and see if the new ball delay has expired. If it has we will save the time that the new ball is being dropped an drop a new ball.
        if currentTime > (lastBallDropped + newBallDelay) {
                lastBallDropped = currentTime

We want a random color for the ball and a random position to drop the ball so we will use arc4random_uniform to create our random numbers.
                let colors = ["red", "yellow", "blue"]
                var xpos = Int(arc4random_uniform(UInt32(self.size.width-100) ))+50
                let ypos = self.size.height - 120
                let location = CGPoint(x: xpos, y: Int(ypos))
                var colorIndex = arc4random_uniform(3)
And then finally add the ball:
                addColorBall(location, withColor: colors[Int(colorIndex)])

The GameScene.swift file should now look like this:
//
//  GameScene.swift
//  Epic Color Crash
//
//  Created by Daniel Burton on 7/25/15.
//  Copyright (c) 2015 Daniel Burton. All rights reserved.
//

import SpriteKit

class ColoredBall: SKSpriteNode {
}

class GameScene: SKScene {
    var lastBallDropped: CFTimeInterval = 0
    var newBallDelay: CFTimeInterval = 1.5

    func addColorBall(location: CGPoint, withColor color:String) {
        let sprite = ColoredBall(imageNamed: color)
        
        sprite.position = location
        sprite.name = color
        let body = SKPhysicsBody(circleOfRadius: 25)
        sprite.physicsBody = body
        body.allowsRotation = false
        body.friction = 0.0
        self.addChild(sprite)
    }

    
    override func didMoveToView(view: SKView) {
        /* Setup your scene here */
    }
    
    override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
        /* Called when a touch begins */
        
        for touch in (touches as! Set<UITouch>) {
        }
    }
   
    override func update(currentTime: CFTimeInterval) {

        // if the time has come to drop a new ball
        if currentTime > (lastBallDropped + newBallDelay) {
                lastBallDropped = currentTime
                let colors = ["red", "yellow", "blue"]
                var xpos = Int(arc4random_uniform(UInt32(self.size.width-100) ))+50
                let ypos = self.size.height - 120
                let location = CGPoint(x: xpos, y: Int(ypos))
                var colorIndex = arc4random_uniform(3)
                addColorBall(location, withColor: colors[Int(colorIndex)])
        }

    }
    

}

We can now build the app and run it on a simulator. Colored balls will appear near the top and fall of the bottom of the screen. Not exactly what we want. We want them to stay in the scene. So lets add a border to contain the balls. Add one line to the didMovetoView function to create a physicsBody that is an edge loop. The physics engine will not apply physics to the edge loop but other objects that are affected by the physics engine will bounce off the edge instead of just going through it. We will just create the loop as a rectangle that is the size of our scene. All of this in one simple line:

    override func didMoveToView(view: SKView) {
        /* Setup your scene here */
        self.physicsBody = SKPhysicsBody(edgeLoopFromRect: self.frame)

    }

OK now it drops ball but we can see that our scene is actually bigger than the screen. Also we want the game to be the same no matter what device it is running on so that on an iPad you don't have a big area that holds hundreds of balls and on an old iPhone you have a scene that only holds a few dozen. So I am going to resize the scene and then have SpriteKit scale the scene to fit the device.

First I select the GameScene.sks file in the Navigator panel, and then in the Utilities panel on the right I click on the node inspector icon, a circle with four little circles in the corners (OK, I know circles don't have corners.) I can now set the background color to black and the size to 422 by 750. 



Now back in the didMoveToView function I set the scale mode AspectFit, scale to fit the full scene in on the screen but keep the x scale and y scale the same.
        self.scaleMode = .AspectFit

Now if we run it on any of the simulators we should get a consistent game area. Here is the GameScene.swift file so far:
//
//  GameScene.swift
//  Epic Color Crash
//
//  Created by Daniel Burton on 7/25/15.
//  Copyright (c) 2015 Daniel Burton. All rights reserved.
//

import SpriteKit

class ColoredBall: SKSpriteNode {
}

class GameScene: SKScene {
    var lastBallDropped: CFTimeInterval = 0
    var newBallDelay: CFTimeInterval = 1.5

    func addColorBall(location: CGPoint, withColor color:String) {
        let sprite = ColoredBall(imageNamed: color)
        
        sprite.position = location
        sprite.name = color
        let body = SKPhysicsBody(circleOfRadius: 25)
        sprite.physicsBody = body
        body.allowsRotation = false
        body.friction = 0.0
        self.addChild(sprite)
    }

    
    override func didMoveToView(view: SKView) {
        /* Setup your scene here */
        self.scaleMode = .AspectFit

        self.physicsBody = SKPhysicsBody(edgeLoopFromRect: self.frame)

    }
    
    override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
        /* Called when a touch begins */
        
        for touch in (touches as! Set<UITouch>) {
        }
    }
   
    override func update(currentTime: CFTimeInterval) {

        // if the time has come to drop a new ball
        if currentTime > (lastBallDropped + newBallDelay) {
                lastBallDropped = currentTime
                let colors = ["red", "yellow", "blue"]
                var xpos = Int(arc4random_uniform(UInt32(self.size.width-100) ))+50
                let ypos = self.size.height - 120
                let location = CGPoint(x: xpos, y: Int(ypos))
                var colorIndex = arc4random_uniform(3)
                addColorBall(location, withColor: colors[Int(colorIndex)])
        }

    }
    

}

In the next step we will start adding in some game logic.

No comments:

Post a Comment


My Bicycle Store