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 the second part of the series, you started making your game, learning about Scene Kit nodes along the way.
In the third part of the series, you learned how to make your geometry move through the power of Scene Kit physics.
In the fourth part of the series, you learned how to make your geometry spawn over time through the Scene Kit render loop.
In this fifth and final part of the series, you’ll add some cool particle systems to the game and wrap it up along the way! :]
Let’s dive back in!
Getting Started
Picture yourself in the movie theatre, popcorn in hand; on the screen, a bad guy from The Fast and the Furious smashes his roaring, high-performance dragster into a highly volatile fuel tanker which explodes in a massive fireball of death and carnage. Yeah! :]
Now think of that same scene, but without the massive fireball explosion: you can almost feel the collective disappointment of audiences around the world. :[
Just like a Hollywood blockbuster, your game needs special effects to amp up the excitement level; these special effects come in the form of what’s known as particle systems. You can use particle systems for a multitude of effects, from moving star fields, to burning rocket boosters, to rain and snow, to metal sparks – and yes, even massive exploding fireballs!
Let’s take a look at how you can add some of these neat special effects to Geometry Fighter.
SCNParticleSystem
In Scene Kit, SCNParticleSystem
manages the creation, animation and removal of particles from the scene graph.
A particle is essentially a small image sprite. The particle system doesn’t add the individual particles into the scene graph itself, so you don’t have direct access to each particle – the particle system manages the particles and their look, size, and location.
However, you can influence the particle system by modifying various properties on it, such as:
- Appearance: Each particle of the system can be rendered as a single image, or an animated sequence of images. You can adjust the size, tint color, blending mode and other rendering parameters of the particles generated.
- Life Span: The system uses a particle emitter, which gives birth to each individual particle. The lifespan of the particle determines how long it stays visible in the scene.
- Emitter behavior: You can control various parameters of the emitter, such as where particles spawn and the spawn rate.
- Variation: Introducing variations into your particle system can make it look more, or less, random.
- Movement: You can adjust how particles move once they’ve spawned. Particles use a simplified physics simulation to speed up performance, but the particles can still interact with objects managed by the physics engine.
Particle System Editor
Before you add a particle system to your game world, you’ll need a group to house this particle system to keep your project organized. Right-click on the GeometryFighter group and select New Group, like so:
Name this new group Particles. Right-click on the group and select New File. Select the iOS\Resource\SceneKit Particle System template and click Next to continue:
On the next screen, select Fire for Particle system template then click Next. Save the file as Trail.scnp and click Create. Once you’re done, you should see the following in your scene:
Hot stuff! :] Say “hello” to Xcode’s built-in particle system editor.
Here’s a quick overview of the various sections of the editor as annotated above:
- Center Stage: The center holds a visual representation of your particle system. You can use this to get an idea of what the end result will look like.
- Gesture Controls: You can use gestures to manipulate the camera view; it’s similar to how you’d move the camera around in a scene.
- Pause/Play Button: You can pause your particle system simulation and inspect it in more detail. While paused, the pause button changes to a play button you can use to resume the simulation.
- Restart Button: This lets you restart your particle simulation from the beginning.
- Camera Reset Button: Use this to reset your camera view to its default position.
- Color Button: This lets you set an appropriate background color for your editor; for example, it’s easier to see snowflakes against a black background.
- Particle System Properties: Selecting the Attributes Inspector reveals a host of properties which you’ll learn about in the next section.
Configuring the Trail Particle System
In this section, you’ll take an in-depth look at the particle system attributes on the right-hand side of the editor. As you go through each section, copy the values of each setting in the screenshots into your own particle system.
Keep an eye on the particle system editor as you change each attribute; you’ll see how each parameter affects the behavior of the particle system. Later on, you’ll use this particle effect in your game to create a trail of particles falling from your spawned objects.
Emitter Attributes
The particle emitter is the origin from where all particles spawn. Here’s the emitter attributes:
-
Birth rate: Controls the emitted rate of particles. Set this to
25
, instructing the particle engine to spawn new particles at a rate of 25 particles per second. -
Warmup duration: The amount of seconds the simulation runs before it renders particles. This can be used to display a screen full of particles at the start, instead of waiting for the particles to fill the screen. Set this to
0
so that simulation can be viewed from the very beginning. - Location: The location, relative to the shape, where the emitter spawns its particles. Set this to Vertex, which means the particles will use the geometry vertices as spawning locations.
- Emission space: The space where emitted particles will reside. Set this to World Space so that the emitted particles are emitted into the world space, and not the local space of the object node itself.
- Direction mode: Controls how spawned particles travel; you can move them all in a constant direction, have them travel radially outwards from the surface of the shape, or simply move them in random directions. Set it to Constant, keeping all emitted particles moving in a constant direction.
-
Direction: Specifies a directional vector to use when direction mode is constant. Set this vector to
(x: 0, y: 0, z:0)
, setting the direction to nothing. -
Spreading angle: Randomizes the emitting angle of spawned particles. Set this to
0º
, thus emitting particles exactly in the previously set direction. -
Initial angle: The initial angle at which to emit particles. Set this to
0º
as this does not matter with a zero direction vector. - Shape: The shape from which to emit particles. Set the shape up as a Sphere, thus using a sphere shape as the geometry.
-
Shape radius: This existence of this attribute depends on which shape you’re using; for an spherical emitter, this determines the size of the sphere. Set this to
0.2
, which defines a sphere just large enough for what you need.
Simulation Attributes
The simulation attributes manage the motion of particles over their lifetimes. This lets you manage their movement without having to use the physics engine:
-
Life span: Specifies the lifetime of a particle in seconds. Set this to
1
, so that a single particle will only exist for a total time of a single second. -
Linear velocity: Specifies the linear velocity of the emitted particles. Set this to
0
, the particles are spawned with no direction or velocity. -
Angular velocity: Specifies the angular velocity of the emitted particles. Set this to
0
, the particles will not be spinning. -
Acceleration: Specifies the force vector applied to the emitted particles. Set this to
(x: 0, y: -5, z: 0)
, which is a downwards vector, simulating a soft gravity effect on the particles once spawned. -
Speed factor: A multiplier that sets the speed of the particle simulation. Set this to
1
, running the simulation at a normal speed. -
Stretch factor: A multiplier that stretches particles in their direction of motion. Set this to
0
, keeping the particle image un-stretched.
Image Attributes
The image attributes control the visual aspects of the particles. They also govern how the appearance of those particles can change over their lifetimes:
- Image: Specifies an image with which each particle will be rendered. Select the CircleParticle.png image, giving the particle its primary shape.
- Color: Sets the tint of the specified image. Set the color to White, giving the particle system a base color of white.
- Animate color: Causes particles to change color over their lifetimes. Un-check this, because the particle color is not going to change at all.
-
Color variation: Adds a bit of randomness to the particle color. You can set this to
(h: 0, s: 0, b: 0, a: 0)
, because the particle color will not vary. -
Size: Specifies the size of the particles. Set this to
0.1
, so that the emitted particles are small in size.
Image Sequence Attributes
To create an animated image for your particles, you arrange each frame of the animation into a grid on a single image (like a sprite sheet in a game). Then you simply use that grid image as the image for your particle emitter. The image sequence attributes let you control the basic animation properties of the particle:
-
Initial frame: Sets the first zero-based frame of the animation sequence. The zeroth frame corresponds to the top left image in the grid. You’re using a single frame image, so set this to
0
. -
Frame rate: Controls the rate of the animation in frames per second. Set this to
0
, it only applies when using an image containing multiple frames. -
Animation: Specifies the behaviour of the animation sequence. Repeat loops the animation, Clamp only plays once, and Auto Reverse plays from the start to the end, then back again. You can leave this on
Repeat
, it doesn’t matter when using a single frame image. -
Dimensions: Specifies the number of rows and columns in the animation grid. Set this to
(Rows: 1, Columns: 1)
, because you’re using a single frame image.
Rendering Attributes
The rendering attributes define how the render phase treats the particles:
- Blending: Specifies the blend mode of the renderer when drawing the particles into the scene. Set this to Alpha, which will use the image alpha channel information for transparency.
- Orientation: Controls the rotation of the particles. Set this to Billboard screen-aligned, which will keep the flat particles facing the camera view at all times, so you won’t notice that the particles are indeed flat images.
- Sorting: Sets the rendering order of the particles. This property works in conjunction with the blend mode and affects how the blending is applied. Set this to None, the particle system will not make use of sorting.
- Lighting: Controls whether Scene Kit applies lighting to the particles. Un-check this, so that the particle system ignores any lights in the scene.
Physics Attributes
The physics attributes let you specify how particles behave in the physics simulation:
- Affected by gravity: Causes the scene’s gravity to affect the particles. Un-check this, you don’t want the particle system to participate in the physics simulation.
- Affected by physics fields: Causes physics fields within the scene to affect the particles. Un-check this, you don’t want physics fields to have an effect on the particles.
- Die on Collision: Lets physics bodies in your scene collide and destroy the particles. Un-check this, you don’t want particles to be removed when they collide with node objects in the scene.
- Physics Properties: Basic physics properties that control the physics behaviour of the particles during the physics simulation. You can leave all these at their default values, because the particle system will not make use of this.
Life Cycle Attributes
The life cycle attributes let you control the overall life cycle of your particle system:
-
Emission Duration: Controls the length of time that the emitter will emit new particles. Set this to
1
, which will activate the particle emitter for a total length of 1 second. -
Idle Duration: Looping particle systems emit particles for the specified emission duration, then become idle for the specified idle duration, after which the cycle repeats. Set this to
0
, the particle system will only emit once. - Looping: Specifies whether the particle system emits particles once, as in an explosion, or continously, as a volcano would. Set this to Loops continuously, so that the emitter emits for as long as it can before it is removed from the scene again.
Phew! There are a lot of attributes to consider for a single particle system, but this gives you a lot of control to get the exact special effect you’re looking for.
If you diligently copied over the values from the screenshots to your particle system, you will have a system that represents the following effect:
If yours doesn’t look like this, try rotating the camera. It might also help to change the background color to a dark blue like you see here to make the particle system easier to see.
Adding the Trail Particles
It’s finally time to add the cool particle effect to your game. Add the following to your GameViewController.swift class:
// 1 func createTrail(color: UIColor, geometry: SCNGeometry) -> SCNParticleSystem { // 2 let trail = SCNParticleSystem(named: "Trail.scnp", inDirectory: nil)! // 3 trail.particleColor = color // 4 trail.emitterShape = geometry // 5 return trail } |
Here’s what’s going on above:
-
This defines
createTrail(_: geometry:)
which takes incolor
andgeometry
parameters to set up the particle system. - This loads the particle system from the file you created earlier.
- Next, you modify the particle’s tint color based on the parameter passed in.
-
This uses the
geometry
parameter to specify the emitter’s shape. - Finally, this returns the newly created particle system.
This method helps you create instances of SCNParticleSystem
, but you still need to add the particle systems to your spawned shape objects.
Note that createTrail(_: geometry:)
takes in a color parameter and uses it to tint the particles. You will set the color of the particle system to be the same as the color of the shape.
Find the line in spawnShape()
where you set the shape’s material diffuse contents and split it up so that the random color is stored in a constant like so:
let color = UIColor.random() geometry.materials.first?.diffuse.contents = color |
Next, add the following lines further down in spawnShape()
, just after you apply a force to the physics body of geometryNode
:
let trailEmitter = createTrail(color, geometry: geometry) geometryNode.addParticleSystem(trailEmitter) |
This uses createTrail(_: geometry:)
to create a particle system and attach it to geometryNode
.
Build and run; take a look at your hard work in action!
Woot! :] It looks great – but what would really make this shine is a heads-up display.
Adding a Heads-up Display
In this short section you’ll use the Game Utils to quickly add a little heads-up display to show your player’s remaining lives, best score, and current score. The code behind the scenes uses a Sprite Kit label and uses the output of the label as a texture for a plane.
Add the following new property to GameViewController.swift, right below spawnTime
:
var game = GameHelper.sharedInstance |
This lets you quickly access the GameHelper
shared instance, which contains a set of methods to do the heavy lifting for you.
Add the following method to the bottom of GameViewController
, below createTrail()
:
func setupHUD() { game.hudNode.position = SCNVector3(x: 0.0, y: 10.0, z: 0.0) scnScene.rootNode.addChildNode(game.hudNode) } |
Here you make use of game.hudNode
from the helper library. You set its the HUD node’s position and add it to the scene.
Next, you need to call setupHUD()
from somewhere. Add the following line to the bottom of viewDidLoad()
:
setupHUD() |
Now that you have a heads-up display, you need to keep it up to date. Add the following call to game.updateHUD()
to the bottom of renderer(_: updateAtTime:)
game.updateHUD() |
Build and run; you’ll see your display at the top of the screen as shown below:
Your game now has a nifty little HUD with a life counter, the high score and the current score.
Okay, the heads-up display is nice, but it’s high time to add some interaction to your game.
Adding Touch Handling
As is usually the case, enabling touch in your app isn’t as straightforward as one would hope.
The first step is to understand how touch handling works in 3D. The image below shows a touch point in a side view of your scene and how Scene kit translates that touch point into your 3D scene to determine which object you’re touching:
So what steps do you take to handle the user’s touch event?
- Get Touch Location. First, you need to get the location of the user’s touch on the screen.
-
Convert to View Coordinates. After that, you need to translate that touch location to a location relative to the
SCNView
instance that’s presenting the scene. - Fire a Ray for a Hit Test. Once you’ve established a touch location relative to the view, Scene Kit can perform a hit test for you by firing off a ray (No, not that Ray! :]) into your scene and returning a list of objects that intersect with the ray.
Naming Nodes
Before you can activate the touch ray of death, you need a way to identify each spawned object. The simplest approach is to give them names.
Add following to spawnShape()
, right after you add the particle system to geometryNode
:
if color == UIColor.blackColor() { geometryNode.name = "BAD" } else { geometryNode.name = "GOOD" } |
True to the spirit of the black-hatted villains of old Western movies, you assign the moniker "BAD"
to black-colored objects and "GOOD"
to all others.
Adding a Touch Handler
Next you need to write a method that you will later call when you detect that the user has tapped a particular node.
Add the following method to the bottom of GameViewController
, below setupHUD()
:
func handleTouchFor(node: SCNNode) { if node.name == "GOOD" { game.score += 1 node.removeFromParentNode() } else if node.name == "BAD" { game.lives -= 1 node.removeFromParentNode() } } |
This method checks the moniker of the touched node; good nodes increase the score and bad (black) nodes reduce the number of lives by one. In either case, you remove the node from the screen as it’s destroyed.
Using the Touch Handler
To capture the user’s touches, you’ll use touchesBegan(_: withEvent:)
, which is called every time the player touches the screen.
To implement your own version, add the following to GameViewController
, right below handleTouchFor(_:)
:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) { // 1 let touch = touches.first! // 2 let location = touch.locationInView(scnView) // 3 let hitResults = scnView.hitTest(location, options: nil) // 4 if hitResults.count > 0 { // 5 let result = hitResults.first! // 6 handleTouchFor(result.node) } } |
Taking each numbered comment in turn:
- Grab the first available touch.
-
Translate the touch location to a location relative to the coordinates of
scnView
. -
hitTest(_: options:)
gives you an array ofSCNHitTestResult
objects that represent any intersections that hit ray starting from the spot inside the view that the user touched, and going away from the camera. - Check if there are any results from the hit test.
- If there are, take the first result in the set.
- Finally, you pass the first result node to your touch handler, which will either increase your score – or cost you a life!
One final step. You don’t need the camera control anymore so change the line in setupView()
as follows:
scnView.allowsCameraControl = false |
Build and run; get ready to unleash your deadly finger of annihilation! :]
Tap on the spawned objects and make them disintegrate into thin air. Whoo-hoo! :]
Challenge
Time to up the cool factor – and what’s cooler than explosions? I know: nothing, right?
That brings you to the challenge of this chapter: create another particle system and name it Explode.scnp. See if you can figure out what attributes to modify to make those particles explode.
The effect should look something similar to this:
You can use the following image as a starting point for your particle system:
Shaping Particle Explosions
Now that you’ve created the explosion particle system, you need to add some code to make those nodes explode. You’re going to use some special properties to make the explosion take the same shape as whatever node you touch.
Add the following to the bottom of GameViewController
, below touchesBegan(_: withEvent)
:
// 1 func createExplosion(geometry: SCNGeometry, position: SCNVector3, rotation: SCNVector4) { // 2 let explosion = SCNParticleSystem(named: "Explode.scnp", inDirectory: nil)! explosion.emitterShape = geometry explosion.birthLocation = .Surface // 3 let rotationMatrix = SCNMatrix4MakeRotation(rotation.w, rotation.x, rotation.y, rotation.z) let translationMatrix = SCNMatrix4MakeTranslation(position.x, position.y, position.z) let transformMatrix = SCNMatrix4Mult(rotationMatrix, translationMatrix) // 4 scnScene.addParticleSystem(explosion, withTransform: transformMatrix) } |
Here’s the play-by-play of the above code:
-
createExplosion(_: position: rotation:)
takes three parameters:geometry
defines the shape of the particle effect, whileposition
androtation
help place the explosion into the scene. -
This loads Explode.scnp and uses it to create an emitter. The emitter uses
geometry
asemitterShape
so that particles will emit from the surface of the shape. -
Enter the Matrix! :] Don’t be scared by these three lines; they simply provide a combined rotation and position (or translation) transformation matrix to
addParticleSystem(_: withTransform:)
. -
Finally you call
addParticleSystem(_: wtihTransform)
onscnScene
to add the explosion to the scene.
You’re so close to replicating those great Hollywood explosions! Add the following line twice inside handleTouchFor(_:)
, once to the “good” if
block and once to the “bad” else
block, right before you remove node
from the parent:
createExplosion(node.geometry!, position: node.presentationNode.position, rotation: node.presentationNode.rotation) |
This uses the presentationNode
property to retrieve the position
and rotation
parameters of node
. You then call createExplosion(_: position: rotation:)
with those parameters.
presentationNode
because the physics simulation is currently moving the node.
Build and run; tap away and make those nodes explode!
Juice
Congratulations! At this point you have completed your first Scene Kit game.
However, there’s still plenty room for improvement, right? To push your game to that next level, you absolutely have to add something known as juice. Juice will give your game that little something special, just to make it stand out above the rest.
Here’s a few ideas that will definitely juice things up:
- Game state management. With basic game state management you’ll be able to control certain game machanics based on a game state like TapToPlay, Playing or GameOver.
- Splash screens. Use pretty splash screens. They provide the player with visual clues of the current game state.
- Sound effects. Add cool sound effects to provide the player with crucial audio feedback of good and bad interaction with the game elements.
- Camera shakes. Really bad explosions produce really big shockwaves. Adding a shaking camera effect just that little something extra.
Where To Go From Here?
Here is the example code from this Scene Kit tutorial with Swift (with the juice applied).
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!
The post Scene Kit Tutorial with Swift Part 5: Particle Systems appeared first on Ray Wenderlich.