如何在Swift中为SpriteKit定义类别位掩码枚举?

29

为了在Objective-C中定义一个类别位掩码枚举,我过去常常这样打:

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

我如何使用 Swift 实现相同的功能?我试过使用枚举,但是无法使其正常工作。这是我迄今为止尝试的。

错误截图

9个回答

19

如果您查看这个Swift教程,您可以通过使用以下方法避免整个toRaw()或rawValue转换:

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 

19
您可以使用二进制字面量:0b10b100b100等。
在Swift中,您不能对枚举类型执行按位或操作,因此在枚举中使用位掩码实际上是没有意义的。请查看这个问题以了解NS_OPTION的替代方法。

@Confused - rawValue是枚举的一个“属性”,它返回用于表示枚举情况的实际原始值。如果您请求枚举的特定情况,它将不会返回实际值,而是分配给它的类别。因此,例如,给定一个枚举类型"Fruit"和等于0b100的情况"banana",如果您打印出"Fruit.banana"的值,它将返回"banana",但是如果您打印"Fruit.banana.rawValue"的值,它将打印实际值4。 - daver
谢谢@daver。我需要仔细思考一下,画几次图,然后尝试想象我试图描绘的东西...然后也许这对我来说就有意义了。设计师的大脑在编程的范式和过程的模糊性、不透明性和纯粹的任意性方面遇到了巨大的困难。 - Confused
@confused - 别担心,我认为需要理解的关键思想就是Swift枚举正在努力让你将它们视为类别,即仅仅是名称而不是数字或值,只有当你真的、真的、真的想知道存储在底层的“真实值”时,Swift才会强制你使用.rawValue来查看。 - daver
谢谢!这是一些有用的见解。问题的另一部分可能是我从未真正看到使用枚举来清晰解决问题的方法。在我的想法中,它们具有三重依赖:按定义本身的结构、值的澄清/设置/获取以及测试/检查。大多数时候,这似乎是不必要的人为虚构。 - Confused
@daver 所以我还没有那种“啊哈!”的时刻,也就是我还不理解为什么要使用它们,只是因为我认为我应该使用它们来尝试理解它们,而不是天生就看到了它们的用途和位置。 - Confused
显示剩余4条评论

9

请看AdvertureBuilding SpriteKit游戏。他们用Swift重建了它,你可以在iOS8开发者网站上下载源码。

他们正在使用以下方法创建枚举:


``` Swift enum Direction: Int { case up = 0, down, left, right } ```
enum ColliderType: UInt32 {
  case Hero = 1
  case GoblinOrBoss = 2
  case Projectile = 4
  case Wall = 8
  case Cave = 16
}

这是设置的方式:
physicsBody.categoryBitMask = ColliderType.Cave.toRaw()
physicsBody.collisionBitMask = ColliderType.Projectile.toRaw() | ColliderType.Hero.toRaw()
physicsBody.contactTestBitMask = ColliderType.Projectile.toRaw()

并且像这样检查:

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
    }
}

4

正如用户user949350所指出的那样,您可以使用字面值。但他忘了指出的是,您的原始值应该在“方括号”中。请注意,苹果公司列举类别的代码示例。它们是1、2、4、8和16,而不是通常的1、2、3、4、5等。

因此,在您的代码中,应该像这样:

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

如果你想让你的玩家节点与敌人或鸡飞船发生碰撞,你可以像这样做:

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

1
尝试将您的案例转换为UInt。
enum CollisionCategory: UInt{
    case PlayerSpaceship = 0
    case EnemySpaceship = UInt(1 << 0)
    case PlayerMissile = UInt(1 << 1)
    case EnemyMissile = UInt(1 << 2)
}

这对我来说解决了错误。

我复制了你的代码,但我仍然得到了我在截图中发布的相同错误。它没有改变任何东西。 - Rafał Sroka
有趣。嗯,你总是可以默认使用字面量1、2、4、8等。 - Connor

1

处理位掩码的一种简单方法是在Swift中创建一个UInt32类型的枚举,其中包含了所有不同的碰撞类型。具体如下

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

然后在您的玩家类中添加物理引擎并设置碰撞检测。

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

对于你的攻击者类(或弹丸、鸟、流星等),设置它的物理体:

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

请注意,您可以设置物理体的形状为任何形状。
然后确保设置了SKPhysicsContactDelegate(例如,您可以让场景成为代理),然后实现可选协议方法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")
        }

    }

}

通过添加更多的ColliderTypes,您可以在游戏中检测到更多的碰撞。

1
枚举中的位掩码值必须是2的平方,对吗?如果是这样的话,也许要强调一下,以便未来的读者不会假设你示例中的下一个值可能是3。 - Crashalot

0

UInt存在一些小问题,但考虑到只使用32位,这应该可以解决。我建议提交一个radar,您应该能够使用任何常量值(1 << 2将始终相同)

无论如何,一旦解决了UInt的错误,这将起作用

枚举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()

0

我更喜欢使用下面的方式,它完全正常工作,而且我认为这是与你最初尝试最接近的方式:

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

附言:这是Swift 4


0

使用枚举的Swift 3:

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 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接