mikecargal

mikecargal

Apple Game Frameworks and Technologies: Changes to GameScene+PhysicsContact

Title: Changes to GameScene+PhysicsContact

(I understand, it may be to late in your process to make such a change, but I’ll share here anyway, and would welcome feedback)

The GameScene+PhysicsContact extension was the source of one of my typos, and I also found the code to be quite “dense” to read.

I’m taking a bit of time at the end of Val’s Revenge to go in and make changes that seem good, but mainly to test that I’m getting the hang of Swift.

I added a couple of functions to factor out a lot of the common code and reduce the noise of reading the cases and then matching up the nodes. (On the plus side, my typo was accidentally having a collisionBitMask one place rather than a categoryBitMask (probably just a cursor key error with code completion, but I really don’t want to admit how long it took me to track that down). This code prevents that sort of error by placing it into the function where it’s done once, and will either be always right, or always wrong)

Anyway… as the tag says… Just a suggestion

This is my revised version of GameScene+PhysicsContact.swift:

import SpriteKit

extension GameScene: SKPhysicsContactDelegate {
    func match(_ contact: SKPhysicsContact, body: PhysicsBody) -> (match:SKNode, other:SKNode) {
        return contact.bodyA.categoryBitMask == body.categoryBitMask ?
            (contact.bodyA.node!, contact.bodyB.node!) :
            (contact.bodyB.node!, contact.bodyA.node!)
    }
    
    func collisionOf(_ bodyA :PhysicsBody, _ bodyB: PhysicsBody) -> UInt32 {
        bodyA.categoryBitMask | bodyB.categoryBitMask
    }

    func didBegin(_ contact: SKPhysicsContact) {
        let collision = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
        switch collision {
        // MARK: - Player | Collectible

        case collisionOf(.player,.collectible):
            let (playerNode, collectible) = match(contact, body: .player)

            if let player = playerNode as? Player {
                player.collectItem(collectible)
            }

        // MARK: - Player | Door

        case collisionOf(.player,.door):
            let (playerNode, door) = match(contact, body: .player)

            if let player = playerNode as? Player {
                player.useKeyToOpenDoor(door)
            }

        // MARK: - Projectile | Collectible

        case collisionOf(.projectile,.collectible):
            let (projectileNode, collectibleNode) = match(contact, body: .projectile)
            if let collectibleComponent = collectibleNode.entity?.component(ofType: CollectibleComponent.self) {
                collectibleComponent.destroyedItem()
            }
            projectileNode.removeFromParent()

        // MARK: - Player | Monster

        case collisionOf(.player,.monster):
            let (playerNode, _) = match(contact, body: .player)
            if let healthComponent = playerNode.entity?.component(ofType: HealthComponent.self) {
                healthComponent.updateHealth(-1, forNode: playerNode)
            }

        // MARK: - Projectile | Monster

        case collisionOf(.projectile,.monster):
            let (monsterNode, _) = match(contact, body: .monster)

            if let healthComponent = monsterNode.entity?.component(ofType: HealthComponent.self) {
                healthComponent.updateHealth(-1, forNode: monsterNode)
            }

            // MARK: - Player | Platform

        case collisionOf(.player,.exit):
            let (playerNode, _) = match(contact, body: .player)
            // update the saved stats
            if let player = playerNode as? Player {
                GameData.shared.keys = player.getStats().keys
                GameData.shared.treasure = player.getStats().treasure
            }

            GameData.shared.level += 1
            loadSceneForLevel(GameData.shared.level)
        default:
            break
        }
    }
}

First Post!

mikecargal

mikecargal

slight improvement to avoid “evil” ! unwraps.

import SpriteKit

extension GameScene: SKPhysicsContactDelegate {
    func match(_ contact: SKPhysicsContact, body: PhysicsBody) -> (match: SKNode, other: SKNode) {
        guard let nodeA = contact.bodyA.node,
              let nodeB = contact.bodyB.node
        else {
            fatalError("Expected contact bodies to both have attached nodes")
        }
        return contact.bodyA.categoryBitMask == body.categoryBitMask ?
            (nodeA, nodeB) :
            (nodeB, nodeA)
    }

    func collisionOf(_ bodyA: PhysicsBody, _ bodyB: PhysicsBody) -> UInt32 {
        bodyA.categoryBitMask | bodyB.categoryBitMask
    }

    func didBegin(_ contact: SKPhysicsContact) {
        let collision = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
        switch collision {
        // MARK: - Player | Collectible

        case collisionOf(.player, .collectible):
            let (playerNode, collectible) = match(contact, body: .player)

            if let player = playerNode as? Player {
                player.collectItem(collectible)
            }

        // MARK: - Player | Door

        case collisionOf(.player, .door):
            let (playerNode, door) = match(contact, body: .player)

            if let player = playerNode as? Player {
                player.useKeyToOpenDoor(door)
            }

        // MARK: - Projectile | Collectible

        case collisionOf(.projectile, .collectible):
            let (projectileNode, collectibleNode) = match(contact, body: .projectile)
            if let collectibleComponent = collectibleNode.entity?.component(ofType: CollectibleComponent.self) {
                collectibleComponent.destroyedItem()
            }
            projectileNode.removeFromParent()

        // MARK: - Player | Monster

        case collisionOf(.player, .monster):
            let (playerNode, _) = match(contact, body: .player)
            if let healthComponent = playerNode.entity?.component(ofType: HealthComponent.self) {
                healthComponent.updateHealth(-1, forNode: playerNode)
            }

        // MARK: - Projectile | Monster

        case collisionOf(.projectile, .monster):
            let (monsterNode, _) = match(contact, body: .monster)

            if let healthComponent = monsterNode.entity?.component(ofType: HealthComponent.self) {
                healthComponent.updateHealth(-1, forNode: monsterNode)
            }

            // MARK: - Player | Platform

        case collisionOf(.player, .exit):
            let (playerNode, _) = match(contact, body: .player)
            // update the saved stats
            if let player = playerNode as? Player {
                GameData.shared.keys = player.getStats().keys
                GameData.shared.treasure = player.getStats().treasure
            }

            GameData.shared.level += 1
            loadSceneForLevel(GameData.shared.level)
        default:
            break
        }
    }
}

Where Next?

Popular Pragmatic Bookshelf topics Top

AleksandrKudashkin
On the page xv there is an instruction to run bin/setup from the main folder. I downloaded the source code today (12/03/21) and can’t see...
New
adamwoolhether
Is there any place where we can discuss the solutions to some of the exercises? I can figure most of them out, but am having trouble with...
New
akraut
The markup used to display the uploaded image results in a Phoenix.LiveView.HTMLTokenizer.ParseError error. lib/pento_web/live/product_l...
New
jonmac
The allprojects block listed on page 245 produces the following error when syncing gradle: “org.gradle.api.GradleScriptException: A prob...
New
s2k
Hi all, currently I wonder how the Tailwind colours work (or don’t work). For example, in app/views/layouts/application.html.erb I have...
New
Henrai
Hi, I’m working on the Chapter 8 of the book. After I add add the point_offset, I’m still able to see acne: In the image above, I re...
New
tkhobbes
After some hassle, I was able to finally run bin/setup, now I have started the rails server but I get this error message right when I vis...
New
Keton
When running the program in chapter 8, “Implementing Combat”, the printout Health before attack was never printed so I assumed something ...
New
dachristenson
I just bought this book to learn about Android development, and I’m already running into a major issue in Ch. 1, p. 20: “Update activity...
New
New

Other popular topics Top

PragmaticBookshelf
Free and open source software is the default choice for the technologies that run our world, and it’s built and maintained by people like...
New
DevotionGeo
I know that -t flag is used along with -i flag for getting an interactive shell. But I cannot digest what the man page for docker run com...
New
PragmaticBookshelf
From finance to artificial intelligence, genetic algorithms are a powerful tool with a wide array of applications. But you don't need an ...
New
PragmaticBookshelf
Learn different ways of writing concurrent code in Elixir and increase your application's performance, without sacrificing scalability or...
New
PragmaticBookshelf
Use WebRTC to build web applications that stream media and data in real time directly from one user to another, all in the browser. ...
New
mafinar
This is going to be a long an frequently posted thread. While talking to a friend of mine who has taken data structure and algorithm cou...
New
PragmaticBookshelf
Build efficient applications that exploit the unique benefits of a pure functional language, learning from an engineer who uses Haskell t...
New
PragmaticBookshelf
Author Spotlight Rebecca Skinner @RebeccaSkinner Welcome to our latest author spotlight, where we sit down with Rebecca Skinner, auth...
New
New
AnfaengerAlex
Hello, I’m a beginner in Android development and I’m facing an issue with my project setup. In my build.gradle.kts file, I have the foll...
New

Sub Categories: