プログラミング

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つです。現在プログラミング学習に励んでいる人は、ぜひ参考にしてみてくださいね。

 

注目の記事

教師が勝ち組!?出会い&学習の超正統派マッチングサービスとは【ビジネス企画書】
ビジネスモデル
マーケティング
教師が勝ち組!?出会い&学習の超正統派マッチングサービスとは【ビジネス企画書】

  「出会いがない」   社会人になると多くの人が抱く悩みの1つです。職場の男女比率や年齢層が理由の人もいれば、同じ職場での出会いは求めていない人もいるでしょう。 今回はそんな「出会い」を解決するための企画「出会い&学習の超正統派マッチングサービス」について、企画案と考察をまとめました。   ...

引き継ぎ考慮のメッセージID重複回避編 PHPポケモン 58
プログラミング
PHP,PHPポケモン,ポケモン
引き継ぎ考慮のメッセージID重複回避編 PHPポケモン 58

今回のPHPポケモンでは内部の作り込みをしていきます。見た目への反映は無いので、プレイを楽しみにしている人や、ポケモンが好きで毎日チェックしてくれているような人は、ブラウザをバックしてもらって問題ありません。   それでは、前々回辺りから保留にしていた「メッセージIDに重複回避対策」についてです。 &...

EC-CUBE4内にWordPressを設置(共存)する方法
SEO対策,プログラミング
EC-CUBE4,PHP,Symfony,Twig,WordPress
EC-CUBE4内にWordPressを設置(共存)する方法

今回使用したバージョン EC-CUBE4.0.5 WordPress5.6 ※EC-CUBE4、WordPressのカスタマイズは自己責任でお願いします。また、現在稼働中のサイトで試す際には、必ずバックアップを取ってから行なうようにしてください。   EC-CUBE4とは ネットショップを作るとなれば、様々なサービスが出回っ...

ピカチュウから学ぶオブジェクト指向 〜レベルシステム導入編〜 3
プログラミング
PHP,PHPポケモン,オブジェクト指向,ポケモン
ピカチュウから学ぶオブジェクト指向 〜レベルシステム導入編〜 3

  大人気?シリーズ「ピカチュウから学ぶオブジェクト指向」、今回はレベルシステムの導入編です。   第1回(基礎編)、第2回(クラス継承編)で作成したPokemonとPikachuのクラスを使用するので、最初から学習したい人はぜひご参考ください。      レベルシステムの導入   ポケモンのゲ...

ポケモンセンター編 PHPポケモン 37
プログラミング
PHP,PHPポケモン,ポケモン
ポケモンセンター編 PHPポケモン 37

ポケモンセンター  バトルシステムを作る関係上、どうしてもダメージを受けることが多くなってきたので、そろそろポケモンセンターを建設(作成)します。 とは言っても、ポケモンセンターに行って交換やらボックス整理などができるわけではなく、ただ回復ポイントを設置するだけの簡単な処理です。   HPの回復 ...

動画にカラオケテロップを入れる編集方法【AfterEffectsで色変わりの文字】
動画編集
Adobe,AfterEffects
動画にカラオケテロップを入れる編集方法【AfterEffectsで色変わりの文字】

  動画に声と同じタイミングでテロップを入れたい カラオケのような文字はどうやっていれればいいの?   一見簡単に見えるものも、いざ導入しようとすればどうやればいいかわからない、そんなこと多いのではないでしょうか? 今回はAdobe AfterEffectsを使った方法をご紹介します。些細な編集が動画のク...

有料ノートが公開停止!?プラットフォーム依存の危険性とは
雑記
note,YouTube,ブログ
有料ノートが公開停止!?プラットフォーム依存の危険性とは

  「いままでのnoteがいきなり公開停止になった」 「YouTubeであげていた人気動画が削除された」   規約変更、違反などを理由にこういった被害は年々増えています。自覚して悪意あるコンテンツを作っているユーザーであれば当然の結果だと言ってしまえば済むことですが、努力して収益化する糸口を見つけ...

ダメージ固定技編(ちきゅうなげ・カウンター) PHPポケモン 41
プログラミング
PHP,PHPポケモン,ポケモン
ダメージ固定技編(ちきゅうなげ・カウンター) PHPポケモン 41

ダメージ固定技とは PHPポケモンでも作成したダメージ計算機能ですが、ポケモンの技の中にはそれを必要としない技がいくつかあります。それが「固定ダメージ技」です。 ポケモンwiki(ダメージ固定技) https://wiki.ポケモン.com/wiki/ダメージ固定技 ステータスに依存せず、わざ自体にダメージ量が決...

カテゴリ

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 独立 神戸 福祉 秘密鍵 翻訳 自己啓発 英語 見積書 計算機 読書 起業 迷惑メール 配列 銀の弾丸 集客 雑学力