理解SpriteKit的CollisionBitMask

3

我正在学习使用SpriteKit,并且正在跟随碰撞的教程。我很难理解以下代码:

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
}

为什么我们要分配这些被称为位图的东西,以及它们如何在下面的代码中起作用呢?

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个回答

10

位掩码是用二进制格式描述项目的标志

想象一下你有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

现在我希望我的箭只能攻击可攻击的精灵(这里是僵尸)

我会设置contactTestBitmask来告诉箭头它可以击中什么东西

arrow.contactTestBitmask = Attackable 00000001

现在我们需要检查箭头何时击中了僵尸,这就是didBeginContact的作用。

didBeginContact所做的是,通过使用AND操作来查找匹配项,检查移动物品的contactTestBitmask与它所击中的categoryBitmask之间的匹配。

在我们的情况下:

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

由于我们的价值>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
}

由于箭头=00100000,僵尸=00010101,我们知道僵尸的值比箭头低,因此在这种情况下,僵尸小于箭头。

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

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

我们想说如果一个不死生物被可燃物体击中,做某事。

因此,在代码中,这将是:

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

但是如果箭头是冰箭呢?我们不想进入那个if语句。

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

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

这些 if 语句的作用是确保我们开启了某些特定的功能,并在对它们做出反应时执行某些操作。

如果您想了解更多关于位掩码如何工作的信息,建议您研究一下如何制作真值表。本质上,这就是我们要做的。我们只是创建了一些真值表,并尝试确定一个语句是否为真,如果是真的,就执行一个动作。


然而, Attackable 1 << 0
Ranged 1 << 1
Undead 1 << 2
Magic 1 << 3
Regenerate 1 << 4
Burning 1 << 5
Frozen 1 << 6
Poison 1 << 7 <- "1<<7" 是什么意思?
- GizGazGo
你在问什么? - Knight0fDragon
在顶部,当您给出8个描述的示例时,我不太明白那里发生了什么,例如“1<<7”是什么意思? - GizGazGo
@GizGazGo,我稍后为您分解一下,这些都是位 << 表示移位,因此 1 << 1 表示取值并将其向左移动一位,因此在二进制中,您从 0001 转换为 0010。 - Knight0fDragon

2

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

在本例中,我们将使用4个物体,并仅显示位掩码的最后8位以简化。这4个物体是3个带有物理体的SKSpriteNode和一个边界:

let edge = frame.insetBy(dx: 0, dy: 0)
physicsBody = SKPhysicsBody(edgeLoopFrom: 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的分类比特掩码的否定(取反,即~运算符)进行运算,以“关闭”nodeA的比特掩码的位。例如,停止红圆与紫色正方形的碰撞:

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,红圆不再与类别为....0001(紫色正方形)的物体碰撞。

您可以直接设置collsionsbitMask而不是关闭单个位:

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

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

蓝色正方形只会与类别为 ..01 或 ..10 的物体发生碰撞

可以在任何时候使用以下代码打开两个物体之间的接触或碰撞(不影响任何现有的接触或碰撞):

redCircle.physicsBody?.contactTestBitMask |= purpleSquareCategory

我们逻辑与redCircle的bitMask和purpleSquare的category bitMask以“打开”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继续进行好像什么都没有发生一样。如果您想让两个对象彼此弹开,它们必须都被告知与另一个物体碰撞:
blueSquare.physicsBody?.collisionBitMask = redCircleCategory
redcircle.physicsBody?.collisionBitMask = blueSquareCategory

然而,联系并不是单向的;如果您想知道物体A何时接触(联系)物体B,则只需在物体A上设置与物体B相关的接触检测即可。您无需为了物体A而在物体B上设置接触检测。

blueSquare.physicsBody?.contactTestBitMask = redCircleCategory

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

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