Putting a SpriteKit Scene on a View Controller

by Lou Franco

In a typical iOS app, you build your screens either with UIKit's UIViewController, UIView and possibly storyboards or you might try SwiftUI and build out a View based on an ObservableObject.

Those kinds of apps often have scrollable lists of data, forms, tabs, or hierarchical navigation suitable for a to do list or an email client.

But games are something very different.

To develop 2D games on iOS, Apple has provided SpriteKit. SpriteKit builds up its user interfaces from nodes that are in a "physics world" scene.

To get an idea of what that means, let's build a very minimal scene.

Scenes are hosted in view controllers

If you start with a blank app in Xcode (not the game template), you get an empty view controller to start. If you put this code in it, it will host an empty SpriteKit scene.

First, we need to import SpriteKit

Then, use this code in your view controller.

class GameViewController: UIViewController {
    var scene: GameScene?

    override func loadView() {
        super.loadView()
        self.view = SKView()
        self.view.bounds = UIScreen.main.bounds
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        setupScene()
    }

    func setupScene() {
        if let view = self.view as? SKView, scene == nil {
            let scene = GameScene(size: view.bounds.size)
            view.presentScene(scene)
            self.scene = scene
        }
    }

    override var prefersStatusBarHidden: Bool {
        return true
    }
}

And make a new file called GameScene.swift with this code:

import SpriteKit
import GameplayKit

class GameScene: SKScene, SKPhysicsContactDelegate {
    override func didMove(to view: SKView) {
        self.backgroundColor = .cyan
    }
}

If you do that correctly, you will see a blank Cyan colored full screen. That screen is a SpriteKit scene. The function didMove(to view:) is called when scene is presented by the view controller and can be used to set up the game.

The Coordinate System

In SpriteKit, the coordinate system is a little different than UIKit. The origin is at the bottom-left of the screen. As you move to the right, X increases, and as you move up, Y increases. This might actually be more intuitive to you as it matches the coordinate system used in most graphs, but if you are used to UIKit, then you have to get used to Y being flipped.

Also, when you place objects, you are placing their center-point. In UIKit, the position of a view is its top-left point, but if you imagine setting the center property instead, it will match what SpriteKit does when you set a node's position.

A Falling Ball

In SpriteKit, a game is a physics simulation. There is gravity, and objects have friction, mass, and bounce off each other.

Let's put a ball in the air and let it fall to the ground.

In UIKIt, we have different UIViews like labels and buttons. In SpriteKit, we have different node types. For this code, we'll use the SKShapeNode which can render as different simple shapes like a circle or rectangle.

The basic idea is to create nodes, set up their physical behavior, attributes, and starting position, and then add them to the scene.

func setupGame() {
  guard let view = self.view else { return }

  // Create a ball that looks like a circle and acts like a circle to the physics engine
  let ball = SKShapeNode(circleOfRadius: 10)
  ball.physicsBody = SKPhysicsBody(circleOfRadius: 10)

  // Put it in the center of the screen and make it white
  ball.position = CGPoint(x: view.frame.midX, y: view.frame.midY)
  ball.fillColor = .white

  addChild(ball)

  // Create the ground which looks and acts like a rectangle at the bottom of the screen
  let groundHeight: CGFloat = 40
  let size = CGSize(width: view.frame.size.width, height: groundHeight)
  let ground = SKShapeNode(rectOf: size)
  ground.physicsBody = SKPhysicsBody(rectangleOf: size)

  // Put the ground on the bottom of the screen and make it brown
  ground.position = CGPoint(x: view.frame.midX, y: groundHeight / 2)
  ground.fillColor = .brown

  // The ground is not affected by gravity and doesn't move
  ground.physicsBody?.isDynamic = false

  addChild(ground)
}

Make sure to put a call to setupGame() in the didMove(to view:) function.

When you run it, it looks like this:

Ball falling to ground

What's next

In the next few articles, we'll make some useful extensions and then look at how to make Pong.

Make sure to sign up to my newsletter to get notified when a new article is published.

Contact me if you are working on a Game and need help.

Next Article: Some useful SKPhysicsBody Extensions for SpriteKit nodes

Never miss an article

Get more articles like this in your inbox.