How to Make a Game Like Candy Crush Tutorial: OS X Port

Learn how to take an existing iOS Sprite Kit game and level it up to work on OS X too! By Gabriel Hauber.

Leave a rating/review
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Porting UIKit to Sprite Kit

As mentioned earlier, the Sprite Kit framework is built on top of the platform-specific UIKit framework on iOS and AppKit on OS X. On both platforms SKScene inherits ultimately from SKNode. On iOS this is built on top of UIResponder; on OS X it’s NSResponder. UIResponder provides methods such as touchesBegan and touchesMoved. NSResponder provides functions such as mouseDown and mouseDragged.

You have several options to tackle this difference:

This has the advantage of only having platform-neutral code in the shared group, and any OS-specific stuff exists only in the relevant targets. However, the amount of platform-specific work you’ll have to do is actually quite small, so there is a third, better way.

  1. Conditional compilation in the GameScene class.
    This has the advantage of keeping all code in a single source file. But it sacrifices code readability to do this since you’ll have one big source file with a lot of #if ande #else statements.
  2. Extract the common GameScene code into a superclass and implement OS-specific stuff in a subclass specific to its target. e.g.:
    • GameScene.swift
    • GameSceneIOS.swift
    • GameSceneMac.swift

    This has the advantage of only having platform-neutral code in the shared group, and any OS-specific stuff exists only in the relevant targets. However, the amount of platform-specific work you’ll have to do is actually quite small, so there is a third, better way.

  3. Put all event handling code inside a class extension and use conditional compilation.
    Doing this keeps code all in one place, and it is reusable by any class that derives from SKNode (something you will take advantage of shortly).

Cross-platform event handling extension

Right-click on the CookieCrunch Shared group, select New File… and create a new Swift File. Name it EventHandling.swift and make sure it is added to both the CookieCrunch and CookieCrunch Mac targets.

Replace the contents of the file with the following code:

import SpriteKit

// MARK: - cross-platform object type aliases

#if os(iOS)
typealias CCUIEvent = UITouch
#else
typealias CCUIEvent = NSEvent
#endif

The first step is to create the CCUIEvent type alias. On iOS it refers to a UITouch object; on OS X, an NSEvent. This will let you use this type in event handling code without having to worry about what platform you are developing on… within reason, of course. In your cross-platform code you will be limited to only calling methods or accessing properties that exist on both platforms.

Apple itself takes this approach for classes such as NSColor and UIColor, by creating a type alias SKColor that points to one or the other as appropriate for the platform. You’re already in good company with your cross-platform style! :]

Next, add the following code to the file:

extension SKNode {
    
  #if os(iOS)
    
  // MARK: - iOS Touch handling
    
  override public func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent)  {
    userInteractionBegan(touches.first as! UITouch)
  }

  override public func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent)  {
    userInteractionContinued(touches.first as! UITouch)
  }

  override public func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
    userInteractionEnded(touches.first as! UITouch)
  }

  override public func touchesCancelled(touches: Set<NSObject>, withEvent event: UIEvent) {
    userInteractionCancelled(touches.first as! UITouch)
  }
    
  #else
    
  // MARK: - OS X mouse event handling
    
  override public func mouseDown(event: NSEvent) {
    userInteractionBegan(event)
  }
    
  override public func mouseDragged(event: NSEvent) {
    userInteractionContinued(event)
  }
    
  override public func mouseUp(event: NSEvent) {
    userInteractionEnded(event)
  }
    
  #endif

  // MARK: - Cross-platform event handling

  func userInteractionBegan(event: CCUIEvent) {
  }
    
  func userInteractionContinued(event: CCUIEvent) {
  }
    
  func userInteractionEnded(event: CCUIEvent) {
  }

  func userInteractionCancelled(event: CCUIEvent) {
  }
    
}

This section of code defines the extension to SKNode for cross-platform behavior. You’re mapping the relevant location-based event handling methods on each platform to generic userInteractionBegan/Continued/Ended/Cancelled methods. In each call, the CCUIEvent is passed as a parameter.

Cross-platform GameScene class

Now, in any SKNode-derived class in your project, you only need to change any use of touchesBegan, etc, to userInteractionBegan, etc and it should compile and run on both platforms!

Open GameScene.swift. First, find and replace the following lines:

override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
  // Convert the touch location to a point relative to the cookiesLayer.
  let touch = touches.first as! UITouch
  let location = touch.locationInNode(cookiesLayer)

with:

override func userInteractionBegan(event: CCUIEvent) {
  // Convert the touch location to a point relative to the cookiesLayer.
  let location = event.locationInNode(cookiesLayer)

Similarly, find the method definition for touchesMoved:

override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {

and replace it with the following:

override func userInteractionContinued(event: CCUIEvent) {

Within that function, replace the lines

let touch = touches.first as! UITouch
let location = touch.locationInNode(cookiesLayer)

with:

let location = event.locationInNode(cookiesLayer)

Now replace:

override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {

with:

override func userInteractionEnded(event: CCUIEvent) {

And finally, replace:

override func touchesCancelled(touches: Set<NSObject>, withEvent event: UIEvent) {
  touchesEnded(touches, withEvent: event)
}

with:

override func userInteractionCancelled(event: CCUIEvent) {
  userInteractionEnded(event)
}

That’s all the changes to call the cross-platform version of the methods. Thanks to the type alias, you can just implement the userInteraction methods now for both OS X and iOS.

Build and run the Mac target. It won’t do anything yet, as its AppDelegate doesn’t set up the game.

As a final check to make sure you’ve done this all correctly, build and run the iOS target. The game should still play as normal.

Removing Dependencies on UIKit

Most of the game user interaction logic is, understandably enough, located in the GameViewController class. However, while AppKit for OS X provides view controllers and views just like UIKit on iOS, the differences are large enough that it would take a lot of work to reimplement the logic in the game controller specifically for OS X.

However, if you survey the GameViewController code, you’ll notice that most of it should be reusable on both platforms. Background music is played through an AVAudioPlayer which is also available on OS X. Much of the game turn logic should be reusable. It is only the code that accesses UIKit components (UILabel, UIImageView and UIButton) that needs to be adapted for OS X.

The various labels can be replaced with Sprite Kit’s SKLabelNode. The Shuffle button and Game Over images can be implemented using SKSpriteNode objects. With these, you can create a custom controller object that is usable on both platforms, maximising code reuse and leaving the total amount of platform-specific code at an absolute minimum.

Gabriel Hauber

Contributors

Gabriel Hauber

Author

Over 300 content creators. Join our team.