Home iOS & Swift Books Apple Augmented Reality by Tutorials

14
Raycasting & Physics Written by Chris Language

In this chapter, you’ll pick up from where you left off in the previous one. Your AR-based SpriteKit game is coming along well, and you’ve laid a lot of the groundwork already. Your goal now is to add all the missing pieces and finishing touches.

Take a moment to take stock of what you’ve done and what’s up next.

What’s done?

  • Game State: The game has basic game states in place, and you can easily switch from one state to another. This lets you control your code based on the current state of the game.

  • Spawn Point: When the player taps the screen, the game adds an AR anchor in the camera’s view. A tiny box that acts as the spawning point for the emojis also appears.

  • Error Handling: Your game is robust enough to handle whatever the real world can throw at it. It informs the player of any tracking issues and, most importantly, it can recover from an interruption.

What’s next?

  • Spawning Emojis: With the spawn point in place, you’ll spawn multiple emojis at this location.

  • Running Actions: To add some polish, you’ll run some custom actions on the emojis to play sound effects, scale and run additional code.

  • Enabling Physics: You’ll enable physics so the emojis participate in the physics simulation. This gives each emoji a physical shape and applies forces like gravity to it.

  • Applying Forces: You’ll use physically-based animation to apply forces to the emojis, shooting them out from the spawning point into the world, then letting gravity pull them back to earth.

  • 2D Raycasting: You’ll use 2D raycasting to check if the player touches any of the spawned emojis to save them from certain death.

Now that you know what’s next, it’s time to get cracking!

Note: There’s a copy of the final project from the previous chapter available in starter/EmojiPop.

Spawning emojis

Your first step is to get the emojis to spawn. You’ll use the spawn point as the parent node to spawn the new emojis. This ensures the emojis spawn in the player’s view.

Start by creating a helper function that spawns a single emoji. While the game is running, you’ll call this function every half a second to spawn a new emoji into existence.

Open Scene.swift, then add the following function to Scene:

func spawnEmoji() {  
  // 1
  let emojiNode = SKLabelNode(
    text:String(emojis.randomElement()!))
  emojiNode.name = "Emoji"
  emojiNode.horizontalAlignmentMode = .center
  emojiNode.verticalAlignmentMode = .center
  // 2
  guard let sceneView = self.view as? ARSKView else { return }
  let spawnNode = sceneView.scene?.childNode(
    withName: "SpawnPoint")
  spawnNode?.addChild(emojiNode)
}

This defines a function named spawnEmoji() whose main responsibility is spawning a single emoji.

Take a closer look at what it’s doing:

  1. Creates a new SKLabelNode using a random emoji character from the string of emojis available in emojis. The node is named Emoji and it’s centered vertically and horizontally.

  2. Interrogates the available node in scene, looking for the node named SpawnPoint. It then adds the newly-created emoji as a child of spawnNode. This places the emoji into the scene.

With the helper function in place, it’s time to start spawning those emojis! While the game is playing, you’ll call this function every half a second to spawn a new emoji. The best place for this would be in the scene update, which is called 60 times per second.

Add the following to update(_:):

// 1
if gameState != .Playing { return }
// 2
if spawnTime == 0 { spawnTime = currentTime + 3 }
// 3   
if spawnTime < currentTime {
  spawnEmoji()
  spawnTime = currentTime + 0.5;
}
//4     
updateHUD("SCORE: " + String(score) + 
  " | LIVES: " + String(lives))

Here’s how this breaks down:

  1. You only want to update the game while it’s in the Playing state.
  2. If spawnTime is 0, the game just started so you give the player a few seconds to prepare for the onslaught of emojis that are about to spawn. This creates a slight delay of 3 seconds before the first emoji spawns.
  3. Once spawnTime is less than currentTime, it’s time to spawn a new emoji. Once spawned, you reset spawnTime to wait for another half a second before spawning the next emoji.
  4. Finally, you update the HUD with the current score and available lives.

Great, you’re finally spawning emojis! You’re welcome to do a quick build and run to test things out, but prepare to be underwhelmed.

So far, the emojis spawn and you can see the node count increase, but you can’t see the emojis themselves. That’s because they’re hiding behind the spawn point.

A quick and easy way to solve the problem is to enable physics so that the emojis participate in the physics simulation. Once spawned, gravity will pull the emojis toward the ground.

Enabling physics

SpriteKit comes with a very powerful 2D physics engine. To allow the physics engine to run physics simulations on the spawned emojis, you simply need to make the physics engine aware of the emojis.

Physics body types

One of the key properties you must specify when creating a physics body is its type. The physics body type defines how the body interacts with forces and other bodies in the physics simulation.

Physics shapes

In addition to the type, shape is another important property you must specify when creating a physics body. This defines the 2D shape the physics engine uses to detect collisions.

Enabling physics

In SpriteKit, all physics bodies are SKPhysicsBody objects. Once you create a physics body, you assign it to the physicsBody property of the SKNode.

// Enable Physics
emojiNode.physicsBody = SKPhysicsBody(circleOfRadius: 15)
emojiNode.physicsBody?.mass = 0.01

Force

In real life, when you want to make a ball move, you have to apply a force to it — by kicking it, for example. Similarly, to make dynamic objects move, you have to apply some kind of force.

Adding some randomness

Adding some randomness to the gameplay will make the game more challenging and increase the replay value. Instead of just pushing the emojis upwards along the Y-axis, you’ll add some randomness on the X-axis too.

func randomCGFloat() -> CGFloat {
  return CGFloat(Float(arc4random()) / Float(UINT32_MAX))
}

Applying an impulse

Instead of applying a constant force, you’ll apply the force as an impulse. Gravity, for example, is a constant force, whereas a kick is an impulse.

// Add Impulse
emojiNode.physicsBody?.applyImpulse(CGVector(
  dx: -5 + 10 * randomCGFloat(), dy: 10))

Torque

Torque is another type of force that you can apply to physics bodies — a rotational force. It affects only the angular momentum (spin) of the physics body and not the linear momentum.

Applying torque

You can use applyTorque(), which is available on the physicsBody, to apply torque to a SKNode.

// Add Torque
emojiNode.physicsBody?.applyTorque(-0.2 + 0.4 * randomCGFloat())

Actions

Actions allow you to perform basic animations to manipulate a node’s position, scale, rotation and opacity within a scene. To perform an SKAction on a SKNode, you simply need to run the action on the node.

Sequence & group actions

You can run only run a single SKAction on a SKNode at a time, but there are two special types of actions you can use to run multiple actions in a sequence or in a group.

Adding sound files

In the next section, you’re going to use a play sound action to add some fun noises to your game. To do this, you’ll need to add a few sound files to your project.

Running actions

Now that you know all about actions, you’ll bring the game to life by adding some actions to it.

// 1
let spawnSoundAction = SKAction.playSoundFileNamed(
      "SoundEffects/Spawn.wav", waitForCompletion: false)
let dieSoundAction = SKAction.playSoundFileNamed(
      "SoundEffects/Die.wav", waitForCompletion: false)
let waitAction = SKAction.wait(forDuration: 3)
let removeAction = SKAction.removeFromParent()
// 2
let runAction = SKAction.run({
  self.lives -= 1
  if self.lives <= 0 {
    self.stopGame()
  }
})
// 3
let sequenceAction = SKAction.sequence(
  [spawnSoundAction, waitAction, dieSoundAction, runAction,
    removeAction])    
emojiNode.run(sequenceAction)

Understanding 2D raycasting

The poor emojis, there’s no way to save them right now. That’s just so sadistically… satisfying! :]

Handling touches

Next, you’ll add touch functionality to the game so the player can save those poor emojis.

func checkTouches(_ touches: Set<UITouch>) {
  // 1
  guard let touch = touches.first else { return }
  let touchLocation = touch.location(in: self)
  let touchedNode = self.atPoint(touchLocation)
  // 2
  if touchedNode.name != "Emoji" { return }
  score += 1
  // 3     
  let collectSoundAction = SKAction.playSoundFileNamed(
      "SoundEffects/Collect.wav", waitForCompletion: false)
  let removeAction = SKAction.removeFromParent()
  let sequenceAction = SKAction.sequence(
    [collectSoundAction, removeAction]) 
  touchedNode.run(sequenceAction)
}
checkTouches(touches)

Adding finishing touches

When the game starts, the spawn point just pops into view. This feels a bit abrupt and unpolished. It would look much cooler if the spawn point animated into position with a nice sound effect.

boxNode.setScale(0)
let startSoundAction = SKAction.playSoundFileNamed(
  "SoundEffects/GameStart.wav", waitForCompletion: false)
let scaleInAction = SKAction.scale(to: 1.5, duration: 0.8)
boxNode.run(SKAction.sequence(
  [startSoundAction, scaleInAction]))

Key points

Congratulations, you’ve reached the end of the chapter and section.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.

Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:

© 2020 Razeware LLC

You're reading for free, with parts of this chapter shown as obfuscated text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.