Edited at

【ゲームで学ぶオブジェクト思考】HP(ヒットポイント)をどう作る?

More than 1 year has passed since last update.


免責事項

記事中のコードはc++を基本として書いていますが

あくまで考え方を示すサンプル的な立ち位置です。

動作確認はしていないので、コードがおかしいところは脳内補間していただければと思います。


HP(ヒットポイント)とは

ゲームでおなじみ、体力的なパラメータ。

ゲーム作りを始めた頃はこんな感じでHPを作りました。

int hp = 10;

// 5ダメージ与えるなら引き算
hp -= 5;

// 5回復するなら足し算
hp += 5;

HPといってもただの数値だし、int型の変数で管理しよ。

そんな感じで実装を進めましたがすぐにめんどくさくなってきました。


めんどくさくなってきたこと


ダメージ受けまくるとHPがマイナスになる。

int hp = 10;

// 10のダメージ
hp -= 10;

// 10のダメージ
hp -= 10;

// HPがマイナスならないようにしなきゃ。。。
if (hp < 0){
hp = 0;
}


回復すればするほど、体力が際限なく増えていく。

int hp = 10;

// 100回復
hp += 100;

// 体力の上限超えないようにしなきゃ。。
if (10 < hp){
hp = 10;
}

HPの最大値がマジックナンバーとかひどい。

この時点でレベルアップしてもHP上がらない仕様が完成。


残っている体力の割合計算が手間

int hp = 10;

// 5ダメージ
hp -= 5;

// 残り体力は50パーセントだけど、intだと0か1にしかならねぇ
float rate = hp / 10;

int同士の割り算の結果はint

キャストするなり、暗黙の型変換なりを意識しないといけない。


HPをint型の変数でやるのはダメ

共通処理は関数にすればいいけど

共通の特徴はクラスにした方がいい。


HPの特徴


  • 最大値がある。

  • 最大値より大きくはならない

  • 最小値がある

  • 最小値よりは小さくならない

  • floatのが使い勝手がいい

HPは範囲を持つ数値だ。

あれ、これってHPだけじゃなくてMPとか

基本的なステータス(攻撃力、防御力、素早さ、運、お金さえ)全般に言える事じゃない?


範囲を持つ数値というクラスを作ろう

#include <algorithm> // min,maxとか使いたい。


class ExNum
{
private:
float mMax; // 最大値
float mMin; // 最小値
float mPre; // 現在値

public:
// コンストラクタで初期化(不定値対策)
ExNum(float max = 0, float min = 0, float pre = 0)
:mMax(max), mMin(min), mPre(pre){}

// いろんなメソッドを作っていくよ
}

とりあえず最大値、最小値、現在値の3種類のメンバー変数を用意する。

ここにいろいろメソッドを作っていく。


最大値、最小値、現在値に値をセット、または取得するメソッドを作る。


// とりあえず最大値、最小値、現在値のゲッター
float getMax(){ return mMax; }
float getMin(){ return mMin; }
float get(){ return mPre; }

// 最大値をセットする時は最小値より小さくならないようにする。
// また現在値が最大値を超えているなら、最大値と同じにする。
void setMax(float value)
{
mMax = max(mMin, value);
if(mMax < get()) set(mMax);
}

// 最小値をセットする時は最大値より大きくならないようにする。
// あと、現在値が最小値未満になるなら、最小値と同じにする。
void setMin(float value)
{
mMin = min(value, mMax);
if(get() < mMin) set(mMin);
}

// 現在値をセットする時は最小値以上、最大値以下になるようにする。
void set(float value)
{
value = min(value, mMax);
value = max(value, mMin);
mPre = value;
}

現在値をset,getというシンプルなメソッドにしているのは

使うときにそっちの方が自然かなというだけです。

+や-などの演算子をオーバーロードするのもありかと思います。


使うときのイメージ

// 最大HP100で、HPも100、最小値は0で作成。

ExNum hp(0, 100, 100);

hp.set(hp.get() - 200); // 200のダメージ、でも0未満にはならない
hp.set(hp.get() + 200); // 200回復、でも100は超えない。

hp.setMax(hp.getMax() + 10); // 最大HPを10UP、Lvアップなんかこれでいけるね。

float rate = hp.get() / hp.getMax(); // 残りHPの割合も出せるね。

if(hp.get() <= hp.getMin()){} // 死んでる判定。

int型でやってた時よりもすっきりした、けど。。。

まだ使いにくいところがあ沢山ある。


  • ダメージや回復でいちいちhp.get()して計算するのだるい

  • 死んでる判定とかHPの割合とかもいちいち計算しなくてもいいよね?


HPをもっと便利に


最大値、最小値、現在値を追加するメソッドを作る。

// 現在値を追加、戻り値で最終的な値を返すと便利かな?

float add(float value)
{
set(get() + value);
return get();
}

// 最大値を追加
float addMax(float value)
{
setMax(getMax() + value);
return getMax();
}

// 最小値を追加
float addMin(float value){
setMin(getMin() + value);
return getMin();
}


HPが空とか、満タンとかを判定するメソッドを作る。

// HPが空です。

bool isEmpty(){
return (get() <= getMin()); // 最小値以下なら空だよね
}

// HPが満タンです
bool isFull(){
return (getMax() <= get()); // 最大値以上なら満タンだよね。
}


割合を計算してくれるメソッドを作る。

// 割合を取得

float rate(){
return (get() / getMax()); // 現在値 / 最大値
}


他にも考えればいろいろできそう


  • 体力を満タンにするvoid full()とか

  • 体力を空っぽにするvoid empty()とか

  • グラビデみたいな現在の体力の1/4ダメージを与えるみたいなのも、そんなメソッド作れるよね。


使うときのイメージ2

// 最大HP100で、HPも100、最小値は0で作成。

ExNum hp(0, 100, 100);

hp.add(-200); // 200のダメージ、でも0未満にはならない
hp.add(+200); // 200回復、でも100は超えない。

hp.addMax(10); // 最大HPを10UP、Lvアップなんかこれでいけるね。

float rate = hp.rate(); // 残りHPの割合も出せるね。

if(hp.isEmpty()){} // 死んでる判定。
if(hp.isFull()){} // 体力満タン、体力MAXの時だけ発動するスキルとかあるよね

こうなってくるとだいぶ使いやすくなってきた気がします。

そしてHPというのは様々な振る舞いを持っていることに気づきます。

ただの数値だけでなく、範囲内で増減するという特徴や

空になったり、満タンになったり、割合を求めたり。


まとめ

オブジェクト指向はよく現実的なモノに例えられて教えられる事が多いと思います。

実際そのイメージに近しい事をすることもあるとは思います。

ですが今回のテーマHPの場合はどうでしょうか

モノに例えて考えようとしてもなかなかイメージしにくいテーマな気がしますが

プログラミングにおいてはこれもオブジェクト指向です。

HPという1つのパラメータさえ、オブジェクトとして扱うのです。

たかが数字ですが、されど数字です

ただの数字と侮ることなかれです。

もちろん今回のHPの作り方が最善というわけではありません。

もっといいやり方や天才か!っていう方法もあるでしょう。

またそれがプログラミングの面白さであり、醍醐味である気もします。

長くなりましたが、自分の経験から得たオブジェクト思考的なものを書いてみました。

オブジェクト思考ってなんだよって思ってる人の一助になれたでしょうか

余計に混乱させたでしょうか?

わからないですが、一助になれたのであれば幸いです。