How to define category bit mask enumeration for SpriteKit in Swift?

萝らか妹 提交于 2019-11-29 22:18:28
nschum

What you could do is use the binary literals: 0b1, 0b10, 0b100, etc.

However, in Swift you cannot bitwise-OR enums, so there is really no point in using bitmasks in enums. Check out this question for a replacement for NS_OPTION.

If you look at this swift tutorial, you can avoid the whole toRaw() or rawValue conversion by using:

struct PhysicsCategory {
  static let None      : UInt32 = 0
  static let All       : UInt32 = UInt32.max
  static let Monster   : UInt32 = 0b1       // 1
  static let Projectile: UInt32 = 0b10      // 2
}

monster.physicsBody?.categoryBitMask = PhysicsCategory.Monster 
monster.physicsBody?.contactTestBitMask = PhysicsCategory.Projectile 
monster.physicsBody?.collisionBitMask = PhysicsCategory.None 
knert

Take a look at the AdvertureBuilding SpriteKit game. They rebuilt it in Swift and you can download the source on the iOS8 dev site.

They are using the following method of creating an enum:

enum ColliderType: UInt32 {
  case Hero = 1
  case GoblinOrBoss = 2
  case Projectile = 4
  case Wall = 8
  case Cave = 16
}

And the setup is like this

physicsBody.categoryBitMask = ColliderType.Cave.toRaw()
physicsBody.collisionBitMask = ColliderType.Projectile.toRaw() | ColliderType.Hero.toRaw()
physicsBody.contactTestBitMask = ColliderType.Projectile.toRaw()

And check like this:

func didBeginContact(contact: SKPhysicsContact) {

// Check for Projectile
    if contact.bodyA.categoryBitMask & 4 > 0 || contact.bodyB.categoryBitMask & 4 > 0   {
          let projectile = (contact.bodyA.categoryBitMask & 4) > 0 ? contact.bodyA.node : contact.bodyB.node
    }
}

As noted by, user949350 you can use literal values instead. But what he forgot to point out is that your raw value should be in "squares". Notice how the sample of code of Apple enumerates the categories. They are 1, 2, 4, 8 and 16, instead of the usual 1, 2, 3, 4 , 5 etc.

So in your code it should be something like this:

enum CollisionCategory:UInt32 {
case PlayerSpaceShip = 1,
case EnemySpaceShip = 2,
case ChickenSpaceShip = 4,

}

And if you want your player node to collide with either enemy or chicken spaceship, for example, you can do something like this:

playerNode.physicsBody.collisionBitMask = CollisionCategory.EnemySpaceShip.toRaw() | CollisionCategory.ChickenSpaceShip.toRaw()

Try casting your cases as UInt.

enum CollisionCategory: UInt{
    case PlayerSpaceship = 0
    case EnemySpaceship = UInt(1 << 0)
    case PlayerMissile = UInt(1 << 1)
    case EnemyMissile = UInt(1 << 2)
}

This gets rid of the errors for me.

An easy way to handle the bitmasks in swift is to create an enum of type UInt32 containing all your different collision types. That is

enum ColliderType: UInt32 {
    case Player = 1
    case Attacker = 2
}

And then in your Player Class add a physics body and setup the collision detection

physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(size.width, size.height))
physicsBody.categoryBitMask = ColliderType.Player.toRaw()
physicsBody.contactTestBitMask = ColliderType.Attacker.toRaw()
physicsBody.collisionBitMask = ColliderType.Attacker.toRaw()

And for your Attacker Class (or projectile, bird, meteor, etc.) setup its physics body as

physicsBody = SKPhysicsBody(circleOfRadius: size.width / 2)
physicsBody.categoryBitMask = ColliderType.Attacker.toRaw()
physicsBody.contactTestBitMask = ColliderType.Player.toRaw()
physicsBody.collisionBitMask = ColliderType.Player.toRaw()

(Note that you can setup the physics body to be whatever shape you want)

Then make sure you have a SKPhysicsContactDelegate setup (e.g. you can let your scene be the delegate) and then implement the optional protocol method didBeginContact

class GameScene: SKScene, SKPhysicsContactDelegate {

    override func didMoveToView(view: SKView) {

        physicsWorld.contactDelegate = self
        // Additional setup...

    }

    func didBeginContact(contact: SKPhysicsContact!) {

        println("A collision was detected!")

        if (contact.bodyA.categoryBitMask == ColliderType.Player.toRaw() &&
            contact.bodyB.categoryBitMask == ColliderType.Attacker.toRaw()) {

            println("The collision was between the Player and the Attacker")
        }

    }

}

By adding more ColliderTypes you can detect more collisions in your game.

There is a bit of a bug with UInt, but given I think only 32 bits are used anyway this would work. I would also suggest submitting a radar, you should be able to use any constant value (1 << 2 will always be the same)

Anyway, here's once they've got rid of the bugs with UInts, this would work

enum CollisionCategory: Int{ case PlayerSpaceship = 0, EnemySpaceShip, PlayerMissile, EnemyMissile

func collisionMask()->Int{
    switch self{
    case .PlayerSpaceship:
        return 0;
    default:
        return 1 << (self.toRaw()-1)
    }
}
}
CollisionCategory.PlayerMissle.collisionMask()

Swift 3 with enum:

enum PhysicsCategory: UInt32 {
  case none = 1
  case monster = 2
  case projectile = 4
  case wall = 8
}

monster.physicsBody?.categoryBitMask = PhysicsCategory.monster.rawValue 
monster.physicsBody?.contactTestBitMask = PhysicsCategory.projectile.rawValue
monster.physicsBody?.collisionBitMask = PhysicsCategory.none.rawValue 

I prefer to use like below which works just fine and I think it is the closest way to your original attempt:

// MARK: Categories - UInt32
let playerCategory:UInt32 = 0x1 << 0
let obstacleCategory:UInt32 = 0x1 << 1
let powerUpCategory:UInt32 = 0x1 << 2

P.S.: This is Swift 4

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!