前回に引き続き、状態異常チェックを実装します。
まず、前回実装した「ねむり」の処理についてですが、やはりターン数をセットして経過ターン数を引いていくという処理の方が解除率もゲーム再現になるので、まず修正をしておきます。サーセン。
チェック格納トレイト(/Traits/Battle/CheckTrait.php)
/**
* アタック前の状態異常チェック
*
* @param object Pokemon
* @return boolean
*/
protected function checkBeforeSa($pokemon)
{
if(empty($pokemon->getSa())){
// 状態異常にかかっていない
return true;
}
switch ($pokemon->getSa()) {
/**
* まひ
*/
case 'SaParalysis':
// 1/4の確率で行動不能
$paralysis = new SaParalysis;
if(random_int(1, 4) === 1){
$this->setMessage($paralysis->getFalseMessage($pokemon->getPrefixName()));
return false;
}
break;
/**
* こおり
*/
case 'SaFreeze':
// 1/5の確率でこおり解除
$freeze = new SaFreeze;
if(random_int(1, 5) === 1){
// こおり解除
$pokemon->releaseSa();
$this->setMessage($freeze->getRecoveryMessage($pokemon->getPrefixName()));
}else{
// 行動不可
$this->setMessage($freeze->getFalseMessage($pokemon->getPrefixName()));
return false;
}
break;
/**
* ねむり
*/
case 'SaSleep':
// ターンカウントが残っていれば行動不能
$sleep = new SaSleep;
// ターンカウントを進める
$pokemon->goSaTurn();
if($pokemon->getSa('turn') <= 0){
// ねむり解除
$pokemon->releaseSa();
$this->setMessage($sleep->getRecoveryMessage($pokemon->getPrefixName()));
}else{
// 行動失敗
$this->setMessage($sleep->getFalseMessage($pokemon->getPrefixName()));
return false;
}
break;
/**
* ひんし
*/
case 'SaFainting':
return false;
break;
}
// 行動可
return true;
}
その他若干修正をかけていますが、メインの修正は前述したように「ねむり状態」のターン数部分です。ねむり状態をセットする際に、2〜4のターン数を確定させて、チェック時に毎回減算してき0ターンになれば解除という方法です。
攻撃技である「ねむりごな」の追加効果を見てみましょう。
ねむりごな(/Classes/Move/SleepPowder.php)
/**
* 追加効果
*
* @param array $args
* @return void
*/
public function effects(...$args)
{
/**
* @param Pokemon $atk 攻撃ポケモン
* @param Pokemon $def 防御ポケモン
*/
list($atk, $def) = $args;
// 相手をねむり状態にする(2〜4ターン)
$msg = $def->setSa('SaSleep', random_int(2, 4));
// メッセージをセット
$this->setMessage($msg);
}
おなじみrandom_intを使ってターン数をランダムでセットしています。これで、ねむるなどを使った際も同じ処理で判定することができます。
それでは今回のメインである状態異常チェックの後半部分に入りましょう。
状態異常チェック
行動前にチェックする状態異常は前回実装しましたね。今回は行動後に判定を行う状態異常です。該当するのは以下の3つです。
- どく
- もうどく
- やけど
それぞれ、ダメージを受けるといったものです。もちろん、これらのダメージによってひんしになる可能性もあるため、それぞれチェックを行なった後にダメージ計算、ひんし判定という流れも実装しなければなりません。
チェック格納トレイト(/Traits/Battle/CheckTrait.php)
/**
* アタック後の状態異常チェック
*
* @param object Pokemon
* @return boolean (false:ひんし)
*/
protected function checkAfterSa($pokemon)
{
if(empty($pokemon->getSa())){
// 状態異常にかかっていない
return true;
}
switch ($pokemon->getSa()) {
/**
* どく
*/
case 'SaPoison':
// 最大HPの1/8ダメージを受ける
$poison = new SaPoison;
// 小数点以下切り捨て
$damage = (int)($pokemon->getStats('HP') / 8);
if($damage){
// 最小ダメージ数は1
$damage = 1;
}
// メッセージ
$this->setMessage($poison->getTurnMessage($pokemon->getPrefixName()));
break;
/**
* もうどく
*/
case 'SaBadPoison':
// 最大HPの(ターン数/16)ダメージを受ける(最大15/16)
$bad_poison = new SaBadPoison;
// ターンカウントを進める
$pokemon->goSaTurn();
// 小数点以下切り捨て
$damage = (int)($pokemon->getStats('HP') / 16) * $pokemon->getSa('turn');
if($damage){
// 最小ダメージ数は1
$damage = 1;
}
// メッセージ
$this->setMessage($bad_poison->getTurnMessage($pokemon->getPrefixName()));
break;
/**
* やけど
*/
case 'SaBurn':
// 最大HPの1/16ダメージを受ける
$burn = new SaBurn;
// 小数点以下切り捨て
$damage = (int)($pokemon->getStats('HP') / 16);
if($damage){
// 最小ダメージ数は1
$damage = 1;
}
// メッセージ
$this->setMessage($burn->getTurnMessage($pokemon->getPrefixName()));
break;
}
// ダメージ計算
$pokemon->calRemainingHp('sub', $damage ?? 0);
// ひんしチェック(ひんしチェックとは逆の結果(boolean)を返却)
return !$this->checkFainting($pokemon);
}
それでは状態異常について1つずつ見ていきましょう。
どく
ターン終了ごとに、ダメージを受ける(第一世代では最大HPの1/16、第二世代以降は最大HPの1/8)。
小数点は切り捨て。ただし、ダメージの最小は1。
どく(ポケモンwiki)
毒のダメージは最大HPの1/8(切り捨て)、毎ターン受けることになります。
/**
* どく
*/
case 'SaPoison':
// 最大HPの1/8ダメージを受ける
$poison = new SaPoison;
// 小数点以下切り捨て
$damage = (int)($pokemon->getStats('HP') / 8);
if($damage){
// 最小ダメージ数は1
$damage = 1;
}
// メッセージ
$this->setMessage($poison->getTurnMessage($pokemon->getPrefixName()));
break;
最小ダメージは1となるので、もし計算結果が0であれば1ダメージと判定しています。ダメージ計算からひんし判定の流れは残りの2つと共通処理になるため、switchから抜け出した後に記述します。
// ダメージ計算
$pokemon->calRemainingHp('sub', $damage ?? 0);
// ひんしチェック(ひんしチェックとは逆の結果(boolean)を返却)
return !$this->checkFainting($pokemon);
もうどく
ターン終了ごとに、ダメージを受ける。受けるダメージは、ターンごとにHPの1/16、2/16・・・と増える(最高15/16)、
小数点は切り捨て。ただしダメージの最小は1。
もうどく(ポケモンwiki)
こちらも毒と同じように毎ターンダメージを受けるというものですが、経過ターンに合わせてダメージ量に変化があります。ここでターン数を使用します。
/**
* もうどく
*/
case 'SaBadPoison':
// 最大HPの(ターン数/16)ダメージを受ける(最大15/16)
$bad_poison = new SaBadPoison;
// ターンカウントを進める
$pokemon->goSaTurn();
// 小数点以下切り捨て
$damage = (int)($pokemon->getStats('HP') / 16) * $pokemon->getSa('turn');
if($damage){
// 最小ダメージ数は1
$damage = 1;
}
// メッセージ
$this->setMessage($bad_poison->getTurnMessage($pokemon->getPrefixName()));
break;
ねむり状態とは異なり、最初にターン数をデフォルト(0)でセットしておきます。毎ターンチェックを行う最初の段階でターンを進行させ、その数値を16で割った数にかけることでダメージ量とします。
ポケモンクラス(/Classes/Pokemon.php)
/**
* ターンカウントをすすめる(状態異常)
*
* @return void
*/
public function goSaTurn()
{
// 状態異常クラスを取得
$sa = $this->getSa();
switch ($sa) {
/**
* ねむり
*/
case 'SaSleep':
// 残ターン数を1マイナス
$this->sa[$sa]--;
break;
/**
* もうどく
*/
case 'SaBadPoison':
// 経過ターン数を1プラス(最大15)
if($this->sa[$sa] <= 14){
$this->sa[$sa]++;
}else{
$this->sa[$sa] = 15;
}
break;
}
}
ターンカウントを進めるメソッド(goSaTurn)はポケモンクラスに作成しています。ねむりでは減算、もうどくでは加算処理が必要になるため、分岐させてそれぞれに適した処理を割り当てています。もうどくの最大ダメージ量は15/16のため、ターン数の上限は15としています。
やけど
ターン終了時に、HPが減少する(第一世代・第七世代以降では最大HPの1/16、第二世代~第六世代は最大HPの1/8)。
小数点は切り捨て。ただしダメージの最小は1。
第二世代まで、ダメージ発生のタイミングはターン終了時ではなく行動直後(#備考も参照)。
やけど(ポケモンwiki)
やけどのダメージは最新世代を参考に1/16のダメージとしています。こちらは毒ダメージの処理と同じで、ダメージ量だけが異なります。
/**
* やけど
*/
case 'SaBurn':
// 最大HPの1/16ダメージを受ける
$burn = new SaBurn;
// 小数点以下切り捨て
$damage = (int)($pokemon->getStats('HP') / 16);
if($damage){
// 最小ダメージ数は1
$damage = 1;
}
// メッセージ
$this->setMessage($burn->getTurnMessage($pokemon->getPrefixName()));
break;
これで毎ターンの状態異常チェック用のメソッドが完成しました。
行動順判定の変更
実際に状態異常のメソッドを呼び出す前に、行動順判定の算出方法を見直しておきましょう。
状態異常も行動順で呼び出す必要があり、その度に2つの順番分の処理を記述していては可視性も保守性も悪くなってしまいます。なので、ダブルバトル等をもし実装した場合でも活用できるように、行動順で配列に格納するというメソッドを作成し、foreachで順番に処理ができるようにしましょう。
バトルコントローラー(/Classes/Controller/BattleController.php)
/**
* たたかう
*/
case 'fight':
// 自ポケモンの技をインスタンス化
$p_move = $this->getInstance($param);
// 行動順の判定
$order_array = $this->orderMove(
[$this->pokemon, $this->enemy, $p_move],
[$this->enemy, $this->pokemon, $e_move],
);
// 行動順にforeachでattackメソッドを実行
foreach($order_array as list($atk, $def, $move)){
if($this->attack($atk, $def, $move)){
// 相手をひんし状態にした
break;
}
}
// 行動順にforeachでcheckAfterSaとcheckAfterScを実行
foreach($order_array as list($atk, $def, $move)){
if(!$this->checkAfterSa($atk)){
// ひんし状態になった
break;
}
}
新しくorderMoveというメソッドを作成しました。これにポケモンの情報を配列として格納して、行動順に配列で受け取り($order_array)、foreachで処理をしています。
行動が終われば、再度foreachで順番に行動後の状態異常判定(checkAfterSa)を実行しています。
行動順判定用数値の算出
それでは新しく作成したorderMoveというメソッドの內部を見てみましょう。
バトルコントローラー(/Classes/Controller/BattleController.php)
/**
* 行動順の判定
* (行動順判定用数値を生成)
* @var 100万の位:優先度
* @var 10〜10万の位:すばやさ
* @var 1の位:乱数
* @param array [攻撃ポケモン, 防御ポケモン, 攻撃ポケモンの技]
* @return array [行動順判定用数値 => [攻撃ポケモン, 防御ポケモン, 技],...]
*/
private function orderMove(...$pokemons)
{
$results = [];
foreach($pokemons as list($atk, $def, $move)){
// 優先度のセット
$speed = $move->getPriority() * 1000000;
// 素早さ実数値の加算
$speed += $atk->getStats('Speed', true) * 10;
// 乱数の生成(同速判定用)
$key = $speed + random_int(0, 9);
// 重複回避
while(isset($results[$key])){
$key = $speed + random_int(0, 9);
}
// 配列へセット
$results[$key] = [$atk, $def, $move];
}
// 降順(行動が早い順)に並び替え
krsort($results);
// [行動順判定用数値 => [ポケモン => 技],...] の多次元配列で返却
return $results;
}
引数は可変長引数でポケモンの配列を更にまとめた配列として受け取っています。現在はダブルバトルの仕様は実装していないので不要ですが、今後柔軟に活用できるようにと作成しました。
今までは技の優先度が勝っていれば先行と単純に判定をしていましたが、これらをすべて数値化して判定を行います。
優先度 + 素早さ実数値 + 乱数
単純にこの計算をしてしまえば、正しい判定はできません。なので、優先度の高い順に上位の位を割り当てました。
すばやさには上限が設定されている。ランク補正・特性・もちもの・おいかぜの効果を受けてもすばやさは10000を超えることはない。8192以上になったすばやさは8192引かれ、すばやさ1809~8191のポケモンより行動順が遅くなる。
すばやさ(ポケモンwiki)
ポケモンの素早さの最大値は10000を超えることが無いらしいので、そのまま参考にして100万の位を優先度、実数値を10から10万の位、そして乱数を1の位に割当てました。乱数は、同速だった場合の判定用として使用します。
// 優先度のセット
$speed = $move->getPriority() * 1000000;
// 素早さ実数値の加算
$speed += $atk->getStats('Speed', true) * 10;
// 乱数の生成(同速判定用)
$key = $speed + random_int(0, 9);
// 重複回避
while(isset($results[$key])){
$key = $speed + random_int(0, 9);
}
素早さをそれぞれの値に割当てて計算、最後に乱数を割り当てて、それをキーにした配列を作成していきます。同速で乱数が同じとなれば上書きされてしまうので、重複回避用にwhileを使用しました。
あとは、キーの値で降順に並び替えをすれば、行動順に並び替えされた配列を返却することができます。
// 降順(行動が早い順)に並び替え
krsort($results);
// [行動順判定用数値 => [ポケモン => 技],...] の多次元配列で返却
return $results;
これで、より簡単に行動順でアクションを起こすことができるようになりました。出力結果を見てみましょう。
いろんな技を試さないとダメになったので、デバック用にミュウを作成しました。きっとゲームフリークスもこのために用意したんでしょう。調べていないので、その辺り気になった人は是非調べてみてください。
問題なく毒のダメージ判定が行われましたね。これで行動後の状態異常チェック処理は完成です。
まとめ
いかがだったでしょうか。
今回のPHPポケモンでは「行動後の状態異常の判定」についてご紹介しました。
ゲームづくりに興味がある人、現在プログラミング学習に取り組んでいる人は、ぜひ参考にしてみてくださいね。