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

jon
Some minor things in the paper edition that says “3 2020” on the title page verso, not mentioned in the book’s errata online: p. 186 But...
New
GilWright
Working through the steps (checking that the Info,plist matches exactly), run the demo game and what appears is grey but does not fill th...
New
raul
Page 28: It implements io.ReaderAt on the store type. Sorry if it’s a dumb question but was the io.ReaderAt supposed to be io.ReadAt? ...
New
nicoatridge
Hi, I have just acquired Michael Fazio’s “Kotlin and Android Development” to learn about game programming for Android. I have a game in p...
New
jskubick
I found an issue in Chapter 7 regarding android:backgroundTint vs app:backgroundTint. How to replicate: load chapter-7 from zipfile i...
New
brunogirin
When trying to run tox in parallel as explained on page 151, I got the following error: tox: error: argument -p/–parallel: expected one...
New
hazardco
On page 78 the following code appears: <%= link_to ‘Destroy’, product, class: ‘hover:underline’, method: :delete, data: { confirm...
New
jwandekoken
Book: Programming Phoenix LiveView, page 142 (157/378), file lib/pento_web/live/product_live/form_component.ex, in the function below: d...
New
Keton
When running the program in chapter 8, “Implementing Combat”, the printout Health before attack was never printed so I assumed something ...
New
bjnord
Hello @herbert ! Trying to get the very first “Hello, Bracket Terminal!" example to run (p. 53). I develop on an Amazon EC2 instance runn...
New

Other popular topics Top

AstonJ
Or looking forward to? :nerd_face:
New
Exadra37
Please tell us what is your preferred monitor setup for programming(not gaming) and why you have chosen it. Does your monitor have eye p...
New
siddhant3030
I’m thinking of buying a monitor that I can rotate to use as a vertical monitor? Also, I want to know if someone is using it for program...
New
dasdom
No chair. I have a standing desk. This post was split into a dedicated thread from our thread about chairs :slight_smile:
New
AstonJ
I ended up cancelling my Moonlander order as I think it’s just going to be a bit too bulky for me. I think the Planck and the Preonic (o...
New
AstonJ
Biggest jackpot ever apparently! :upside_down_face: I don’t (usually) gamble/play the lottery, but working on a program to predict the...
New
foxtrottwist
A few weeks ago I started using Warp a terminal written in rust. Though in it’s current state of development there are a few caveats (tab...
New
First poster: joeb
The File System Access API with Origin Private File System. WebKit supports new API that makes it possible for web apps to create, open,...
New
Help
I am trying to crate a game for the Nintendo switch, I wanted to use Java as I am comfortable with that programming language. Can you use...
New
New

Sub Categories: