経験値の獲得
今まではポケモンに直接経験値を与えるというチートびっくりの仕様でしたが、バトルシステムも終盤に差し掛かってきたので「倒したポケモンから経験値を取得する」というごく当たりまえの仕様を導入していきます。
基礎経験値の設定
では、経験値の計算式に入る前に、必要なパラメーターを1つ用意しましょう。それは「基礎経験値」についてです。
毎度ながらポケモンwikiさんご協力(というより参考)の元、実装していきます。
ポケモンにはそれぞれ基礎経験値というものが割り当てられており、そのポケモンを倒した際にその値を元に計算がなされます。同じレベルでも、キャタピーを倒すよりトランセルを倒した方が経験値が高いというのは、この基礎経験値が影響しているためです。
これはポケモンそれぞれに割り当てる必要があるので、各ポケモンのクラスにbase_exというプロパティを追加して、初期値に表の値をセットしていきましょう。
フシギダネ(/Classes/Pokemon/Fushigidane.php)
/**
* 基礎経験値
* @var integer
*/
protected $base_exp = 64;
サンドバック野生ポケモンとして固定出現してくださるフシギダネさんに基礎経験値である64を割り当てました。上書きすることはない値のため、取得用メソッドのみGet格納トレイトに追加しましょう。
Get格納トレイト(/Traits/Pokemon/GetTrait.php)
/**
* 基礎経験値を取得する
* @return integer
*/
public function getBaseExp()
{
return $this->base_exp;
}
これで経験値の計算に必要な値が揃いました。
獲得経験値の計算
次に経験値の計算です。
経験値の計算方法は、世代を経るごとに大きく修正が加えられています。ダメージ計算などとは違い、旧式の計算方法を再採用したり、さらに新計算方法に戻すなど時代の変化に合わせながら実験的に導入されているようにも感じさせます。
PHPポケモンでは、最新世代(第8世代)を参考にカスタムします。まずはwikiに掲載された経験値の仕様を確認してみましょう。
第五世代、第七世代、第八世代
こちらでは、倒したポケモンのレベルも経験値を変化させるパラメータとして登場する。
経験値 = (EXP × A × レベル補正^2.5 + 1) × B × C × D
- EXP : 倒されたポケモンのレベル × 倒されたポケモンの基礎経験値 ÷ 5
レベル補正 = (2L + 10) / (L + Lp + 10)- L : 倒されたポケモン
- Lp : 倒したポケモン
- A : (第五世代のみ)トレーナー戦なら1.5、それ以外は1。第七世代では常に1
- B : デルパワー・ロトポンの効果。けいけんちパワー+は1.2、++は1.5、+++は2.0、–は0.8、—は0.66、—は0.5。けいけんポンは1.5。
- C : 他言語版と交換したポケモンは1.7、同言語と交換したポケモンは1.5、おやが自分であれば1
- D : しあわせタマゴを持っていれば1.5、それ以外は1
経験値を貰えるポケモンが複数匹いる場合、
- 第五世代 : 「EXP × A」をその匹数で割り、その値を「EXP × A」の代わりに使ってそれ以降の計算を行う。
- 第七世代 : 匹数で割ることはなく、貰えるポケモンそれぞれに上記の計算を行う。
まず、Aの値についてですが、トレーナー戦か野生かで経験値の獲得量に変化があるようです。これは第5世代のみ導入されていた計算方式のため、関係なく1をセットするものとして不要とします。
次にB、C、Dについてです。デルパワーなど経験値補正仕様はもちろん実装せず、ポケモンの交換も今の所実装予定はなく、持ち物も現段階はスルーする予定なのですべて不要とします。
複数匹いる場合の割り振り処理についてですが、最新世代では所有ポケモン全員に獲得経験値が割り当てられるという仕様が採用されていました。ですが、これに関しては採用せず登場したポケモンだけが経験値をもらえるという方式を採用予定です。現在は複数匹所有のロジックがまだ出来ていませんので、一旦保留としましょう。
以上を踏まえ、今回採用する経験値の計算式は以下の通りです。
経験値 = (EXP × レベル補正^2.5 + 1)
それでは、前回追加したひんし判定(checkFainting)のメソッド内に経験値計算と獲得処理を追加しましょう。
チェック格納トレイト(/Traits/Battle/CheckTrait.php)
/**
* ひんし判定
*
* @param object $pokemon
* @return boolean
*/
protected function checkFainting($pokemon)
{
if($pokemon->getSa() === 'SaFainting'){
// ひんし状態
$this->setMessage($pokemon->getPrefixName().'は倒れた');
if($pokemon->getPosition() === 'friend'){
// 味方がひんし状態になった
$this->setMessage('目の前が真っ暗になった');
}else{
// 相手をひんし状態にした
// 経験値の計算
$exp = $this->calExp($this->pokemon, $this->enemy);
// 経験値をポケモンにセット(返り値をpokemonに格納)
$this->pokemon = $this->pokemon
->setExp($exp);
// ポケモンに溜まったメッセージを取得
$this->setMessage($this->pokemon->getMessages());
}
// バトル終了判定用メッセージの格納
$this->setMessage(' ', 'battle-end');
return true;
}else{
// ひんし状態ではない
return false;
}
}
相手ポケモンをひんし状態にした後、取得経験値をcalExpというメソッドを使い算出し、setExpを使って勝利ポケモンに経験値を与えています。
ではcalExpという経験値計算用のメソッドをバトルコントローラー内に作成しましょう。
バトルコントローラー(Classes/Controller/BattleController.php)
/**
* 経験値の計算
* (EXP × LM^2.5 + 1)
*
* @var EXP 倒されたポケモンのレベル × 倒されたポケモンの基礎経験値 ÷ 5
* @var LM レベル補正 (2L + 10) / (L + Lp + 10)
* @var L 倒されたポケモン($lose)のレベル
* @var Lp 倒したポケモン($win)のレベル
*
* @param object $win Pokemon
* @param object $lose Pokemon
* @return integer
*/
protected function calExp($win, $lose)
{
// EXP
$exp = $lose->getLevel() * $lose->getBaseExp() / 5;
// レベル補正
$lm = (2 * $lose->getLevel() + 10) / ($lose->getLevel() + $win->getLevel() + 10);
// 経験値の計算結果を整数(切り捨て)で返却
return (int)($exp * $lm ** 2.5 + 1);
}
ダメージ計算同様、作成した式に対して必要数値を当てはめていき、算出結果を整数として返却しています。これでバトルポケモンに合わせた経験値計算が実装できました。後ほど出力結果をみてみましょう。
経験値バーの実装
バトル中の経験値計算が完成したので、第2世代から採用された「経験値バー」についても作成していきましょう。バーのデザインは、HP同様にBootstrapのプログレスを採用します。
割合の算出
経験値バーの算出に必要なのは「現在のレベルで必要になる経験値をどれだけ満たしているか」です。
Get格納トレイト(/Traits/Pokemon/GetTrait.php)
/**
* 現在のレベルから次のレベルまでの経験値達成率
* @return integer
*/
public function getPerCompNexExp()
{
if($this->level >= 100){
return 100;
}
// 現在のレベルので経験値達成率を%の数値で返却(int)
// 現在のレベルで必要な経験値の超過分(余り)
$surplus = $this->exp - ($this->level ** 3);
// 現在のレベルから次のレベルに必要な経験値量
$need = ((($this->level + 1) ** 3) - ($this->level ** 3));
// 割合計算(%の数値で返却)
return $surplus / $need * 100;
}
大分考えたのですが、上記式を採用しました。
まず、レベルが100であれば算出は不要のため100(%)を返却します。0でも構いません。
まず、現在の達成率(現在のレベルで必要な経験値の超過分)を求めるために
現在の取得経験値 ― 現在のレベルで必要な経験値
の値を$surplusに格納しました。
次に、割合の分母となる値(現在のレベルから次のレベルまでに必要な経験値)を求めるために、
次のレベルに必要な経験値 ― 現在のレベルに必要な経験値
の値を$needに格納しました。
最後に割合を求める式として
$surplus / $need * 100
の値を返却しています。
べき乗の加算量を求める簡単な式があれば、お問い合わせで教えてください。高校でまともに数学を勉強していない自分には、現状これが精一杯でした。
あとは、算出した割合をプログレスのwidthにセットするだけです。最大値は100、最小値には0、現在の値にはwidthと同じ値をセットしておきます。
バトル画面(/battle.php)
<?php # 経験値バー ?>
<div class="progress" style="height:4px;">
<div class="progress-bar bg-primary" role="progressbar" style="width:<?=$pokemon->getPerCompNexExp()?>%;" aria-valuenow="<?=$pokemon->getPerCompNexExp()?>" aria-valuemin="0" aria-valuemax="100"></div>
</div>
プログレスの設置場所はHPバーの下です。
それでは経験値の獲得と合わせて出力結果を確認してみましょう。
無事ポケモンを倒すことで経験値を取得できました。これでバトルによる経験値獲得処理は完成です。
お詫び(不備修正)
第29回にもなると、そこそこ不備も見つかったので修正報告がてらお詫びします。おそらく今後も不備修正はボロボロ出てくることになると思われるので、その辺りはプログラミングの醍醐味、学習の一環としてお楽しみ下さい。とりあえずサーセン。
数値(計算結果)について
まず、ガバガバプログラミング言語のPHPを使っている関係上、数値の処理でそこそこ不備が見つかっています。例えば、HPで小数点込みの値が表示されるなどです。この辺りは引数で受け取る際に型指定するなどを入念にしていくことで対策します。
例:引数の型指定
/**
* 残りHPの計算
*
* @param string $param
* @param integer $val (default:0)
* @return integer
*/
public function calRemainingHp($param, int $val=0)
おかしなレベルアップ
必要経験値を満たせていないのにレベルアップしてしまうということに今回気づきました。確認したところ、経験値の取得処理で「次のレベルまでに必要な経験値」を取得経験値加算後に行なっていたことが原因でした。
Set格納トレイト(/Traits/Pokemon/SetTrait.php)
/**
* 経験値をセット(取得)する
* @param integer $exp
* @return object
*/
public function setExp($exp)
{
if(!is_numeric($exp)){
// 入力値のチェック
$this->setMessage('数値を入力してください', 'error');
return $this;
}
// 次のレベルに必要な経験値を取得
$next_exp = $this->getReqLevelUpExp();
// 経験値を加算
$this->exp += (int)$exp;
$this->setMessage($this->getNickname().'は経験値を'.$exp.'手に入れた!', 'success');
// レベル上限の確認
if($this->level >= 100){
return $this;
}
if($next_exp <= $exp){
$levelup = true;
/**
* 次のレベルに必要な経験値を超えている場合
*/
$this->actionLevelUp();
while($this->getReqLevelUpExp() < 0){
$this->actionLevelUp();
}
}
// 進化判定
if(isset($levelup) && isset($this->evolve_level) && ($this->evolve_level <= $this->level)){
return $this->evolve();
}else{
return $this;
}
}
その他諸々修正しています。
攻撃の流れ
攻撃の流れについてですが、今までattackメソッド完了後にひんし判定を行なっていましたが、その場合状態異常になってからひんしになるということが発生していました。
例:ひのこ→やけどした→倒れた
なので、再度見直してattack処理の中でひんし判定までの一連の流れを行うように修正しました。
攻撃用トレイト(/Trait/Battle/AttackTrait.php)
/**
* 攻撃
* (攻撃→ダメージ計算→ひんし判定)
*
* @param object $atk_pokemon
* @param object $def_pokemon
* @param object $move
* @return boolean (true:相手を戦闘不能にした)
*/
protected function attack($atk_pokemon, $def_pokemon, $move)
{
// 攻撃メッセージを格納
$this->setMessage($atk_pokemon->getPrefixName().'は'.$move->getName().'を使った!');
// タイプ相性チェック
$type_comp_msg = $this->checkTypeCompatibility($move->getType(), $def_pokemon->getTypes());
// 「こうかがない」の判定(命中率と威力がnullではなく、タイプ相性補正が0の場合)
if(!is_null($move->getAccuracy()) && !is_null($move->getPower()) && ($this->m === 0)){
// こうかがない
$this->setMessage($def_pokemon->getPrefixName().'には効果が無いみたいだ');
return;
}
// 命中判定
$hit = $this->checkHit($move->getAccuracy());
if(!$hit){
// 攻撃失敗
$this->setMessage('しかし攻撃は外れた!');
return;
}
// 必要ステータスの取得
$stats = $this->getStats($move->getSpecies(), $atk_pokemon, $def_pokemon);
// ダメージ計算
if($move->getSpecies() !== 'status'){
/**
* 物理,特殊技
*/
if(!is_null($move->getPower())){
// 急所判定(固定ダメージ技は判定不要)
$critical = $this->checkCritical($move->getCritical());
if($critical){
$this->setMessage('急所に当たった!');
}
}
// 乱数補正値の計算
$this->calRandNum();
// タイプ一致補正の計算
$this->calMatchType($move->getType(), $atk_pokemon->getTypes());
// ダメージ計算
$damage = $this->calDamage(
$atk_pokemon->getLevel(), # 攻撃ポケモンのレベル
$stats['a'], # 攻撃ポケモンの攻撃値
$stats['d'], # 防御ポケモンの防御値
$move->getPower(), # 技の威力
$this->m, # 補正値
);
// やけど補正
if(($move->getSpecies() === 'physical') && ($atk_pokemon->getSa() === 'SaBurn')){
// 物理且つやけど状態ならダメージを半減
$damage *= 0.5;
}
// タイプ相性のメッセージを返却
$this->setMessage($type_comp_msg);
}else{
/**
* 変化技
*/
$damage = 0;
}
// ダメージ計算
$def_pokemon->calRemainingHp('sub', $damage);
// ひんしチェック
if($this->checkFainting($def_pokemon)){
// 相手を「ひんし」状態にした
return true;
}else{
// 相手は「ひんし」状態ではない
// 追加効果
$move->effects($atk_pokemon, $def_pokemon);
// 追加効果のメッセージをセット
$this->setMessage($move->getMessages());
return false;
}
}
ただ、反動技(とっしん等)で両者が力尽きた場合などが加味されておらず、この辺りは近々まとめて行く予定です。一旦は応急処置として考えておいてください。
いきあたりばったりで開発を始めるとこういうことになるので注意しましょう。(戒め)
まとめ
いかがだったでしょうか。
今回のPHPポケモンでは「バトルによる経験値獲得」について、経験値の計算方法などをご紹介しました。
ゲームづくりに興味がある人、プログラミング学習に取り組んでいる人は、ぜひ参考にしてくださいね。