【问题标题】:Game Engine Collison Bitmask... Why 0x01 etc?游戏引擎碰撞位掩码...为什么是 0x01 等?
【发布时间】:2016-01-20 15:52:40
【问题描述】:

在 Sprite Kit(iOS 开发)和 Cocos2d-x(我知道这几乎是 Sprite Kit 的灵感,因此他们使用很多相同的工具)中都遇到了这种情况,我最终决定弄清楚找出为什么会这样:

当使用物理引擎时,我创建了一个精灵,并为它添加了一个物理体。在大多数情况下,我了解如何设置类别、碰撞和接触位掩码,以及它们是如何工作的。问题是实际的位掩码数:

SpriteKit:

static const uint32_t missileCategory     =  0x1 << 0;
sprite.physicsBody.categoryBitMask = missileCategory;

Cocos2D-X:

sprite->getPhysicsBody()->setCategoryBitmask(0x01); // 0001

我完全不明白为什么在这两种情况下我都会写 0x01 或 0x1

就我的逻辑而言,如果让我说一个玩家类别、一个敌人类别、一个导弹类别和一个墙壁类别,那就只有 4 个类别。为什么不对类别使用字符串?或者甚至只是任何非 CS 人员都能理解的二进制数字,例如 0、1、2 和 3?

最后,我很困惑为什么有 32 个不同的类别可用?我认为一个 32 位整数有 0 到十亿个数字(当然是无符号数)。那么为什么我没有数十亿种不同的可能类别呢?

是否有某种我不理解的优化?或者这只是他们使用的旧约定,但不需要?或者有什么事情是只有 2 个学期的大学课程 CS 培训的人无法理解的?

【问题讨论】:

  • 您可能希望查看Apple's docs on collision bit masks,它有一个很好的示例。这是他们使用位掩码的部分,向上滚动查看他们将如何设置它们的位置。
  • 使用 0x1
  • 0b10000000000000000000000000000000、2147483648、0x80000000 或 0x1

标签: ios sprite-kit cocos2d-x bitmask


【解决方案1】:

位掩码的原因是它使您/程序能够轻松快速地计算两个对象之间是否发生碰撞。因此:是的这是某种优化。

假设我们有这三个类别

  • 导弹0x1 &lt;&lt; 0
  • 玩家0x1 &lt;&lt; 1
  • 0x1 &lt;&lt; 2

现在我们有一个Player 实例,它的类别设置为player。它的碰撞位掩码设置为missile | player | wall+ 而不是| 也可以),因为我们希望能够与所有三种类型发生碰撞:其他玩家、关卡墙壁和飞来飞去的子弹/导弹。

现在我们有一个Missile,类别设置为missile,碰撞位掩码设置为player | wall:它不会与其他导弹碰撞,而是会击中玩家和墙壁。

如果我们现在想要评估两个对象是否可以相互碰撞,我们将获取第一个对象的类别位掩码和第二个对象的碰撞位掩码,然后简单地 &amp; 它们:

上述设置在代码中如下所示:

let player : UInt8 = 0b1 << 0  // 00000001 = 1
let missile : UInt8 = 0b1 << 1 // 00000010 = 2
let wall : UInt8 = 0b1 << 2    // 00000100 = 4

let playerCollision = player | missile | wall // 00000111 = 7
let missileCollision = player | wall          // 00000101 = 5

后面的推理基本上是:

if player & missileCollision != 0 {
    print("potential collision between player and missile") // prints
}
if missile & missileCollision != 0 {
    print("potential collision between two missiles") // does not print
}

我们在这里使用了一些位运算,每个位代表一个类别。 您可以简单地枚举位掩码 1,2,3,4,5...,但是您无法对它们进行任何数学运算。因为您不知道作为类别位掩码的 5 是否真的是类别 5,或者它是类别 1 和类别 4 的对象。

但是,仅使用位我们可以做到这一点:就 7 的 2 的幂而言,唯一的表示是 4 + 2 + 1:因此,任何具有冲突位掩码 7 的对象都会与类别 4、2 和 1 发生冲突。一个位掩码为 5 的正是类别 1 和类别 4 的组合 - 没有其他方法。

现在,由于我们不枚举 - 每个类别使用一位,而常规整数只有 32(或 64)位,我们只能有 32(或 64)个类别。

看看以下更广泛的代码,它演示了如何在更一般的术语中使用掩码:

let playerCategory : UInt8 = 0b1 << 0
let missileCategory : UInt8 = 0b1 << 1
let wallCategory : UInt8 = 0b1 << 2

struct EntityStruct {
    var categoryBitmask : UInt8
    var collisionBitmask : UInt8
}

let player = EntityStruct(categoryBitmask: playerCategory, collisionBitmask: playerCategory | missileCategory | wallCategory)
let missileOne = EntityStruct(categoryBitmask: missileCategory, collisionBitmask: playerCategory | wallCategory)
let missileTwo = EntityStruct(categoryBitmask: missileCategory, collisionBitmask: playerCategory | wallCategory)
let wall = EntityStruct(categoryBitmask: wallCategory, collisionBitmask: playerCategory | missileCategory | wallCategory)

func canTwoObjectsCollide(first:EntityStruct, _ second:EntityStruct) -> Bool {
    if first.categoryBitmask & second.collisionBitmask != 0 {
        return true
    }
    return false
}

canTwoObjectsCollide(player, missileOne)     // true
canTwoObjectsCollide(player, wall)           // true
canTwoObjectsCollide(wall, missileOne)       // true
canTwoObjectsCollide(missileTwo, missileOne) // false

这里的重要部分是canTwoObjectsCollide 方法不关心对象的类型或有多少类别。只要您坚持使用位掩码,您就可以确定两个对象在理论上是否会发生碰撞(忽略它们的位置,这是另一天的任务)。

【讨论】:

  • 我无法理解这部分:“如果导弹和导弹碰撞!= 0 {...}”,以我的思维方式,导弹是 00010,导弹碰撞是 00101,这些都不是 0,所以这会发生……我怎么不理解这个?
  • @Confused 是按位与,按位与的结果将在两个输入都为 1 的地方为 1,所有其他位将为 0。在您的示例中,00010 和 00101 没有在同一位置有任何 1,因此与它们的结果为 0。
【解决方案2】:

luk2302's answer 很棒,但只是为了更进一步和其他方向......

为什么要使用十六进制表示法? (0x1 &lt;&lt; 2 等)

一旦您知道位 位置 是重要部分,它(如 cmets 中所述)只是样式/可读性的问题。你也可以这样做:

let catA = 0b0001
let catB = 0b0010
let catC = 0b0100

但是像这样的二进制字面量(就 Apple 工具而言)对于 Swift 来说是新的,并且在 ObjC 中不可用。

你也可以这样做:

static const uint32_t catA =  1 << 0;
static const uint32_t catB =  1 << 1;
static const uint32_t catC =  1 << 2;

或:

static const uint32_t catA =  1;
static const uint32_t catB =  2;
static const uint32_t catC =  4;

但是,出于历史/文化原因,程序员使用十六进制表示法来提醒自己/其他读者您的代码,特定整数文字的位模式比其绝对值更重要,这已成为一种惯例。 (另外,对于第二个 C 示例,您必须记住哪个位具有哪个位置值,而使用 &lt;&lt; 运算符或二进制文字您可以强调位置。)

为什么是位模式?为什么不___?

使用位模式/位掩码是一种性能优化。为了检查碰撞,物理引擎必须检查世界上的每一 pair 个对象。因为它是成对的,所以性能成本是二次方的:如果你有 4 个对象,你有 4*4 = 16 个可能的碰撞要检查... 5 个对象是 5*5 = 25 个可能的条件,等等。你可以用一些明显的排除(不用担心物体与自身发生碰撞,A 与 B 碰撞与 B 与 A 碰撞等),但增长仍与二次方成比例;也就是说,对于 n 个对象,您需要检查 O(n2) 可能的冲突。 (请记住,我们计算的是场景中的对象总数,而不是类别。)

许多有趣的物理游戏在场景中总共有超过 5 个对象,并且以每秒 30 或 60 帧的速度运行(或者至少希望如此)。这意味着物理引擎必须在 16 毫秒内检查所有可能的碰撞对。或者最好远小于 16 毫秒,因为它在发现碰撞之前/之后还有其他物理方面的事情要做,并且游戏引擎需要时间来渲染,而您可能需要时间来你的游戏里面也有逻辑。

位掩码比较非常快。类似面具比较的东西:

if (bodyA.categoryBitMask & bodyB.collisionBitMask != 0)

...是你可以要求 ALU 做的最快的事情之一——比如快一两个时钟周期。 (有人知道在哪里跟踪每个指令图的实际周期吗?)

相比之下,字符串比较本身就是一种算法,需要更多的时间。 (更不用说让这些字符串表达应该导致冲突的类别的组合的一些简单方法了。)

挑战

由于位掩码是一种性能优化,它们也可能是一个(私有)实现细节。但是大多数物理引擎,包括 SpriteKit 的,都将它们作为 API 的一部分。最好有一种方式在高层次上说“这些是我的类别,这些是它们应该如何交互”,并让其他人处理将该描述转换为位掩码的细节。 Apple's DemoBots 示例代码项目似乎有一个简化此类事情的想法(请参阅源代码中的 ColliderType)...随意使用它来设计自己的。

【讨论】:

    【解决方案3】:

    回答您的具体问题

    “为什么有 32 种不同的类别可用?我认为是 32 位 整数有 0 到十亿的数字(当然是无符号的)。所以为什么 难道我没有数十亿种不同的可能类别吗?”

    答案是类别总是被视为一个 32 位的位掩码,应该设置 ONLY ONE 位。所以这些是有效值:

    00000000000000000000000000000001 = 1 = 1 << 0
    00000000000000000000000000000010 = 2 = 1 << 1
    00000000000000000000000000000100 = 4 = 1 << 2
    00000000000000000000000000001000 = 8 = 1 << 3
    00000000000000000000000000010000 = 16 = 1 << 4
    00000000000000000000000000100000 = 32 = 1 << 5
    00000000000000000000000001000000 = 64 = 1 << 6
    00000000000000000000000010000000 = 128 = 1 << 7
    00000000000000000000000100000000 = 256 = 1 << 8
    00000000000000000000001000000000 = 512 = 1 << 9
    00000000000000000000010000000000 = 1024 = 1 << 10
    00000000000000000000100000000000 = 2048 = 1 << 11
    .
    .
    .
    10000000000000000000000000000000 = 2,147,483,648 = 1 << 31
    

    所以有 32 个不同的类别可用。但是,您的 categoryBitMask 可以有多个位集,因此确实可以是从 1 到 UInt32 的最大值的任何数字。例如,在街机游戏中,您可能有以下类别:

    00000000000000000000000000000001 = 1 = 1 << 0   //Human
    00000000000000000000000000000010 = 2 = 1 << 1   //Alien
    00000000000000000000000000000100 = 4 = 1 << 2   //Soldier
    00000000000000000000000000001000 = 8 = 1 << 3   //Officer
    00000000000000000000000000010000 = 16 = 1 << 4  //Bullet
    00000000000000000000000000100000 = 32 = 1 << 5 //laser
    00000000000000000000000001000000 = 64 = 1 << 6 //powershot
    

    所以一个人类平民的 categoryBitMask 可能为 1,人类士兵 5 (1 + 4),外星军官 6,普通子弹 16,导弹 80 (16 + 64),巨型死亡射线 96 等等.

    【讨论】:

      猜你喜欢
      • 2015-05-20
      • 2012-10-28
      • 1970-01-01
      • 2012-05-11
      • 2018-05-26
      • 1970-01-01
      • 2022-08-19
      • 1970-01-01
      • 2016-04-15
      相关资源
      最近更新 更多