Configure the Physics Bodies

In Chapter 8, Using the Scene Editor to Add Physics, you used the Scene Editor to add a physics body to the player node. However, as you start to build out Val’s Revenge, keeping track of the physics categories can (and will) get a little tricky. While it’s entirely possible to use only the Scene Editor, setting up the physics bodies like you did for the player node, you have another option: use components and extensions.

Unlike the physics code you wrote in Configure Physics Categories, where you simply set up a single enum to hold the physics categories using a UInt32 type, you’ll instead expand that idea and use a combination of String enums and protocols. You’ll also create and use a new physics component to set up your physics bodies.

To make this solution more robust, you won’t handle the collisions directly in the GameScene class; instead, you’ll build out a separate GameScene extension to further keep your project code organized.

There’s a lot to do. Let’s start with setting up the physics categories.

Mapping and Designing Your Physics Categories

Before you start writing physics-related code—well, any code, really—it’s always nice to have a plan. The plan, here, is to use seven physics categories, like so:

images/UsingStatesAndStateMachines/physics-categories.png

Each category represents some “object” within your game—and now that you know what’s what, you’re ready to begin writing some code.

Setting up and Configuring the Physics Categories

Inside the Components group, create a new file (N) using the iOS Swift File template. Name the new file PhysicsComponent.swift and replace its contents with the following code:

 import​ ​SpriteKit
 import​ ​GameplayKit
 
 enum​ ​PhysicsCategory​: ​String​ {
 case​ player
 case​ wall
 case​ door
 case​ monster
 case​ projectile
 case​ collectible
 case​ exit
 }setting up and
 
 enum​ ​PhysicsShape​: ​String​ {
 case​ circle
 case​ rect
 }

Here, you’re setting up two String enums: one for the physics body category, and one for the physics body shape. You’ll use the raw values specified here when setting up your game objects. These values will then be used to determine which category an object belongs to, and what shape to use for its physics body.

For this type of setup to work, you need a new struct that conforms to the OptionSet protocol.[41] OptionSets are well-suited for working with bit masks. You also need the struct to implement the Hashable protocol[42] as is required when working with sets. (Note: The OptionSet and Hashable protocols are quite powerful; if you’re not familiar with them, reading through the Apple documentation may provide some valuable insight.)

Below the PhysicsShape enum, add the following code:

 struct​ ​PhysicsBody​: ​OptionSet​, ​Hashable​ {
 let​ rawValue: ​UInt32
 
 }

This code is the beginning of your PhysicsBody struct—the heart of your physics bodies and collision detection solution. Notice your new struct conforms to both the Hashable and OptionSet protocols.

Inside the new PhysicsBody struct, and below the line that reads let rawValue: UInt32, and the following options:

 static​ ​let​ player = ​PhysicsBody​(rawValue: 1 << 0) ​// 1
 static​ ​let​ wall = ​PhysicsBody​(rawValue: 1 << 1) ​// 2
 static​ ​let​ door = ​PhysicsBody​(rawValue: 1 << 2) ​// 4
 static​ ​let​ monster = ​PhysicsBody​(rawValue: 1 << 3) ​// 8
 static​ ​let​ projectile = ​PhysicsBody​(rawValue: 1 << 4) ​// 16
 static​ ​let​ collectible = ​PhysicsBody​(rawValue: 1 << 5) ​// 32
 static​ ​let​ exit = ​PhysicsBody​(rawValue: 1 << 6) ​// 64

You’ll notice that these options perfectly align with the case statements in the PhysicsCategory struct. While having the case statements and options match isn’t necessarily a requirement, it does make it a little easier when setting up your component.

The next step is to define how the physics categories will interact with one another, in other words, which categories will collide and which will simply participate in contact checking. Below the last option in the list, and still within the PhysicsBody struct, add the following code:

 static​ ​var​ collisions: [​PhysicsBody​: [​PhysicsBody​]] = [
  .player: [.wall, .door],
  .monster: [.wall, .door]
 ]
 
 static​ ​var​ contactTests: [​PhysicsBody​: [​PhysicsBody​]] = [
  .player: [.monster, .collectible, .door, .exit],
  .wall: [.player],
  .door: [.player],
  .monster: [.player, .projectile],
  .projectile: [.monster, .collectible, .wall],
  .collectible: [.player, .projectile],
  .exit: [.player]
 ]

Using the collisions property, you make it so that both the player and the monsters will collide with the walls and doors instead of passing through them. Using the contactTests property, you define which bodies will trigger a message for the physics contact delegate. The next three properties you’re about to add will tie this solution together. Below the contactTests property, add the following code:

 var​ categoryBitMask: ​UInt32​ {
 return​ rawValue
 }
 
 var​ collisionBitMask: ​UInt32​ {
 let​ bitMask = ​PhysicsBody
  .collisions[​self​]?
  .​reduce​(​PhysicsBody​(), { result, physicsBody ​in
 return​ result.​union​(physicsBody)
  })
 
 return​ bitMask?.rawValue ?? 0
 }
 
 var​ contactTestBitMask: ​UInt32​ {
 let​ bitMask = ​PhysicsBody
  .contactTests[​self​]?
  .​reduce​(​PhysicsBody​(), { result, physicsBody ​in
 return​ result.​union​(physicsBody)
  })
 
 return​ bitMask?.rawValue ?? 0
 }

Here, you return the raw value for the category. Because each of the physics bodies will get assigned to only one category, you can keep your code somewhat simple by returning a single value. For the other two properties—contactTestBitMask and collisionBitMask—you use reduce and union to return the values. With reduce, you can reduce an array of values into a single value. You then use union to generate and return the final value.

The final piece of this puzzle is to create the static method for retrieving the correct body. Below the contactTestBitMask property, add the following code:

 static​ ​func​ ​forType​(_ type: ​PhysicsCategory​?) -> ​PhysicsBody​? {
 switch​ type {
 case​ .player:
 return​ ​self​.player
 case​ .wall:
 return​ ​self​.wall
 case​ .door:
 return​ ​self​.door
 case​ .monster:
 return​ ​self​.monster
 case​ .projectile:
 return​ ​self​.projectile
 case​ .collectible:
 return​ ​self​.collectible
 case​ .exit:
 return​ ​self​.exit
 case​ .none:
 break
  }
 
 return​ ​nil
 }

You’ve seen code like this before—this is the static method you’ll use to fetch and return the correct physics body.

At last, you’re ready to create the physics component, which you’ll use to assign physics bodies to your game objects.

Creating the Physics Component

Still inside the PhysicsComponent.swift file, and just below (and outside of) the PhysicsBody struct, add the following code:

 // MARK: - COMPONENT CODE STARTS HERE
 
 class​ ​PhysicsComponent​: ​GKComponent​ {
 
 @GKInspectable​ ​var​ bodyCategory: ​String​ = ​PhysicsCategory​.wall.rawValue
 @GKInspectable​ ​var​ bodyShape: ​String​ = ​PhysicsShape​.circle.rawValue
 
 override​ ​func​ ​didAddToEntity​() {
 guard​ ​let​ bodyCategory =
 PhysicsBody​.​forType​(​PhysicsCategory​(rawValue: bodyCategory)),
 let​ sprite = componentNode ​as?​ ​SKSpriteNode​ ​else​ {
 return
  }
 
 let​ size: ​CGSize​ = sprite.size
 
 if​ bodyShape == ​PhysicsShape​.rect.rawValue {
  componentNode.physicsBody = ​SKPhysicsBody​(rectangleOf: size)
  } ​else​ ​if​ bodyShape == ​PhysicsShape​.circle.rawValue {
  componentNode.physicsBody = ​SKPhysicsBody​(circleOfRadius: size.height/2)
  }
 
  componentNode.physicsBody?.categoryBitMask =
  bodyCategory.categoryBitMask
  componentNode.physicsBody?.collisionBitMask =
  bodyCategory.collisionBitMask
  componentNode.physicsBody?.contactTestBitMask =
  bodyCategory.contactTestBitMask
 
  componentNode.physicsBody?.affectedByGravity = ​false
  componentNode.physicsBody?.allowsRotation = ​false
  }
 
 override​ ​class​ ​var​ supportsSecureCoding: ​Bool​ {
 true
  }
 }

Here, you set up the component with two @GKInspectable properties; you’ll use the values of these two properties to set up the physics body category and shape. You then proceed to set up the body using those values.

Now that you have a component that you can attach to your entities, you’re ready to build the GameScene extension that handles contacts and collisions.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset