The Gibbinold
The Gibbinold

Reputation: 269

Check if two nodes are the same color when they touch each other

I'm new to Swift so apologies if I have made a rookie error. I am trying to get two boxes to disappear when they touch if the two boxes are the same color. I have the following code so far:

This code sets up the game:

    import SpriteKit
    import GameplayKit

    class GameScene: SKScene, SKPhysicsContactDelegate {
        override func didMove(to view: SKView) {
            physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
            physicsWorld.contactDelegate = self

            let background = SKSpriteNode(imageNamed: "background.jpg")
            background.size = self.frame.size;
            background.position = CGPoint(x: 0, y: 0)
            background.blendMode = .replace
            background.zPosition = -1
            addChild(background)

        }

Code to generate a random color:

        enum Color {
            case ColorRed
            case ColorGreen
            case ColorBlue

            public var color: UIColor {
                switch self {
                case .ColorRed: return UIColor(red: 255, green: 0, blue: 0, alpha: 1)
                case .ColorGreen: return UIColor(red: 0, green: 255, blue: 0, alpha: 1)
                case .ColorBlue: return UIColor(red: 0, green: 0, blue: 255, alpha: 1)
                }
            }

            static var all: [Color] = [.ColorRed, .ColorGreen, .ColorBlue]

            static var randomColor: UIColor {
                let randomIndex = Int(arc4random_uniform(UInt32(all.count)))
                return all[randomIndex].color
            }
        }

This is the part that matters - the actual contact between the objects:

    func didBegin(_ contact: SKPhysicsContact) {

        if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {

            let firstBody = contact.bodyA.node as! SKSpriteNode!
            let secondBody = contact.bodyB.node as! SKSpriteNode!

            if firstBody!.color == secondBody!.color {
                firstBody!.removeFromParent()
                secondBody!.removeFromParent()
            }
        } else {

            let firstBody = contact.bodyB.node as! SKSpriteNode!
            let secondBody = contact.bodyA.node as! SKSpriteNode!

            if firstBody!.color == secondBody!.color {
                firstBody!.removeFromParent()
                secondBody!.removeFromParent()
            }
        }
    }

And finally the code for when the user touches the screen:

        override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
            if let touch = touches.first {
                let location = touch.location(in: self)
                let box = SKSpriteNode(color: UIColor.red, size: CGSize(width: 64, height: 64))
                box.color = Color.randomColor
                box.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 64, height: 64))
                box.position = location
                addChild(box)
            }
        }

I have provided all the code so you know the setup. Thank you in advance for your help.

Upvotes: 3

Views: 398

Answers (2)

Whirlwind
Whirlwind

Reputation: 13675

You haven't set contactBitMask appropriately so no contacts were detected... By default, due to performance reasons a default value of this mask is zero:

When two bodies share the same space, each body’s category mask is tested against the other body’s contact mask by performing a logical AND operation. If either comparison results in a nonzero value, an SKPhysicsContact object is created and passed to the physics world’s delegate. For best performance, only set bits in the contacts mask for interactions you are interested in.

The default value is 0x00000000 (all bits cleared).

To fix this, set both contact and category bit masks to an appropriate values, like this:

class GameScene: SKScene,SKPhysicsContactDelegate {

    override func didMove(to view: SKView) {
        self.physicsWorld.contactDelegate = self

        physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
        physicsWorld.contactDelegate = self


    }

    func didBegin(_ contact: SKPhysicsContact) {

        if let bodyA = contact.bodyA.node as? SKSpriteNode,
           let bodyB = contact.bodyB.node as? SKSpriteNode{
            //Of course this is simple example and you will have to do some "filtering" to determine what type of objects are collided.
           // But the point is , when appropriate objects have collided, you compare their color properties.
            if bodyA.color == bodyB.color {
                bodyA.run(SKAction.removeFromParent())
                bodyB.run(SKAction.removeFromParent())
            }
        }
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        if let touch = touches.first {
            let location = touch.location(in: self)
            let box = SKSpriteNode(color: UIColor.red, size: CGSize(width: 64, height: 64))
            box.color = Color.randomColor

            box.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 64, height: 64))
            box.physicsBody?.contactTestBitMask = 0b1
            box.physicsBody?.categoryBitMask = 0b1
            box.position = location
            addChild(box)
        }
    }
}

Now when a contact happen between two bodies, as said in docs, each body's category bit mask is tested agains the other body's contact mask, by performing logical AND operation. If a result is non-zero, a contact notification occurs. In this case this would be 1 & 1 = 1.

Upvotes: 4

chengsam
chengsam

Reputation: 7405

First, declare a struct for the categoryBitMask:

struct ColorMask {
    static let Red: UInt32 = 0x1 << 0
    static let Green: UInt32 = 0x1 << 1
    static let Blue: UInt32 = 0x1 << 2
}

Second, change you enum declaration to the following:

enum Color {
    case ColorRed
    case ColorGreen
    case ColorBlue

    public var color: UIColor {
        switch self {
        case .ColorRed: return UIColor(red: 255, green: 0, blue: 0, alpha: 1)
        case .ColorGreen: return UIColor(red: 0, green: 255, blue: 0, alpha: 1)
        case .ColorBlue: return UIColor(red: 0, green: 0, blue: 255, alpha: 1)
        }
    }

    static var all: [Color] = [.ColorRed, .ColorGreen, .ColorBlue]

    static var randomColor: Color {
        let randomIndex = Int(arc4random_uniform(UInt32(all.count)))
        return all[randomIndex]
    }
}

I just changed the above code to return Color instead of UIColor.

Third, modify didBegin to:

func didBegin(_ contact: SKPhysicsContact) {
    if contact.bodyA.categoryBitMask == contact.bodyB.categoryBitMask {
        let firstBody = contact.bodyA.node as! SKSpriteNode!
        let secondBody = contact.bodyB.node as! SKSpriteNode!

        firstBody!.removeFromParent()
        secondBody!.removeFromParent()
    }
}

In the above code, just compare the categoryBitMask is enough as I will set the body with same color with same categoryBitMask later.

Lastly, set the categoryBitMask to the box using the following code:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    if let touch = touches.first {
        let location = touch.location(in: self)
        let box = SKSpriteNode(color: UIColor.red, size: CGSize(width: 64, height: 64))
        let color = Color.randomColor
        box.color = color.color
        box.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 64, height: 64))
        if color == Color.ColorRed {
            box.physicsBody?.categoryBitMask = ColorMask.Red
        } else if color == Color.ColorGreen {
            box.physicsBody?.categoryBitMask = ColorMask.Green
        }else if color == Color.ColorBlue {
            box.physicsBody?.categoryBitMask = ColorMask.Blue
        }
        box.physicsBody?.contactTestBitMask = ColorMask.Red | ColorMask.Green | ColorMask.Blue
        box.position = location
        addChild(box)
    }
}

Upvotes: 0

Related Questions