Our Biggest Cyber Monday Sale — Ever!

Introducing unlimited access to all courses, all books, and our new monthly live professional development series! Just $899 $399 per year during our Cyber Monday event

Ends in... ::
Home

Scene Kit Tutorial with Swift Part 2: Nodes

In this 5-part Scene Kit tutorial series, you’ll learn how to make your first 3D iOS game: a game like Fruit Ninja called Geometry Fighter!

5/5 3 Ratings
Note: This is an abbreviated chapter from the 3D iOS Games by Tutorials, to give you a sneak peek of what’s inside the book, released as part of 3D iOS Games by Tutorials Week. We hope you enjoy!

Thumb

Welcome back to our Scene Kit Tutorial with Swift series!

This tutorial series will show you how to create your first game with Scene Kit, Apple’s built-in 3D game framework.

In the first part of the series, you learned how to make an empty Scene Kit project as a good starting point.

In this second part of the series, you’ll get started making your game, learning about Scene Kit nodes along the way.

Let’s dive back in!

Note: This tutorial begins where the previous tutorial left off. If you didn’t follow along, no sweat – you can simply use the starter project for this tutorial.

Getting Started

Sprite Kit organizes the components of your game into a hierarchy known as the scene graph.

Each element of your game (such as lights, cameras, geometry, or particle emitters) is called a node and is stored in this tree-like structure.

To illustrate how this works, think back to a nursery rhyme you might have heard in your childhood…

🎵 The hip bone’s connected to the back bone 🎵 The back bone’s connected to the shoulder bone… 🎵

You’re right, it’s the classic song Dem Dry Bones! Bonus points if you can think of a classic video game that makes particularly good use of this ;]

With those lyrics in mind, take a look at the following anatomically-correct structure of a rare four-fingered skeleton:

Skeletons

To help illustrate how you could construct a node-based hierarchy from this skeleton, think of each bone in the skeleton as a node.

As the song points out, the shoulder bone’s connected to the back bone. So consider the back bone as the parent node of the shoulder bone, and the shoulder bone as the child node of the back bone.

To add the shoulder bone to the scene, you add it as a child of the back bone. You can continue to construct the whole arm in this way, adding child bones to parent bones, right up to the little pinky.

To position a bone, you position it relative to its parent. For example, to wave the skeleton’s left arm, you simply rotate the shoulder node back and forth as indicated by the little blue arrow. All child nodes of the shoulder node will rotate along with their parent.

Congratulations! You just passed skeleton anatomy 101! :]

Node in Code

From a technical perspective, a single node is represented by the SCNNode class and represents a position in 3D space relative to its parent node. A node on its own has no visible content and is invisible when rendered as part of a scene. To create visible content, you have to add other components such as lights, cameras or geometries (such as bones) to the node.

The scene graph contains a special node which forms the foundation of your node-based hierarchy: the root node. To construct your scene, you add your nodes either as child nodes of the root node or as a child of one of the root node’s descendants.

Scene Kit Asset Catalog

Once you’re a successful and rich 3D game designer, you’ll have enough money to hire your very own graphics artist and sound engineer, which will free you up to focus on the game code alone. :] The Scene Kit asset catalog has been designed specifically to help you manage your game assets separately from the code.

An asset catalog lets you manage your game assets in a single folder; to use it, simply add a folder with the .scnassets extension to your project and save all your game assets in that folder. Xcode will copy everything in your catalog to your app bundle at build time. Xcode preserves your assets folder hierarchy; this gives you full control over the folder structure.

By sharing your assets folder with your artists, they can quickly fix any issues, such as a not-quite-so-scary cross-eyed monster and have it ready for the next build – without having to copy the changed assets back into the project.

Now that you understand what the asset catalog is all about, you’ll add one to Geometry Fighter.

Drag and drop the GeometryFighter.scnassets folder from the tutorial resources into your game project in Xcode. In the popup that appears, make sure that Copy items if needed, Create Groups, and your GeometryFighter target are all checked, and click Finish.

CopySCNAssetsFolder0

Select the GeometryFighter.scnassets folder in your Project Navigator and note you can see some settings unique to the asset catalog in the right hand pane. Expand the GeometryFighter.scnassets folder and sub-folders to see more detail about your assets:

CopySCNAssetsFolder1

There are two folders inside the asset catalog: the Sounds folder contains all the sound assets you’ll need for your game, while the Textures folder contains all the images you’ll need. Feel free to take a sneak peek of what’s inside.

Adding the Launch Screen

Now that you’ve imported the asset catalog, you’ll take care of some basic housekeeping steps and add a proper image to the launch screen.

First, click Assets.xcassets in the project navigator. Drag and drop GeometryFighter.scnassets\Textures\Logo_Diffuse.png into the assets, below the AppIcon.

AddLogoToXCAssets

Next, click LaunchScreen.storyboard in the project navigator. Select the main view and set the Background property to a dark blue (or some other color you like):

SetViewBackgroundColor

Next, drag the Logo_Diffuse image from the Media Library into the center of the view. Set the Mode property of your new image to Aspect Fit:

AddLogoToView

You’re almost done with your launch screen; all that’s left is to add some constraints so that the splash image will work on all devices. Click the Pin button at the bottom, toggle the constraints on for all four edges and click Add 4 Constraints as shown below:

AddConstraints0

You’re done setting up your launch screen! Build and run your app; you’ll see your shiny new launch screen appear:

BuildAndRun0

Adding a Background image

Once your splash screen disappears, you’re dumped back to the black screen of opportunity. Time to add a nice clean background so you don’t feel like you’re staring into a black hole.

To do this, add the following line of code to the bottom of setupScene() in GameViewController.swift:

scnScene.background.contents = "GeometryFighter.scnassets/Textures/Background_Diffuse.png"

This line of code instructs the scene to load the Background_Diffuse.png image from the asset catalog and use it as the material property of the scene’s background.

Build and run; you should now see a blue background image once the game starts:

BuildAndRun1

You’ve finished all the basic housekeeping tasks for your project. Your game now has a flashy app icon, a splash screen, and a pretty background that’s all ready to display the nodes you’re about to add to the scene.

The Scene Kit Coordinate System

Before you can start adding nodes to the scene, you first need to understand how Scene Kit’s coordinate system works so you can position your nodes where you want them.

In a 2D system such as UIKit or Sprite Kit, you use a point to describe the position of a view or a sprite on the x and y axes. To place an object in 3D space, you also need to describe the depth of the object’s position on the z-axis.

Consider the following simple illustration:

CoordinateSystem

Scene Kit uses this three-axis system to represent position in 3D space. The red blocks are placed along the x-axis, the green blocks along the y-axis and the blue blocks along the z-axis. The grey cube in the very center of the axes indicates the origin, which has coordinates of (x:0, y:0, z:0).

Scene Kit uses the SCNVector3 data type to represent coordinates in three dimensions as a three-component vector. Here’s how you create a vector in code:

let position = SCNVector3(x: 0, y: 5, z: 10)

This declares the variable position with a vector of (x:0, y:5, z:10). You can easily access individual properties of the vector like so:

let x = position.x
let y = position.y
let z = position.z

If you’ve worked with CGPoint before, you can easily draw comparisons between it and SCNVector3.

Note: Nodes added to the scene have have a default position of (x:0, y:0, z:0), which is always relative to the parent node. To place a node at the desired location, you need to adjust the position of the node relative to its parent (local coordinates) – not the origin (world coordinates).

Working with Cameras

Now that you understand how to position nodes in Scene Kit, you’re probably wondering how to actually display something onscreen. Think back to the analogy of the movie set from Chapter 1: to shoot a scene, you’d position a camera looking at the scene and the resulting image of that scene would be from the camera’s perspective.

Scene Kit works in a similar fashion; the position of the node that contains the camera determines the point of view from which you view the scene.

The following illustration demonstrates how a camera works in Scene Kit:

CameraNode

There are a couple of key points in the previous diagram:

  • The camera’s direction of view is always along the negative z-axis of the node that contains the camera.
  • The field of view is the limiting angle of the viewable area of your camera. A tight angle provides a narrow view, while a wide angle provides a wide view.
  • The viewing frustum determines the visible depth of your camera. Anything outside this area – that is, too close or too far from the camera – will be clipped and won’t appear on the screen.

A Scene Kit camera is represented by SCNCamera, whose xPov and yPov properties let you adjust the field of view, while zNear and zFar let you adjust the viewing frustum.

One key point to remember is that a camera by itself won’t do anything unless it’s a part of the node hierarchy.

Adding the Camera

Let’s try this out. Open GameViewController.swift and add the following property below scnScene:

var cameraNode: SCNNode!

Next, add the following method below setupScene():

func setupCamera() {
  // 1
  cameraNode = SCNNode()
  // 2
  cameraNode.camera = SCNCamera()
  // 3
  cameraNode.position = SCNVector3(x: 0, y: 0, z: 10)
  // 4
  scnScene.rootNode.addChildNode(cameraNode)
}

Taking a closer look at the code:

  1. You first create an empty SCNNode and assign it to cameraNode.
  2. You next create a new SCNCamera object and assign it to the camera property of cameraNode.
  3. Then you set the position of the camera at (x:0, y:0, z:10).
  4. Finally, you add cameraNode to the scene as a child node of the scene’s root node.

Finish things off by calling the method you just added in viewDidLoad(), right below setupScene():

setupCamera()

There’s no need to build and run at this point in time, as you won’t see that anything’s changed. Even though your scene now has a camera, there’s still nothing to look at. To fix this, let’s add some actors to this scene.

Working with Geometry

In order to create visible content, you need to add a geometry object to a node. A geometry object represents a three-dimensional shape and is created of many points known as vertices.

Additionally, a geometry object can contain material objects that modify the appearance of a geometry’s surface. Materials let you specify information such as the color and texture of the geometry’s surface and how the geometry should respond to light along with other visual effects. A collection of vertices and materials is known as a model or a mesh.

Scene Kit includes the following built-in geometric shapes, also known as primitives:

PrimitiveGeometry

In the front row from the left, you have a cone, a torus, a capsule and a tube. In the back row from the left, you have a pyramid, a box, a sphere and a cylinder.

Note: You can provide your own custom geometry data, but you’ll cover this in later chapters.

Adding ShapeTypes

Before you start adding geometric shapes to the scene, create a new Swift file to define a ShapeType enum for the various different shapes you’ll use in the game.

Right-click on the GeometryFighter group and select New File…. Select the iOS\Source\Swift File template and click Next:

AddNewSwiftFile0

Name the file ShapeType.swift, make sure it’s included in your project, then click Create.

Once the file’s been created, open ShapeType.swift and replace its contents with the following:

import Foundation

// 1
public enum ShapeType:Int {

  case Box = 0
  case Sphere
  case Pyramid
  case Torus
  case Capsule
  case Cylinder
  case Cone
  case Tube

  // 2
  static func random() -> ShapeType {
    let maxValue = Tube.rawValue
    let rand = arc4random_uniform(UInt32(maxValue+1))
    return ShapeType(rawValue: Int(rand))!
  }
}

The code above is relatively straightforward:

  1. You create a new public enum ShapeType that enumerates the various shapes.
  2. You also define a static method random() that generates a random ShapeType. This feature will come in handy later on in your game.

Adding a Geometry Node

Your next task is to create a method that spawns the various random shapes defined in the ShapeType enumerator.

Add the following method to GameViewController.swift, right below setupCamera():

func spawnShape() {
  // 1
  var geometry:SCNGeometry
  // 2
  switch ShapeType.random() {
  default:
    // 3
    geometry = SCNBox(width: 1.0, height: 1.0, length: 1.0, chamferRadius: 0.0)
  }  
  // 4
  let geometryNode = SCNNode(geometry: geometry)
  // 5
  scnScene.rootNode.addChildNode(geometryNode)
}

Taking each numbered comment in turn;

  1. First you create a placeholder geometry variable for use a bit later on.
  2. Next, you define a switch statement to handle the returned shape from ShapeType.random(). It’s incomplete at the moment and only creates a box shape; you’ll add more to it in the challenge at the end of this chapter.
  3. You then create an SCNBox object and store it in geometry. You specify the width, height, and length, along with the chamfer radius (which is a fancy way of saying rounded corners).
  4. This statement creates an instance of SCNNode named geometryNode. This time, you make use of the SCNNode initializer that takes a geometry parameter to create a node and automatically attach the supplied geometry.
  5. Finally, you add the node as a child of the scene’s root node.

You now need to call this method from somewhere. Add the following line to viewDidLoad() below setupCamera():

spawnShape()

Build and run; you’ll see a white square displayed onscreen:

BuildAndRun2

There’s a few things to observe here:

  • The box node is the default shape from spawnShape(), and sits at (x:0, y:0, z:0) in the scene.
  • You’re viewing the scene through your cameraNode. Since the camera node lives at (x:0, y:0: z:10), the box is smack dab in the center of the camera’s viewable area.

Yes, it’s not very exciting (and hardly three-dimensional looking), but fear not – the next section will change all that.

Using Built-in View Features

SCNView comes with a few out-of-the-box features that help make your life easier.

Add the following lines to setupView() in GameViewController.swift, just below the current implementation:

// 1
scnView.showsStatistics = true
// 2
scnView.allowsCameraControl = true
// 3
scnView.autoenablesDefaultLighting = true

Here’s what’s going on in the code above:

  1. showStatistics enables a real-time statistics panel at the bottom of your scene.
  2. allowsCameraControl lets you manually control the active camera through simple gestures.
  3. autoenablesDefaultLighting creates a generic omnidirectional light in your scene so you don’t have to worry about adding your own light sources for the moment.

Build and run; things should look a little more exciting this time around!

BuildAndRun3

You can use the following gestures to control the active camera in your scene:

  • Single finger swipe: Rotates your active camera around the contents of the scene.
  • Two finger swipe: Moves, or pans your camera left, right, up or down in the scene.
  • Two finger pinch: Zooms the camera in and out of the scene.
  • Double-tap: If you have more than one camera, this switches between the cameras in your scene. Of course since you have only one camera this won’t don that. However, it also has the effect of resetting the camera to its original position and settings.

Challenge

At this point, I’ll leave you off with a challenge to practice what you learned: to improve the switch statement inside spawnShape() to handle the remaining shapes in the enumerator.

Use Apple’s official Scene Kit documentation as a guide to the various geometric shapes. Also take a look at the ShapeType enum to see which shapes are left to create; their names should give you a good idea of where to start.

Don’t worry too much about the sizes to use; just try to make them about the same relative size as the box you made earlier.

If you get this working, congratulations, you are getting a firm grasp of some of the most fundamental concepts in Scene Kit! :]

Where To Go From Here?

Here is the example code from this Scene Kit tutorial with Swift (with the challenge completed).

At this point, you should keep reading to the third part of this tutorial series, where you’ll learn how to make the geometry move via Scene Kit physics.

If you’d like to learn more, you should check out our book 3D iOS Games by Tutorials. The book teaches you everything you need to know to make 3D iOS games, by making a series of mini-games like this one, including a games like Breakout, Marble Madness, and even Crossy Road.

In the meantime, if you have any questions or comments about this tutorial, please join the forum discussion below!

More like this

Contributors

Comments