Λlisue's blog

つれづれなるままに更新されないブログ

ゲームのフラグ管理はビット演算で効率かつ簡単に管理する

どうも更新をサボりまくりの有末です。 本日はゲームなどのフラグ管理に使えるビット演算について書きたいと思います。

進数

日本人の100人にアンケートを取れば、おそらく9の次は10と答える人が大多数だと思います。 ただしこれは厳密には間違っていて9の次が10になるのは10進数で物事を考えているからです。

進数というのは簡単に言うと「いつ桁があがるか?」という事です。 例えば普段使用している10進数であれば0から数えて10個目(9)までは1桁で表せますが 11個目(10)からは2桁になります。

なかなかイメージがし難いと思うので以下2進数、8進数、10進数、16進数を見て行きます。 桁がいつ増えるか?に注目してください

2進数 8進数 10進数 16進数
0 0 0 0
1 1 1 1
10 2 2 2
11 3 3 3
100 4 4 4
101 5 5 5
110 6 6 6
111 7 7 7
1000 10 8 8
1001 11 9 9
1010 12 10 A
1011 13 11 B
1100 14 12 C
1101 15 13 D
1110 16 14 E
1111 17 15 F
10000 20 16 10

まぁ2進数は 0 と 1 だけで数字を表しているということだけわかっていただければ結構 です。

ビット演算

さて2進数の説明も終わったのでビット演算について説明したいと思います。

ビット演算とは2進数で表された数値(ビットパターン)を操作する演算のことです。 主にANDORNOTという3つの演算から成り立ちます(XORとシフトはやや こしいのでこの記事では扱いません)。 それぞれどのような演算か見て行きましょう。

AND

AND演算は2つのビットパターンを取り、1つのビットパターンを返す演算子です。 各ビットにおいて双方が1の場合は1を、それ以外の場合は0を返します。 以下例です(1ビットの場合)。

1 AND 1 = 1
0 AND 1 = 0
1 AND 0 = 0
0 AND 0 = 0

またビット演算は各桁毎の演算になるのでビットパターンに対しては下記のように 働きます(筆算のように上下でANDを適用してください)。

     1100
 AND 1010
----------
     1000

OR

OR演算も2つのビットパターンを取り、1つのビットパターンを返す演算子です。 各ビットにおいてどちらかが1の場合は1を、どちらも0の場合は0を返します。 以下例です(1ビットの場合)。

1 OR 1 = 1
0 OR 1 = 1
1 OR 0 = 1
0 OR 0 = 0

なお、ビットパターンに対しては下記のように働きます。

     1100
  OR 1010
----------
     1110

NOT

NOT演算は1つのビットパターンを取り、1つのビットパターンを返す演算子です。 各ビットにおいて1の場合は0を、0の場合は1を返します。 以下例です(1ビットの場合)。

NOT 1 = 0
NOT 0 = 1

なお、ビットパターンに対しては下記のように働きます。

 NOT 1010
----------
     0101

ビット演算を使ったフラグ管理

数値をビットパターンで表すと、各桁が 0 か 1 で表されます。 したがって 0 を OFF、1 を ON と定義すると大量の状態(フラグ)を一つの数字として 管理することができます

具体的に考えます。 RPGなどにおいて、キャラクターごとに「眠り」や「麻痺」などの状態が存在します。 これを(オブジェクト指向を使わずに)4人パーティーで普通に管理すると以下のような コードになるでしょう(Python的仮想言語)。

player1_sleep = false       # 眠り
player1_palarysis = false   # 麻痺
player1_poison = false      # 毒
player1_excitation = false  # 興奮
player2_sleep = false
player2_palarysis = false
player2_poison = false
player2_excitation = false
player3_sleep = false
player3_palarysis = false
player3_poison = false
player3_excitation = false
player4_sleep = false
player4_palarysis = false
player4_poison = false
player4_excitation = false

この例では状態異常が4つだけなので何とかなりますが、数が増えると面倒ですね。 そんな時はビットパターンを使って状態を管理することを考えます。 以下のようなイメージです。 0はその桁の状態異常は無いことを表し、1はその桁の状態異常であることを表します。

                sleep palarysis poison excitation
player1_state =   0       0        0       0

このようにビットパターンの各桁を状態として定義しておくと、以下のように非常に簡単に 状態を変更できます。

フラグを立てる(状態異常を追加)

フラグを立てるにはOR演算子を使います。 フラグを立てたい桁が 1 のビットパターンをORすることで ステータス変数の中の数値がどのような数値であっても確実に任意の桁を 1 にすること ができます。

# 状態を初期化
player1_state = 0000
# 毒状態にする
# 0000 OR 0010 = 0010
player1_state = player1_state OR 0010
# 麻痺状態にする
# 0010 OR 0100 = 0110
player1_state = player1_state OR 0100

フラグを下げる(状態異常を削除)

フラグを下げるにはANDNOTを組み合わせます。 フラグを下げたい桁が 1 のビットパターンをNOTすることで フラグを下げたい桁以外がすべて1のビットパターンにします。 その後ANDをかけるとフラグを下げたい桁は? AND 0 = 0で確実に 0 になり、 他の桁はフラグが立っていた場合は1 AND 1 = 1、フラグが立っていなかった場合は 0 AND 1 = 0となるので確実に保存されます。

# 状態を初期化(麻痺・毒状態)
player1_state = 0110
# 毒状態を解除
# 0110 AND (NOT 0010) = 0110 AND 1101 = 0100
player1_state = player1_state AND (NOT 0010)
# 麻痺状態を解除
# 0100 AND (NOT 0100) = 0100 AND 1011 = 0000
player1_state = player1_state AND (NOT 0100)

フラグを判別する(状態異常か判別する)

フラグを判別するにはANDを使用します。 判別したい桁が 1 のビットパターンをANDするとフラグが立っていた場合は 1 AND 1 = 1でフラグが立っていなかった場合は0 AND 1 = 0となります。 また判別したい桁以外は 0 なので? AND 0 = 0となるのでANDをかけた ビットパターンと等しくなるかどうかで判別できます。

# 状態を初期化(麻痺・毒状態)
player1_state = 0110
# 眠りか?
# 0110 AND 1000 = 0000
sleep = player1_state AND 1000
if sleep == 1000:
    眠り
# 麻痺か?
# 0110 AND 0100 = 0100
palarysis = player1_state AND 0100
if palarysis == 0100:
    麻痺

このようにビット演算を使うことで簡単に大量のフラグを管理することができます。 またビット演算は単純な論理式で成り立つので処理も高速です。 RPGなどゲーム制作において知っていると便利な方法なのでぜひ頭の片隅においておいて ください。