プログラミング

PHPポケモン「バトルシステム実装編〜ダメージ計算〜」20

PHP PHPポケモン ポケモン
PHPポケモン「バトルシステム実装編〜ダメージ計算〜」20

バトルシステムの実装

 今回はバトルシステムの中でもメインとなるダメージ計算と、命中判定の部分を実装していきます。

 

ダメージ計算

 ポケモンのダメージ計算は初代から現在までそこまで大きな変化はありません。最新世代ではダメージに関係する要素(アイテム等)が多く、それにより補正値の修正はありますが、基本的な計算は以下の式で成り立っています。

 (((レベル×2/5+2)×威力×A/D)/50+2)*M

 

以下が計算式で当てはめられる値になります。 

レベル → 攻撃ポケモンのレベル

威力 → 攻撃技の威力

A → 攻撃ポケモンの攻撃値(物理技:こうげき、特殊技:とくこう)

D → 防御ポケモンの防御値(物理技:ぼうぎょ、特殊技:とくぼう)

M → 補正値(タイプ相性補正、タイプ一致補正、急所補正、乱数補正)

 ※初代では急所補正はMに当てはまらず、レベルの後に乗算されますが、今回はMへ補正を入れます。また第2〜5世代までは2倍の補正が入っていましたが、こちらも最新世代を参考にして1.5倍の補正値とします。

 

ダメージ計算では、要素が多く複雑に見えてしまいますが、基本的には必要な値を算出して式へ当てはめるだけです。必要な値についても、補正値の要素が多いだけで他はステータスや技情報から参照するだけです。

むしろ、計算以上に処理の順番が重要になります。こちらは後日触れるとして、今回は必要最低限のダメージ計算だけを実装します。

 

必要値の算出

 まずは、ダメージ計算に必要なレベル(L)威力(P)攻撃値(A)防御値(D)補正値(M)attackメソッドで取得します。補正値に関しては前回作成したタイプ相性補正のみを仮で使用します。

 

攻撃判定用トレイト(/Traits/Battle/AttackTrait.php
<?php
trait AttackTrait
{
    /**
    * 省略記号
    *
    * @var L レベル
    * @var A 攻撃値
    * @var D 防御値
    * @var P 威力
    * @var M 補正値
    */
 
    /**
    * 攻撃する
    *
    * @param object $atk_pokemon
    * @param object $def_pokemon
    * @param string $move_class
    * @return array
    */
    protected function attack($atk_pokemon, $def_pokemon, $move_class)
    {
        // 技のインスタンスを取得
        $move = $this->getInstance($move_class);
        // 攻撃メッセージを格納
        $this->setMessage($atk_pokemon->getName().'は'.$move->getName().'を使った!');
        // タイプ相性チェック
        $type_comp = $this->checkTypeCompatibility($move->getType(), $def_pokemon->getTypes());
        // 技種類での分岐
        switch ($move->getSpecies()) {
            // 物理
            case 'physical':
            $atk = $atk_pokemon->getStats('Attack');
            $def = $def_pokemon->getStats('Defense');
            // タイプ相性のメッセージを返却
            $this->setMessage($type_comp['message']);
            break;
            // 特殊
            case 'special':
            $atk = $atk_pokemon->getStats('SpAtk');
            $def = $def_pokemon->getStats('SpDef');
            // タイプ相性のメッセージを返却
            $this->setMessage($type_comp['message']);
            break;
            // 変化
            case 'status':
            // ここに変化技の処理
            $atk = 0;
            $def = 0;
            break;
        }
      
    }

 

それではswitch部分から見ていきましょう。

// 技種類での分岐
switch ($move->getSpecies()) {
    // 物理
    case 'physical':
    $atk = $atk_pokemon->getStats('Attack');
    $def = $def_pokemon->getStats('Defense');
    // タイプ相性のメッセージを返却
    $this->setMessage($type_comp['message']);
    break;
    // 特殊
    case 'special':
    $atk = $atk_pokemon->getStats('SpAtk');
    $def = $def_pokemon->getStats('SpDef');
    // タイプ相性のメッセージを返却
    $this->setMessage($type_comp['message']);
    break;
    // 変化
    case 'status':
    // ここに変化技の処理
    $atk = 0;
    $def = 0;
    break;
}

 

物理技、特殊技によって参照するステータスが異なるため、技種別に合わせた分岐を作成しました。必要値通り、物理技なら「こうげき」「ぼうぎょ」、特殊技「とくこう」「とくぼう」を取得しています。

 

ポケモンのインスタンスに対して使用しているgetStatsのメソッドについては以下の用に修正を加えて、必要な値を取得できるようにしています。

 

Get格納トレイト(/Traits/Pokemon/GetTrait.php
/**
* ステータスの取得
*
* @param string|null
* @return array|integer
*/
public function getStats($param=null)
{
    // ポケモンのステータス(実数値)を計算して返却
    foreach($this->base_stats as $key => $val){
        /**
        * ステータスの計算式(小数点以下は切り捨て)
        * HP:(種族値×2+個体値+努力値÷4)×レベル÷100+レベル+10
        * HP以外:(種族値×2+個体値+努力値÷4)×レベル÷100+5
        */
        if($key === 'HP'){
            $correction = $this->level + 10;
        }else{
            $correction = 5;
        }
        $stats[$key] = (int)(($val * 2 + $this->iv[$key] + $this->ev[$key] / 4) * $this->level / 100 + $correction);
    }
    if(is_null($param)){
        // 指定がなければ全ステータスを返却
        return $stats;
    }else{
        // 指定されたステータスを取得
        return $stats[$param];
    }
}

 

引数を指定すれば、該当するステータスを返却するようにしています。これで、ステータスの実数値から必要な値が取得できます。

 

では、攻撃用トレイトに戻り、物理技の分岐を例に見てみましょう。

// 物理
case 'physical':
$atk = $atk_pokemon->getStats('Attack');
$def = $def_pokemon->getStats('Defense');
// タイプ相性のメッセージを返却
$this->setMessage($type_comp['message']);
break;

 

$atk(攻撃値)$def(防御値)にそれぞれ必要なステータスを当てはめています。その後、タイプ相性のメッセージをsetMessageを使ってコントローラーのメッセージとして格納しました。

 

物理技・特殊技の場合は一部を除いて相性判定が必要になりますが、ダメージを直接与えない変化技に関しては「こうかがない」を除いて判定は不要です。攻撃判定すべてを作り終える時点では仕様を変更する可能性がありますが、一旦仮として攻撃技が実行された後にメッセージの返却を行います。

 

次に変化技の分岐についてです。

// 変化
case 'status':
// ここに変化技の処理
$atk = 0;
$def = 0;
break;

 

変化技の判定は上2つと比べて少しことなります。今回はエラーが出ないように、一旦0をセットしてメッセージもなしで返却しています。

 

レベルと技威力はそれぞれのインスタンスから取得することができ、補正値も生成したタイプ相性補正のみを使用するので、これで一旦必要値の準備が整いました。

 

計算式への当てはめ

 それでは計算式への当てはめを行います。可視性を維持するためにも計算式用のメソッド(calDamage)を追加しましょう。

 

攻撃用トレイト(/Traits/Battle/AttackTrait.php
/**
* ダメージ計算(カッコ毎に小数点の切り捨てをする)
* floor(floor(floor(レベル×2/5+2)×威力×A/D)/50+2)*M
*
* @param integer $l     レベル
* @param integer $a     攻撃値
* @param integer $d     防御値
* @param integer $p     威力
* @param integer $m     補正値
* @return integer
*/
private function calDamage($l, $a, $d, $p, $m)
{
    // 計算式を当てはめる
    return floor(floor(floor($l * 2 / 5 + 2) * $p * $a / $d) / 50 + 2) * $m;
}

 

式はそのままです。カッコごとに小数点の切り捨てをしているようなので、カッコをfloor関数に置き換えています。あとは、引数で受け取った必要値を元の式の値として埋め込んでいるだけです。

必要値(引数)に間違いがなければ、これで正しいダメージが算出されるはずです。

 

では次に、メソッドの呼び出し(attackメソッド内)を作成しましょう。

/**
* 攻撃する
*
* @param object $atk_pokemon
* @param object $def_pokemon
* @param string $move_class
* @return array
*/
protected function attack($atk_pokemon, $def_pokemon, $move_class)
{
    // 技のインスタンスを取得
    $move = $this->getInstance($move_class);
    // 攻撃メッセージを格納
    $this->setMessage($atk_pokemon->getName().'は'.$move->getName().'を使った!');
    // タイプ相性チェック
    $type_comp = $this->checkTypeCompatibility($move->getType(), $def_pokemon->getTypes());
    // 技種類での分岐
    switch ($move->getSpecies()) {
        // 物理
        case 'physical':
        $atk = $atk_pokemon->getStats('Attack');
        $def = $def_pokemon->getStats('Defense');
        // タイプ相性のメッセージを返却
        $this->setMessage($type_comp['message']);
        break;
        // 特殊
        case 'special':
        $atk = $atk_pokemon->getStats('SpAtk');
        $def = $def_pokemon->getStats('SpDef');
        // タイプ相性のメッセージを返却
        $this->setMessage($type_comp['message']);
        break;
        // 変化
        case 'status':
        // ここに変化技の処理
        $atk = 0;
        $def = 0;
        break;
    }
    if($move->getSpecies() !== 'status'){
        // ダメージ計算※整数で取得
        $damage = (int)$this->calDamage(
            $atk_pokemon->getLevel(),   # 攻撃ポケモンのレベル
            $atk,                       # 攻撃ポケモンの攻撃値
            $def,                       # 防御ポケモンの防御値
            $move->getPower(),          # 技の威力
            $type_comp['m'],            # 補正値
        );
    }else{
        $damage = 0;
    }
    // ダメージを返却
    return $damage;
}

※タイプ相性での補正値返却キーをmに変更しました

 

switchの下が追記部分です。

まず、技種別による分岐をします。変化技であればダメージ計算は不要になるため、直接ダメージ($damage)に0をセットします。物理、特殊であれば、calDamageのメソッドに対して必要な引数を割り当てています。

※技の威力取得用メソッド(getPower)が抜けていたので、技クラス(親)に追加しておいてください

 

calDamageのメソッド内でfloorを使用している関係上、0ではありますが小数点第一位までの数値が返ってきます。なので、整数としてダメージ量を返却するためintを使って整数型になおしてから変数へ格納しています。

 

今回メッセージにはsetMessageを使用したので、与えるダメージ量だけを最終的な返り値としています。それに合わせてコントローラーを変更しましょう。

 

バトル用コントローラー(/Classes/Controller/BattleController.php
/**
* アクション
*
* @param string $action
* @param mixed $param
* @return void
*/
private function action($action, $param)
{
 
    switch ($action) {
        // にげる
        case 'run':
        unset($_SESSION['enemy']);
        header("Location: ./home.php", true, 307) ;
        exit;
        // たたかう
        case 'fight':
        $response = $this->attack($this->pokemon, $this->enemy, $param);
        $this->setResponse($response, 'damage');
        break;
    }
}

 

ダメージはメッセージで表示する必要がないので、setResponseを使って返却します。あとは、この値が正常に表示されているかを確認するだけです。

 

命中判定

 出力結果を確認する前に、合わせて命中判定も実装します。こちらも比較的簡単ではありますが、判定のタイミングに少し工夫が必要です。

 

まずはattackのメソッドに命中判定を追加しましょう。

 

攻撃用トレイト(/Traits/Battle/AttackTrait.php
/**
* 攻撃する
*
* @param object $atk_pokemon
* @param object $def_pokemon
* @param string $move_class
* @return array
*/
protected function attack($atk_pokemon, $def_pokemon, $move_class)
{
    // 技のインスタンスを取得
    $move = $this->getInstance($move_class);
    // 攻撃メッセージを格納
    $this->setMessage($atk_pokemon->getName().'は'.$move->getName().'を使った!');
    // タイプ相性チェック
    $type_comp = $this->checkTypeCompatibility($move->getType(), $def_pokemon->getTypes());
    // 命中判定
    $hit = $this->checkHit($move->getAccuracy());
    if(!$hit){
        // 攻撃失敗
        $this->setMessage('しかし'.$atk_pokemon->getName().'の攻撃は外れた!');
        return;
    }

--省略

 

命中判定は、タイプ相性判定〜攻撃種別の分岐の間に実行します。これは後ほど説明します。

 

では、攻撃判定を行っているcheckHitのメソッドを見てみましょう。

/**
* 命中判定
*
* @param integer|null
* @return boolean
*/
private function checkHit($accuracy)
{
    // nullの場合は命中率関係無し
    if(is_null($accuracy)){
        return true;
    }
    /**
    * 0〜100からランダムで数値を取得して、それより小さければ命中
    * 例:命中80%→mt_randで60が生成されたら成功、90なら失敗
    */
    if($accuracy >= mt_rand(0, 100)){
        return true;
    }
    return false;
}

 

パラメーターとして技の命中率を受け取り、それを元に攻撃が当たる(true)か、外れる(false)を返却します。

命中率が関係ない技(自分強化、必中技)であれば、引数にはnullが入っているため、問答無用でtrueを返却します。

 

命中の判定については、mt_rand関数を使います。

/**
* 0〜100からランダムで数値を取得して、それより小さければ命中
* 例:命中80%→mt_randで60が生成されたら成功、90なら失敗
*/
if($accuracy >= mt_rand(0, 100)){
    return true;
}

 

mt_randで0〜100の間でランダム(疑似)の数値を取得します。その数値が命中率より大きければ攻撃失敗以下であれば攻撃が成功です。

【例】

ランダムで取得した値が90、命中率が80

→ 失敗

ランダムで取得した値が50、命中率が75

→ 成功

ランダムで取得した値が60、命中率が60

→ 成功

※ランダムで取得する値を「必要命中率」と考えると理解しやすいです。

 

命中とタイプ相性

 ここで、命中率判定のタイミングについて解説します。もしゴーストタイプにノーマル技を使用した場合、命中判定の結果ではなく「こうかがない」というメッセージが返却されます。これは、命中判定より前にタイプ相性判定が行われているということです。

なので、その間に「こうかがない」かどうかのチェックをいれます。

/**
* 攻撃する
*
* @param object $atk_pokemon
* @param object $def_pokemon
* @param string $move_class
* @return array
*/
protected function attack($atk_pokemon, $def_pokemon, $move_class)
{
    // 技のインスタンスを取得
    $move = $this->getInstance($move_class);
    // 攻撃メッセージを格納
    $this->setMessage($atk_pokemon->getName().'は'.$move->getName().'を使った!');
    // タイプ相性チェック
    $type_comp = $this->checkTypeCompatibility($move->getType(), $def_pokemon->getTypes());
    // 「こうかがない」の判定(命中率がnullではなく、タイプ相性補正が0の場合)
    if(!is_null($move->getAccuracy()) && ($type_comp['m'] === 0)){
        // こうかがない
        $this->setMessage($def_pokemon->getName().'には'.$type_comp['message']);
        return;
    }
    // 命中判定
    $hit = $this->checkHit($move->getAccuracy());
    if(!$hit){
        // 攻撃失敗
        $this->setMessage('しかし'.$atk_pokemon->getName().'の攻撃は外れた!');
        return;
    }

 

もし命中率がnullでなく、タイプ補正値が0(こうがかない)であれば、「こうかがない」という旨のメッセージを返却しています。

 

他の補正値計算をしていくことで、このあたりはもう少し正規化できるかも知れませんので、バトルシステムの攻撃判定がすべて実装できた段階でまとめていきます。

 

最終的な出力結果を見てみましょう。

 

 

 

 

それぞれタイプ相性に合わせたダメージ計算が行われていることがわかりますね。

補正値がすべて整っていない状態なので、完璧な値は算出出来ていませんが、比較的それに近い値が出ていることが分かれば問題ありません。

 

まとめ

 いかがだったでしょうか。

今回のPHPポケモンではダメージ計算の方法をご紹介しました。

このあたりは出来上がれば面白くなる部分であり、システムを作る側としても楽しい要素の1つです。現在プログラミング学習に励んでいる人は、ぜひ参考にしてみてくださいね。

 

注目の記事

PHPポケモン「引数メソッド実行編」デモ有り(御三家の追加)11
プログラミング
PHP,PHPポケモン,ポケモン
PHPポケモン「引数メソッド実行編」デモ有り(御三家の追加)11

  前回のPHPポケモンではメソッドを外部から実行できるようにコントローラー(インターフェース)を作成しました。なので、今回は引数が必要となるメソッドの実装に挑戦してみましょう。 最後にはデモページを準備しているので、気になった人はぜひ遊んでみてください。   第1回はコチラ     ...

保守っていくらで何をする?フリーランスWebエンジニア対象!保守業務について解説します
フリーランス
システムエンジニア,フリーランス,プログラマー
保守っていくらで何をする?フリーランスWebエンジニア対象!保守業務について解説します

  サイトの保守を頼まれたけど、具体的に何をやればいいの?   フリーランスの、特に独学やスクール上がりでなった人にとっては保守は何をすれば良いかわからないという悩みを抱えている人は多いのではないでしょうか。 システム開発会社や制作会社にいた人でも、どのぐらいの金額で請け負うのが良いか検...

ピカチュウから学ぶオブジェクト指向 〜レベルアップ編〜 5
プログラミング
PHP,PHPポケモン,オブジェクト指向,ポケモン
ピカチュウから学ぶオブジェクト指向 〜レベルアップ編〜 5

  第3回でレベルシステムを導入し、第4回では経験値システムの導入をしたので、今回はそれを合わせたレベルアップのシステムを導入します。 第1回から作成しているコードを使用しているので、もし最初から学習したい人は第1回の入門編をご覧ください。     レベルアップシステムの導入   レベル...

画像に文字スペースを確保する簡単テクニックを3つ教えます【サムネイル作りで大活躍】
デザイン
Illustrator,Photoshop,サムネイル,バナー
画像に文字スペースを確保する簡単テクニックを3つ教えます【サムネイル作りで大活躍】

  サムネイル用の画像に文字スペースが無くて困っている・・・   せっかくいい写真が撮れたり、フリー画像が見つかったとしても、文字を配置するスペースが確保できずにせっかくのオブジェクトに重ねてしまったり、断念して別の画像を使用するようなケースはかなり多いです。 ですが、せっかく良い画像が見つ...

大半のネットワークビジネスが成立しない理由とは【権利収入の落とし穴】
マーケティング
MLM
大半のネットワークビジネスが成立しない理由とは【権利収入の落とし穴】

  誘われたけど、欠点が上手く説明できない 権利収入って本当にもらえるの?   ネットワークビジネスと聞けば、良い印象を抱かない人がほとんどでしょう。ですが、「何故?」と問われて説明できる人は意外にも少数派です。 そして、SNSを積極的に活用している人ならば、一度は誘いを受けたことがあるの...

独立するならWordPress理解しておけばOK!プログラミングでフリーランスはこれ一つで成り立ちます
プログラミング
PHP,WordPress,フリーランス,独立
独立するならWordPress理解しておけばOK!プログラミングでフリーランスはこれ一つで成り立ちます

  プログラミングでフリーランスを目指すには、どの言語始めればいいの?   プログラミングの学習を始めたのに、それをどう活かせばよいか分からず、いざフリーランスで活動しようと思ってもイメージできずに断念してしまう人は多いです。 言語にも向き不向きがあるため、フリーランスとして活動するために向...

ピカチュウから学ぶオブジェクト指向 〜トレイト編〜 4
プログラミング
PHP,PHPポケモン,オブジェクト指向,ポケモン
ピカチュウから学ぶオブジェクト指向 〜トレイト編〜 4

  ピカチュウから学ぶオブジェクト指向の第4弾は「トレイト(trait)の活用」についてです。更に、レベルシステムを導入すれば欠かせない経験値システムも合わせて実装します。 第3回からの続きとなりますので、もし前回をまだ見ていない人は是非ご参考ください。   それでは今回もピカチュウと一緒に、...

【配列とは】初心者が最短でPHPを使えるようになるための実践的な学び方
プログラミング
foreach,PHP,配列
【配列とは】初心者が最短でPHPを使えるようになるための実践的な学び方

  初心者の方、今からプログラミングを始める方、HTMLとCSSの領域から抜け出せない人たちを対象にした、PHPをできるだけ使えるようになるための方法を紹介するコーナー。 栄えある第二回は【配列編】です。   第一回を飛ばしてこのページへに来てしまい「変数って何?」というレベルの人は、第一回の【変数編...

カテゴリ

SEO対策 イベント デザイン ネットワーク ビジネスモデル フリーランス プログラミング マーケティング ライティング 動画編集 雑記

タグ

5G Adobe AfterEffects AI ajax amazon Animate api artisan atom Automator AWS Bluetooth CSS CVR description EC-CUBE4 ECショップ ESLint Facebook feedly foreach function Google Google AdSense Honeycode htaccess HTML IEEE 802.11ax Illustrator Instagram IoT JavaScript jQuery jQuery UI keyword LAN Laravel Linux MacBook MAMP meta MLM MySQL NoCode note OS OSI参照モデル Paypal Photoshop PHP phpMyAdmin PHPポケモン PremierePro rss SEO SEO対策 Sequel Pro Skype SNS SSH Symfony TCP/IP title Toastr Trait Twig Twitter UCC V系 WAN WebSub Wi-Fi wiki Windows WordPress XAMPP xml Xserver YouTube YouTuber Zoom アーティスト アウトプット アクセス層 アニメーション アフィリエイト イーブイ インターネット インプット エンジニア オブジェクト指向 お金配り クリック単価 クリック数 コミュニケーション能力 コロナ コンサルティング サムネイル システムエンジニア スタートアップ スタイルシート スパム データベース ディープフェイク デザイナー デザイン テレワーク ナンパ ニュース ネットワークモデル ノマドワーク バナー ピカチュウ ビジネス フィード フリーランス ブロガー ブログ プログラマー プログラミング プログラミング学習 プログラミング教育 プロトコル ホームページ制作 ポケモン マークアップ マーケティング メール リモートワーク レンダリング 三井住友 三宮 仕事依頼 児童デイ 児童デイサービス 児童発達支援 公開鍵 初心者 助成金 勉強法 営業 広告 広告収入 必勝マニュアル 放課後等デイサービス 朝活 楽天 深層学習 無線LAN 独立 神戸 福祉 秘密鍵 翻訳 自己啓発 英語 見積書 計算機 読書 起業 迷惑メール 配列 銀の弾丸 集客 雑学力