ビットフラグを衝突判定を例に紹介する

2018年のAdventCalendar 1日目です。

と言いつつ12月1日にかけなくて2日になってしまいました。。!!しかも夜中!!!1!

シューティングゲームを作ったときに衝突判定でビットフラグというのを使ったので紹介したいなと思います。(みんな一般的に知っているものなんだろか。。)

通常Bool値を使ってフラグを管理すると思うのですが沢山のフラグを持っていると管理が大変ですしメソッドの引数として渡したりWebAPIのパラメータとして渡すのも大変です。そんな時に一つの値に複数のフラグを持たせることができるのがビットフラグです。


ビットフラグとはその名の通り2進数を使用してtrue(1)、false(0)を表します。例えば次のような値は一桁目のフラグが立った状態です。

0001

今回は衝突判定で解説していくので次のようなフラグを用意しておきます。

let FLAG_PLAYER =        0b00000001 // プレイヤー
let FLAG_PLAYER_BULLET = 0b00000010 // プレイヤー弾丸
let FLAG_ENEMY =         0b00000100 // 敵
let FLAG_ENEMY_BULLET =  0b00001000 // 敵弾丸
let FLAG_ITEM =          0b00010000 // アイテム


そして敵弾丸オブジェクトを作っておきます。フラグとしてENEMY_BULLETを設定しています。

let enemyBullet = EnemyBullet()
enemyBullet.flag = ENEMY_BULLET


そして「プレイヤーがぶつかったのが敵の弾丸だった場合プレイヤーのライフを1減らす」という処理を書いてみました。

class Player {
    /// ライフ
    var life = 10

    /// 物体と衝突した時に呼び出される
    /// object: 衝突してきた物体
    func collision(with ObjectInterface: object)  {
        if (object.flag & FLAG_ENEMY_BULLET) > 0 {
           player.life =- 1
        }
        if (object.flag & FLAG_ENEMY) > 0 {
            // 敵と当たった時の処理
        }
        if (object.flag & FLAG_ITEM) > 0 {
            // アイテムと当たった時の処理
        }
    }
}

衝突の判定にはAND(論理積)を使っています。 ANDはAとBが両方真(true)の場合に真になることを表します。表に表すと次の通り。(基本情報試験のテキストで見たことありますよね)

A B 出力
1 1 1
1 0 0
0 1 0
0 0 0


objectに敵弾丸が入ってきた場合は、object.flagとFLAG_ENEMY_BULLETをAND演算した結果、0よりも大きい数になったのでobjectが敵弾丸であるということが言えます。

    0001 1000 // object.flag
AND 0000 1000 // FLAG_ENEMY_BULLET
-----------------
    0000 1000 = 80より大きい数


上記の例では引数で渡されたobjectに「プレイヤーの弾丸」が入ってきた時、objectが敵の弾丸であるか、敵であるか、アイテムであるかを判定するまで処理を終えることができません。 イージーモードの道中であれば一つ一つのオブジェクトにこのような判定をしていても良いかもしれませんが某ゲームのこんな画面だった場合少しでも実行する処理を少なくしたいものです。

f:id:withoneAdventCalendar:20181202234130p:plain
20181201_1


なので次のように修正してみることにします。

class Player {
    /// ライフ
    var life = 10

    /// ★追加:衝突対象
    let collisionFlags = ENEMY | ENEMY_BULLET | ITEM // 0001 1100

    /// 物体と衝突した時に呼び出される
    /// object: 衝突してきた物体
    func collision(with ObjectInterface: object)  {
        // ★追加:衝突対象であるかを判定して対象でなければ
        // この先の処理を行わない
        if (object.flag & collisionFlags) == 0 {
            return
        }
        if (object.flag & FLAG_ENEMY_BULLET) > 0 {
           player.life =- 1
        }
        ・・・省略・・・
    }
}

プレイヤーと衝突可能な物体のフラグをcollisionFlagsとして持つように修正しました。 「ENEMY | ENEMY_BULLET | ITEM」(00011100)が入っています。 こうすることによって、objectにプレイヤーの弾丸が入ってきた場合はcollisionFlagsとAND演算をした結果、どこの桁もtrueにならないため衝突対象にないということになります。

    0001 1100 // collisionFlags
AND 0000 0010 // プレイヤーの弾丸
-----------------
    0000 0000 = 0

ビットフラグでの判定は処理が早いらしいのですが最近のパソコンやスマホでそこまで違いがあるのかなーという気もします。
また、余談ですがDBのカラムにフラグが増えてきてしまった場合にもこのビットフラグを使用することもあるようです、 ただ、intで格納されている値を、5 = 0101 = 1番目と3番目のフラグがONだな!とパッとわかるかといえばそうではないと思うので可読性が良くないのと、フラグが追加される分には簡単ですが廃止されるとややこしいといった点もありますしインデックスが効かないのではという懸念もあるため使いどころが限られるかもしれません。 (ビットフラグ用のインデックスがあるというのを誰かがTwitterでつぶやいていたのを見た気がしたのですが該当する資料が見つけられず。。ご存知の方いたらぜひ教えていただきたいです。)