了解SpriteKit CollisionBitMask

时间:2017-09-29 18:11:52

标签: swift xcode sprite-kit

我正在学习使用SpriteKit,我正在学习colllisions的教程。我正在努力理解以下代码:

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
}

为什么我们要分配这些名为bitMaps的内容,以及它们如何在以下代码中使用?:

func didBegin(_ contact: SKPhysicsContact) {

    // 1
    var firstBody: SKPhysicsBody
    var secondBody: SKPhysicsBody
    if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
        firstBody = contact.bodyA
        secondBody = contact.bodyB
    } else {
        firstBody = contact.bodyB
        secondBody = contact.bodyA
    }

    // 2
    if ((firstBody.categoryBitMask & PhysicsCategory.Monster != 0) &&
        (secondBody.categoryBitMask & PhysicsCategory.Projectile != 0)) {
        if let monster = firstBody.node as? SKSpriteNode, let
            projectile = secondBody.node as? SKSpriteNode {
            projectileDidCollideWithMonster(projectile: projectile, monster: monster)

谢谢!

2 个答案:

答案 0 :(得分:5)

BitMasks是用于描述二进制格式项目的标志

所以想象一下你有8种方法可以形容。 (在Spritekit中你有32个)

我们可以将这8个东西整合到一个字节中,因为8位在一个字节中,这样我们就可以节省空间并更快地执行操作。

以下是8个描述的示例

Attackable 1 << 0  
Ranged     1 << 1  
Undead     1 << 2  
Magic      1 << 3  
Regenerate 1 << 4  
Burning    1 << 5  
Frozen     1 << 6  
Poison     1 << 7  

现在我有一个射手,想要归类他。我想说他是一个生活友好的单位,其中包括

我会使用categoryBitmask对他进行分类:

archer.categoryBitmask = Ranged

这将以1字节表示为

00000010
||||||||_ Attackable
|||||||_ Ranged
||||||_ Undead
|||||_ Magic
||||_ Regenerate
|||_ Burning
||_ Frozen
|_ Poison

现在让我们说他的箭是火箭,我会把它归类为:

arrow.categoryBitmask = Burning

00100000
||||||||_ Attackable
|||||||_ Ranged
||||||_ Undead
|||||_ Magic
||||_ Regenerate
|||_ Burning
||_ Frozen
|_ Poison

最后,我们有一个可以被击中并随时间再生的僵尸

zombie.categoryBitmask = Attackable + Undead + Regenerate

00010101
||||||||_ Attackable
|||||||_ Ranged
||||||_ Undead
|||||_ Magic
||||_ Regenerate
|||_ Burning
||_ Frozen
|_ Poison

现在我希望我的箭头只能点击Attackable精灵(在这种情况下是僵尸)

我会设置contactTestBitmask告诉箭头他能打什么

arrow.contactTestBitmask = Attackable 00000001

现在我们需要检查箭头何时击中僵尸,这是didBeginContact进来的地方

didBeginContact会做什么,是通过使用AND操作查找匹配项来检查移动项目的contactTestBitmask到它所点击的categoryBitmask

在我们的案例中

arrow.contactTestBitmask =  00000001
zombie.categoryMask      =  00010101 AND
                            --------
                            00000001

因为我们的价值是&gt; 0,联系成功。

这意味着didBegins被解雇了。

现在我们在didBegins,我们需要确定哪个物理体是我们的箭头,哪个物理体是我们的僵尸

这是下一个声明的来源

func didBegin(_ contact: SKPhysicsContact) {

    // 1
    var firstBody: SKPhysicsBody
    var secondBody: SKPhysicsBody
    if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
        firstBody = contact.bodyA
        secondBody = contact.bodyB
    } else {
        firstBody = contact.bodyB
        secondBody = contact.bodyA
}

由于arrow = 00100000和zombie = 00010101,我们知道僵尸的值低于箭头,所以在这种情况下,zombie是&lt;箭头。

我们将firstBody分配给僵尸,将secondBody分配给箭头

现在我们需要提供一个条件。

我们想说一个不死生物是否被可燃物体击中,就可以做点什么了。

所以在代码中这将是

if (firstBody & Undead > 0) && (secondBody & Burning > 0)
{
//burn zombie
}

但是如果箭头是冰箭怎么办?我们不想进入if语句。

现在我们可以添加第二个条件,让我们冻结僵尸。

if (firstBody & Undead > 0) && (secondBody & Frozen > 0)
{
//freeze zombie
}

这些ifs正在做什么,是确保身体已打开某些功能,然后执行一些操作以响应它们。

要了解有关位掩码如何工作的更多信息,我将研究如何制作真值表。这基本上就是这个。我们只是创建一些真值表,并试图弄清楚一个陈述是否为真,如果是真的,则执行一个动作。

答案 1 :(得分:1)

操纵contactTest和collison位掩码以启用/禁用特定的接触和碰撞。

对于这个例子,我们将使用4个主体,为简单起见,仅显示位掩码的最后8位。这4个实体是3个SKSpriteNodes(每个都有一个物理体)和一个边界:

let edge = frame.insetBy(dx: 0, dy: 0)
physicsBody = SKPhysicsBody(edgeLoopFrom: edge)

请注意,'edge'物理主体是场景的物理主体,而不是节点。

我们定义了4个独特的类别

let purpleSquareCategory:   UInt32 = 1 << 0  // bitmask is ...00000001
let redCircleCategory:      UInt32 = 1 << 1  // bitmask is ...00000010
let blueSquareCategory:     UInt32 = 1 << 2  // bitmask is ...00000100
let edgeCategory:           UInt32 = 1 << 31  // bitmask is 10000...00000000

为每个物理主体分配它所属的类别:

    //Assign our category bit masks to our physics bodies
    purpleSquare.physicsBody?.categoryBitMask = purpleSquareCategory
    redCircle.physicsBody?.categoryBitMask = redCircleCategory
    blueSquare.physicsBody?.categoryBitMask = blueSquareCategory
    physicsBody?.categoryBitMask = edgeCategory  // This is the edge for the scene itself

如果正文的collisionBitMask中的某个位设置为1,那么它会碰撞(弹回)任何在其categoryBitMask中位于相同位置的“1”的主体。同样适用于contactTestBitMask。

除非您另行指定,否则所有内容都会与其他所有内容发生冲突,并且不会生成任何联系人(当任何内容与其他内容联系时,您的代码都不会收到通知):

purpleSquare.physicsBody.collisonBitMask = 11111111111111111111111111111111 // 32 '1's.

每个位置的每一位都是'1',所以当与任何其他categoryBitMask相比时,Sprite Kit会发现'1',因此会发生碰撞。如果您不希望此主体与某个类别发生冲突,则必须将collisonBitMask中的正确位设置为“0”

并将其contactTestbitMask设置为全0:

redCircle.physicsBody.contactTestBitMask = 00000000000000000000000000000000  // 32 '0's

与collisionBitMask相同,除了反转。

可以使用以下方法关闭实体之间的接触或碰撞(保持现有接触或碰撞不变):

nodeA.physicsBody?.collisionBitMask &= ~nodeB.category

我们逻辑地将nodeA的碰撞位掩码与nodeB的类别位掩码的反(逻辑NOT,〜运算符)“关闭”该位nodeA的bitMask。例如,阻止红色圆圈与紫色方块碰撞:

redCircle.physicsBody?.collisionBitMask = redCircle.physicsBody?.collisionBitMask & ~purpleSquareCategory

可以缩短为:

redCircle.physicsBody?.collisionBitMask &= ~purpleSquareCategory

说明:

redCircle.physicsBody.collisonBitMask = 11111111111111111111111111111111
purpleSquareCategory  = 00000000000000000000000000000001
~purpleSquareCategory = 11111111111111111111111111111110 
11111111111111111111111111111111 & 11111111111111111111111111111110 = 11111111111111111111111111111110 

redCircle.physicsBody.collisonBitMask现在等于11111111111111111111111111111110 redCircle不再与类别为...... 0001(purpleSquare)

的物体发生碰撞

您可以直接设置它,而不是关闭collsionsbitMask中的各个位:

blueSquare.physicsBody?.collisionBitMask = (redCircleCategory | purpleSquareCategory)
i.e. blueSquare.physicsBody?.collisionBitMask = (....00000010 OR ....00000001)

等于blueSquare.physicsBody?.collisionBitMask = ....00000011

blueSquare只会与类别或..01或..10

的实体发生碰撞

可以使用以下任意点打开2个实体之间的接触或碰撞(不影响任何现有的接触或碰撞):

redCircle.physicsBody?.contactTestBitMask |= purpleSquareCategory

我们逻辑上将redCircle的bitMask与purpleSquare的类别位掩码一起“打开”redcircle的bitMask中的那个位。这使得redCircel的bitMas中的任何其他位都不受影响。

您可以确保每个形状“弹回”屏幕边缘,如下所示:

// Make sure everything collides with the screen edge
enumerateChildNodes(withName: "//*") { node, _ in
    node.physicsBody?.collisionBitMask |= self.edgeCategory  //Add edgeCategory to the collision bit mask
}

注意:

碰撞可以是片面的,即物体A可以碰撞(反弹)物体B,而物体B继续进行,好像什么也没发生过一样。如果你想让2个物体相互反弹,它们必须被告知要与另一个物体相撞:

blueSquare.physicsBody?.collisionBitMask = redCircleCategory
redcircle.physicsBody?.collisionBitMask = blueSquareCategory
然而,联系人不是片面的;如果您想知道对象A何时触摸(接触)对象B,则就对象B设置对象A的接触检测就足够了。您不必在对象B上为对象A设置接触检测。

blueSquare.physicsBody?.contactTestBitMask = redCircleCategory

我们不需要redcircle.physicsBody?.contactTestBitMask= blueSquareCategory