はじめに

免責事項

記事中のコードはc++を基本として書いていますが
あくまで考え方を示すサンプル的な立ち位置です。
動作確認はしていないので、コードがおかしいところは脳内補間していただければと思います。

前に書いた記事

【ゲームで学ぶオブジェクト思考】HP(ヒットポイント)をどう作る?
よければこちらもご覧ください。

状態とは

キャプチャ.PNG

こんな感じのやつについてです。
毒ってると体力が徐々に減ったり、眠ってると行動とれなくなったり
というのを判断するために状態について考えていこう。

シンプルに作ってみる

ゲーム作りはじめのころはこんな感じで状態を作っていました。

各種状態のフラグを用意

bool isPalsy  = false; // 麻痺
bool isPoison = false; // 毒
bool isSleep  = false; // 睡眠
bool isAnger  = false; // 怒り
bool isSad    = false; // 悲しみ

麻痺ってる処理はこう

// 麻痺ったら、麻痺フラグをtrue
isPalsy = true;

// 麻痺ってるね?
if(isPalsy){
  // Yes I do!
}

毒ってる処理はこう

// 毒ったら、毒フラグをtrue
isPoison = true;

// 毒ってるね?
if(isPosion){
  // Yes I do!
}

いいかんじ、いけてるいけてる。

他にもありそうな処理を考えてみよう。

// 状態異常にかかってないよね?を調べるならこう
if(!isPalsy && !isPoison && !isSleep && !isAnger && !isSad) {
  // I am good!
}

// 万能薬を使った時は状態異常を全部直すぞ
isPalsy = isPoison = isSleep = isAnger = isSad = false;

やばい、これ新しい状態が増える度に修正入るパティーン

状態毎にいちいちフラグ作りたくないぞ

このままだと

  • 沈黙とか新しい状態増えるとフラグ新しく作らないといけない。
  • 万能薬の処理とか判定も書き直さないといけない。
  • バグらせる自信がつく。

こんな時はあれだな、あれ、BITフラグ

BITフラグとは?

これはオブジェクト思考ではなくて
プログラムのテクニック的な話ですが簡単に説明をしておきます。

char state = 0;

例えばこのstateですが、パソコンの内部ではどうなってるでしょうか?
はい、こうなってます。

キャプチャ2.PNG

パソコンは内部ではデータを0と1だけで表現しています。
つまるところ、char型の変数というのは0か1を入れられる箱が8個あるのです。
これってbool型のフラグが8個あるのと同じですよねっていう。

キャプチャ3.PNG

この1bitをフラグとして使おうっていうのがBITフラグです。
さて、という事でBITを扱う事を前提に状態を扱いやすいモノにしていこう。

状態をクラスにしよう

まずは状態(State)クラスを作るよ。
※なおbitの操作方法については本筋から外れるので割愛します。

class State 
{
private:
    char mFlag; // 状態を管理するフラグ(8bitなので8種類まで管理できる)

public:
    // コンストラクタではFlagを0に初期化(不定値対策)
    State() :mFlag(0) {}

    // ここから色んなメソッドを作っていくよ。
};

状態の種類を定義しよう。

// 状態の種類(列挙型使うとわかりやすいね)
struct Kind {
    enum {
        Palsy  = 1 << 0, // 00000001 麻痺
        Poison = 1 << 1, // 00000010 毒
        Sleep  = 1 << 2, // 00000100 睡眠
        Anger  = 1 << 3, // 00001000 怒り
        Sad    = 1 << 4, // 00010000 悲しみ
        All    = 0xFF,   // 11111111 全て
    };
};

状態の種類を列挙型で定義してみました。
それぞれ、bitを1桁ずつずらして1になるように。

状態を変更するメソッドを作っていこう。

// 状態をリセット(健康にする)
void reset() {
    mFlag = 0; // 0にするだけ
}

// 指定された状態をON(有効)にする
void on(char flag) 
{
    mFlag |= flag; // 論理和をとればいいね。
}

// 指定された状態をOFF(無効)にする
void off(char flag) 
{
    mFlag &= ~flag; // BIT反転(チルダ記号)させて論理積とればいいね。
}

状態を判定するメソッドを作っていこう

// 聞かれた状態と一致する?
bool is(char flag) 
{
    return (mFlag == flag); // 完全一致なら比較するだけだね。
}

// 聞かれた状態のいずれかが該当する?
bool either(char flag) {
    return (mFlag & flag) != 0; // 論理積の結果が0じゃないときだね
}

// 健康です。(悪いとこない)
bool isHealthy() {
    return is(0);
}

使うときのイメージ

麻痺らせたり、毒らせたり

// 状態を用意。
State state;

state.on(State::Kind::Palsy);  // 麻痺らせたぜ
state.on(State::Kind::Poison); // 毒らせたぜ

一発でいろんな状態にしたり

// 麻痺、毒、睡眠状態にしてやったぜ(bitなので論理和できる)
state.on(State::Kind::Palsy | State::Kind::Poison | State::Kind::Sleep);

// 全ステータス異常になぁれ
state.on(State::Kind::All);

状態を治してあげたり

state.off(State::Kind::Palsy);  // 麻痺解除だぜ
state.off(State::Kind::Poison); // 毒解除だぜ

// 万能薬のときはこれ。
state.reset();

状態を確認してあげたり

// 毒ですね?毒以外は大丈夫ですね?
if(state.is(State::Kind::Poison)) {
  // はい、毒だけです。。。
}

// 毒か麻痺か睡眠かのいずれかにかかってますね?
if(state.either(State::Kind::Poison | State::Kind::Palsy | State::Kind::Sleep)){
  // はい、どれかはわからないですがどれかにかかっています。。。
}

// めっちゃ元気ですか?悪いとこないですか?
if(state.isHealthy()){
  // Yes! I'm Healthy
}

新しい状態異常を追加したり

struct Kind {
    enum {
        Palsy  = 1 << 0, // 00000001 麻痺
        Poison = 1 << 1, // 00000010 毒
        Sleep  = 1 << 2, // 00000100 睡眠
        Anger  = 1 << 3, // 00001000 怒り
        Sad    = 1 << 4, // 00010000 悲しみ
+       Burn   = 1 << 5, // 00100000 火傷
        All    = 0xFF,   // 11111111 全て
    };
};

列挙型に追加するだけで済むね。

いい感じ?

だいぶ長くなりましたが、使いやすい状態クラスが出来てきました。
これにて完成!

としたいところですが、ただ状態をクラスにしただけで
オブジェクト思考的としてはまだ考える余地があります。

状態クラスをよく見てみよう

キャプチャ4.PNG

状態クラスはこんな感じです。
状態の種類はこんなのがあるよーっていう部分と
状態を覚えさせるためのスイッチ(フラグ)部分です。

正直このスイッチ部分って他でも使えると思いませんか?

状態とスイッチを分離しよう。

いままで状態クラスが持っていたBitの操作をしている部分を抜き出して
BitFlagを操作するだけのモノを作りました。

BitFlag(スイッチ)クラス
// BitFlagクラス
class BitFlag 
{
private:
    char mFlag;

public:
    // フラグリセット
    void reset() {
        mFlag = 0; // 0にするだけ
    }

    // BITフラグON
    void on(char flag) 
    {
        mFlag |= flag; // 論理和をとればいいね。
    }

    // BITフラグOFF
    void off(char flag) 
    {
        mFlag &= ~flag; // 反転させたBITと論理積とればいいね。
    }

    // 指定されたフラグと一致する?
    bool is(char flag) 
    {
        return (mFlag == flag); // 完全一致なら比較するだけだね。
    }

    // 指定されたフラグのいずれかが一致する?
    bool either(char flag) {
        return (mFlag & flag) != 0; // 論理積の結果が0じゃないときだね
    }
};

状態クラスの方は今まで行っていたBITの直接操作がなくなりました。

状態クラス
class State 
{
private:
    BitFlag mFlag;

public:
    // コンストラクタ
    State() :mFlag() {}

// 状態の種類(ビットシフトを使うとわかりやすいね)
struct Kind {
    enum {
        Palsy  = 1 << 0, // 00000001 麻痺
        Poison = 1 << 1, // 00000010 毒
        Sleep  = 1 << 2, // 00000100 睡眠
        Anger  = 1 << 3, // 00001000 怒り
        Sad    = 1 << 4, // 00010000 悲しみ
        All    = 0xFF,   // 11111111 全て
    };
};

    // ここから色んなメソッドを作っていくよ。
    void reset()           { mFlag.off(Kind::All);      } // 状態をリセット(健康にする)
    void on(char flag)     { mFlag.on(flag);            } // 指定された状態をON(有効)にする
    void off(char flag)    { mFlag.off(flag);           } // 指定された状態をOFF(無効)にする
    bool is(char flag)     { return mFlag.is(flag);     } // 聞かれた状態と一致する?
    bool either(char flag) { return mFlag.either(flag); } // 聞かれた状態のいずれかが該当する?
    bool isHealthy()       { return mFlag.is(0);        } // 健康です。(悪いとこない)
};

こうする事でスイッチ(BITフラグ関連の処理)と状態が分離されます。
スイッチは状態以外でも使えるモノになりました。

まとめ

ちょっと長くなりすぎました。
BITフラグを例にしてしまったのは失敗だったかも。

とはいえ、始まりはいくつかのbool変数しかなかったものが
最終的にここまで肥大化したわけです。

おそるべしオブジェクト思考、おそるべしプログラム。
しかしこれがプログラムの面白さであり、醍醐味であるとも思います。

1. 複数のフラグで状態管理する。
2. いろいろ管理がめんどくさいぞ!
3. 状態管理はBITフラグ使うとスマートに書けるじゃないの
4. 状態管理とBITフラグってそれぞれ別で考えられるよね
5. 状態管理するモノとBITを管理するモノを分けよう!

これまた状態管理するモノとBITを管理するモノってのが
オブジェクトなの?って感じですよね。
でもこれもオブジェクト思考。

お粗末様でした。

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.