プログラミング

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

 

注目の記事

起業・独立を考えている人に向けた具体的なアドバイス3選
雑記
起業・独立を考えている人に向けた具体的なアドバイス3選

  「どのタイミングで起業すべき?」 「会社を作るためにはどういったことをすればいいの?」   あなたはこんな悩みを抱えていませんか? 今回は、これから独立・起業をしようと考えている方や、そういった野望をいだいている人に向けて、自らの経験を元に具体的なアドバイスを3つピックアップしてご紹...

レベルアップ時のステータス表示編 PHPポケモン 48
プログラミング
PHP,PHPポケモン,ポケモン
レベルアップ時のステータス表示編 PHPポケモン 48

ステータスの表示 前回経験値バーのアニメーションとレベルアップ時の動的な変更を実装しましたが、レベルアップ時に表示されるステータスの実装は先送りにしていました。なので今回はそのステータス表示を作成しましょう。   ステータスはメッセージとして返却せずに、小モーダルを起動させるという方法で対応しま...

ナンパしてたら独立できた「人間力の鍛え方」
フリーランス
コミュニケーション能力,ナンパ
ナンパしてたら独立できた「人間力の鍛え方」

  経験談から、人間力を鍛える方法をご紹介します。 今回は「ナンパ」がテーマです。なので、少し男性目線の内容になります。 女性の方は、「男性はこうやって考えている人もいるんだ」といった参考にしてください。   ナンパなんて、と思う人もいるでしょう。 ハラスメント規制も強くなる現代では、安易な...

フリーランスが打ち合わせで押さえておきたい3つのポイント【収入アップします】
フリーランス
フリーランス,営業
フリーランスが打ち合わせで押さえておきたい3つのポイント【収入アップします】

  フリーランスになれば、デザイナーやプログラマーといった制作をメインとする仕事の人でもクライアントと打ち合わせをしなければいけません。 特に独立や起業前のキャリアとして営業職に縁がなかった人からすれば、打ち合わせが苦手な人は多いはずです。   今回は、そういった営業ベタでやり方が分からない...

人員配置計算機〜放課後等デイサービス・児童発達支援〜
プログラミング
JavaScript,児童デイ,児童発達支援,放課後等デイサービス,福祉,計算機
人員配置計算機〜放課後等デイサービス・児童発達支援〜

最近はずっとPHPポケモン開発にかかりっきりでしたが、そろそろ「役立つもの」を作りたくなったので、思いつきで作ってみました。 もちろん「汎用性」のあるものは、既に世の中に存在しているため、ありそうでなさそうなニッチなものを攻めてみます。   そんな中、今回挑んだのは「人員配置計算機」です。 ※実際に...

データベース定義は超重要!システム開発を始める前に知っておきたい構造と構成の考え方
プログラミング
MySQL,データベース,プログラミング学習
データベース定義は超重要!システム開発を始める前に知っておきたい構造と構成の考え方

  システムやアプリ開発をする場合、そのほとんどでデータベースを使用しますね。 それぞれのデータを連携させるためにも、その構造をどうするかは重要で、設計が甘ければシステムそのものの保守性はもちろん、想定していた仕組みを実現するのが難しくなることもあります。   今回は、これからデータベースを...

なぜ、お金を配るの?悪質メール・SNSを利用した詐欺的手法への対処法とは
ネットワーク
amazon,SNS,Twitter,お金配り,楽天,迷惑メール
なぜ、お金を配るの?悪質メール・SNSを利用した詐欺的手法への対処法とは

定期的に書きたくなるコラムのコーナー! 今回は、迷惑メールやSNSのDMを活用した悪質な勧誘、巷で流行っているお金配りを隠れ蓑のした巧妙な詐欺的手法などをいくつかご紹介、その対策・リクス回避の方法をまとめました。   まず結論からお伝えすると 「怪しいと思うなら手を出すな」 です。その詳細を知りたい...

OSI参照モデルとTCP/IP 〜ネットワークモデルとは〜【第3回 ド素人のためのネットワーク講座】
ネットワーク
OSI参照モデル,TCP/IP,ネットワークモデル,プロトコル
OSI参照モデルとTCP/IP 〜ネットワークモデルとは〜【第3回 ド素人のためのネットワーク講座】

  ド素人のためのネットワーク講座! 栄えある第3回は「ネットワークモデル」についてです。   横文字や英字が多くなってきたり、歴史的経緯が関係してきたりとややこしくなってくる部分ですが、ネットワークを理解するためには押さえておきたいポイントです。   プログラミングやシステムエンジニアとし...

カテゴリ

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