29

To define a category bit mask enum in Objective-C I used to type:

typedef NS_OPTIONS(NSUInteger, CollisionCategory)
{
    CollisionCategoryPlayerSpaceship = 0,
    CollisionCategoryEnemySpaceship = 1 << 0,
    CollisionCategoryChickenSpaceship = 1 << 1,
};

How can I achieve the same using Swift? I experimented with enums but can't get it working. Here is what I tried so far.

error screenshot

Rafał Sroka
  • 39,540
  • 23
  • 113
  • 143

9 Answers9

19

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.

Community
  • 1
  • 1
nschum
  • 15,322
  • 5
  • 58
  • 56
  • As other answers suggest, you can use enums as long as you use raw values. – Crashalot Sep 12 '16 at 23:04
  • @Crashalot what do raw values look like? Sorry, just do not know. I kind of thought they might be 0b0001, but obviously not. – Confused Oct 12 '16 at 17:28
  • @Confused - rawValue is a 'property' of an enum that returns the actual raw value used to represent a case of an enum. If you ask for a specific case of an enum it will not return an actual value, but rather the category that was assigned to it. So for example given an enum "Fruit" with case "banana" that was set equal to 0b100, if you print out the value of "Fruit.banana" it will return "banana", but if you print the value of "Fruit.banana.rawValue" it will print the actual value, 4. – daver Jan 05 '17 at 05:33
  • Thanks @daver. I'll have to cogitate on this, and draw it out a few times, then try to visualise what I was trying to draw... then maybe this will make sense to me. Designer brain has HUGE troubles with the nebulousness, opaqueness and pure arbitrariness of programming's paradigms and processes. – Confused Jan 05 '17 at 12:49
  • @confused - No worries, I think the key idea to understand is just that Swift enums are trying hard to get you to just treat them like categories, i.e. just names not numbers or values, and only if you really, really, really want to know the 'true value' that's being stored underneath, then Swift forces you to use .rawValue to see that. – daver Jan 06 '17 at 04:10
  • THANK YOU! That's a helpful insight. The other part of the problem might be that I've never really seen ways to use enums clearly, as a means to solve problems. They're triple reliant, in my way of thinking: Structure of themselves by definition, the clarification/setting/getting of a value and then testing/checking of this. It seems a needless contrivance, most of the time. – Confused Jan 06 '17 at 06:45
  • @daver so I'm yet to have that "aha!" moment when I get why to use them, and only use them because I think I should to try to grok them, rather than innately seeing a purpose and place for them. – Confused Jan 06 '17 at 06:46
  • @confused I think the 'problem' that the enum concept was originally meant to solve was the ambiguous use of arbitrary integers to represent categories/names/concepts that appear in branching conditions. For example, in C the only thing you can use in a switch() statement is an int, so if you need to branch based on some category, you can only use 1, 2, 3. Enumeration allows you to 'rename 1, 2, 3 as "ball", "square", "triangle". So your code says "if (ball) then .... "rather than "if (1) then ..." Basically it just makes the code more readable. – daver Jan 24 '17 at 03:39
  • @confused - But in swift enums have much much greater capability than simply giving a readable label to a meaningless number, because they are more like structs&classes: they can have methods, and also you can bundle multiple values together at the same time and treat them like a single entity. I can't really do them justice in a comment, you should read the apple's swift book on enums to see what they can do, they're really very powerful. (except apparently to use a bitmask, then they're still pretty clunky :-( – daver Jan 24 '17 at 03:44
19

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 
William T.
  • 12,831
  • 4
  • 56
  • 53
9

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
    }
}
CodeBender
  • 35,668
  • 12
  • 125
  • 132
knert
  • 91
  • 3
4

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()
Donn
  • 1,680
  • 14
  • 17
1

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.

Connor Pearson
  • 63,902
  • 28
  • 145
  • 142
1

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.

Groot
  • 13,943
  • 6
  • 61
  • 72
  • 1
    The bitmask values in the enum must be squares of 2, right? If so, perhaps highlight this for future readers who assume the next value in your example might be 3. – Crashalot Sep 11 '16 at 22:34
0

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()
rougeExciter
  • 7,435
  • 2
  • 21
  • 17
0

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 
wm.p1us
  • 2,019
  • 2
  • 27
  • 38
0

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

Vetuka
  • 1,523
  • 1
  • 24
  • 40