明滅するプログラマの思索

WEBエンジニアとして勤務している一介の男が、日々気づいたことをまとめるブログです

PHPでゲームのフラグを管理する1つの手法

ゲーム開発をすると、様々なフラグを管理しなくてはいけない場面が出てきます。
特定のイベントをクリアしているかどうか、イベントアイテムを持っているかどうか、特定の操作を行っているか…、さまざまな条件を用意し、スイッチを切り替えることでダイナミックな進行を表現できるようになるのです。
フラグの管理はどのようにするのが良いでしょうか。
RDBを利用して、正規化されたフラグを1つ1つ丁寧に格納するのも方法ではあるでしょう。
ただ、ゲームがアップデートを繰り返すような場合、当初に想定したフラグ数ではとても足りなくなる、そんな場合も考えられます。
その分だけDBのカラム数を増やしても良いですが、限界はあります。
今回は、1つの文字列を使ってフラグを管理する方法について、自分なりの解を以下にまとめてみました。

導入環境

ソフトウェア バージョン
PHP 5.6

※ PDO を利用しています。

仕様

  • フラグは1行の数値[0/1]からなる文字列により管理する
  • フラグそのものの管理は別テーブルとし、ID=文字列のうち何番目か、で判断する
<?php
class Flag
{
    // フラグの数
    const FLAG_COUNT = 255;

    private $user_id;
    private $flag_field;

    public function __construct($user_id)
    {
        $sql = "SELECT flag_field FROM flag WHERE user_id = :user_id";
        $sth = $dbh->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
        $sth->execute([':user_id' => $user_id]);
        $flag_fields = $sth->fetch();
        if (isset($flag_fields["flag_field"])) {
            $this->flag_field = $flag_fields["flag_field"];
        } else {
            $this->generate($user_id);
        }
        $this->user_id = $user_id;
    }

    public function enableFlag($flag_id, $enable = true)
    {
        $replace = $enable ? 1 : 0;
        $this->flag_field = substr_replace($this->flag_field, $replace, $flag_id - 1, 1);
        $sql = "UPDATE flag SET flag_field = :flag_field, update_at = NOW() WHERE user_id = :user_id";
        $sth = $dbh->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
        $sth->execute([':user_id' => $user_id, ':flag_field' => $this->flag_field]);
        return true;
    }

    public function isEnable($flag_id)
    {
        return substr($this->flag_field, $flag_id - 1, 1) == 1 ? true : false;
    }

    public function generate($user_id)
    {
        $this->flag_field = str_repeat('0', self::FLAG_COUNT);
        $sql = "INSERT INTO flag (user_id, flag_field, update_at) VALUES(:user_id, :flag_field, NOW())";
        $sth = $dbh->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
        $sth->execute([':user_id' => $user_id, ':flag_field' => $this->flag_field]);
        return true;
    }
}

上記クラスでは、フラグの個数を255個とし、初期値はすべてOFF(0)になっています。
フラグのON/OFF は enableFlag() メソッドを使い、フラグのチェックには isEnable() メソッドを使います。

<?php
/*
 * ユーザID:1 のフラグインスタンスを取得
 */
$flag = new Flag(1);

/*
 * フラグID:5 のフラグが立っているか確認
 */
if ($flag->isEnable(5)) {
    echo 'フラグON';
} else {
    echo 'フラグOFF';
}

/*
 * フラグID:10 のフラグをONにする
 */
$flag->enableFlag(10, true);