プログラミング

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

 

注目の記事

XserverにSequel ProでSSH接続する方法を4ステップでわかりやすく解説
ネットワーク
phpMyAdmin,Sequel Pro,SSH,Xserver,データベース,公開鍵,秘密鍵
XserverにSequel ProでSSH接続する方法を4ステップでわかりやすく解説

  AWSなどが注目される中、Xserver(エックスサーバー)は操作やセットアップが簡単で、ポートフォリオや業務、個人ブログ(WordPess)で使用している方に需要が高いサービスです。 そしてMacのPCを使っている方はデータベースの管理ツールとして、無料で使えるSequel Proが人気があり、多くのユーザーが使用し...

【Laravel】1対1リレーションをわかりやすく解説(belongsTo)
プログラミング
Laravel,PHP
【Laravel】1対1リレーションをわかりやすく解説(belongsTo)

Laravelの1対1リレーションのbelongsToについて、公式マニュアルでは専用単語ばかりでどうしてもわかりにくいと感じてしまっている方へ向けて、わかりやすく解説しました。 ※例で紹介しているコードについては、一部英語を日本語表記で使用している部分もありますので、コピペで使用する方は必要に応じて置き換え...

未経験からWeb系エンジニアとしてフリーランスになる現実的な方法教えます【軌道に乗れば起業も可能】
フリーランス
エンジニア,フリーランス,プログラミング学習,独立,起業
未経験からWeb系エンジニアとしてフリーランスになる現実的な方法教えます【軌道に乗れば起業も可能】

  セカンドキャリアとしてプログラミングやデザインを学習したい   そう考えてスクールを受講したり、独学で始める人が増えていますが、そのほとんどが実らずに挫折してしまいます。 ですが、学習方法と経験の積み方や職の選び方さえ間違えなければ、技術を身につけてフリーランスとして活動することも難しく...

知らなきゃ損!表記ゆれに強いSEO対策とは「オーガニック検索ユーザーを増やそう」
SEO対策
description,keyword,meta,title
知らなきゃ損!表記ゆれに強いSEO対策とは「オーガニック検索ユーザーを増やそう」

  「なかなかオーガニック検索の割合が増えない」 「どういったSEO対策をすればいいかわからない」   ブログやサイト運営者の方で、思うように成果が出ないと悩んでいる人は、まだまだSEO対策が十分ではない可能性があります。そして、SEO対策の中でも知らないと損する一つが「表記ゆれ」への対応です。 ...

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

  PHPを実践で使えるレベルまで押し上げるための初心者向け講座、第3回目は「関数」です。   第1回の変数、第2回の配列、そして今回の関数の3つをマスターできていれば、いざ業務に望んでも、ある程度通用すると思っておいて良いでしょう。  ※オブジェクト指向やクラスの理解、データベースの知識など、...

構成見直し編(クラス名) PHPポケモン49
プログラミング
PHP,PHPポケモン,ポケモン
構成見直し編(クラス名) PHPポケモン49

構成の見直し 今回は全体構成の見直しをします。ディレクトリについては変更ありませんが、ファイル名とクラス名について大幅な修正をかけていきます。   クラス名の重複回避 まず、クラス名の重複についてです。状態異常・状態変化の子クラスでは重複回避のために接頭語を着けて管理していましたが、他にも重複...

PHPポケモン(α)攻略wiki「最初のポケモン」
雑記
PHPポケモン,wiki,ポケモン
PHPポケモン(α)攻略wiki「最初のポケモン」

リリースから一ヶ月、遂にPHPポケモン(α)の攻略Wiki(仮)が公開です!   というのは大嘘で、内部の大幅変更の関係上、今回は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 独立 神戸 福祉 秘密鍵 翻訳 自己啓発 英語 見積書 計算機 読書 起業 迷惑メール 配列 銀の弾丸 集客 雑学力