プログラミング

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ポケモン104
プログラミング
PHP,PHPポケモン,ポケモン
わざマシン編 作成 PHPポケモン104

わざマシンとは ポケモンはレベルアップ以外でも技を習得することができます。それが「わざマシン」というアイテムです。  わざマシン(ポケモンwiki) https://wiki.ポケモン.com/wiki/わざマシン   最新世代では「技レコード」というものが有り、使い切りとなっています。初代ではわざマシン自体も使い...

デザイン
サムネイル,デザイナー,バナー,広告
デザイナーにセンスは必要ない!誰でも作れるおしゃれなサムネイル

  「デザイナーになるためにはセンスが必要ですか?」   バナーづくりの小規模講習会を実施した際に、この質問した人がいました。 よくある質問なのですが、いつも決まって自分の回答は「NO」です。   実際に一流のデザイナーとして活躍するレベルまでいくには、確かにセンスと呼ばれているものが必要...

ネコにこばん編 PHPポケモン73
プログラミング
PHP,PHPポケモン,ポケモン
ネコにこばん編 PHPポケモン73

ネコにこばんとは 初代ポケモンでは、基本的にトレーナーとのバトルでしかお金を増やすことができず、もし手持ち金が0円の状態で金が必要なイベントが発生すると詰んでしまうという隠された落とし穴がありました。 ですが、初代でも唯一トレーナー意外からお金を得る方法があります。それが「ネコにこばん」という...

20代起業家が教える「やっといて良かった」3つのコト
雑記
起業
20代起業家が教える「やっといて良かった」3つのコト

  「起業するために何を準備すべき?」 「やっておいて良かったことはありますか?」   独立や起業をしようと志している人のほとんどが、こういった質問を投げかけてきます。自分も同じような悩みを持った立場の時には似た質問をしていたので、その真意はよくわかります。   今回はそんな悩みを抱えて...

【PHP7】はてな2つとは??Null合体演算子を使った存在チェック
プログラミング
PHP,プログラミング学習
【PHP7】はてな2つとは??Null合体演算子を使った存在チェック

  PHP Notice:  Undefined variable: 変数名 in /***/***.php on line 2   PHPで変数や対象のキーが存在しない配列を使おうとすれば、上記のようなエラーが吐き出されますね。PHP5.6までは初期値を設定したり、issetで判定したりしてそれを回避していましたが、2015年末にリリースされたPHP7からは新しくN...

PHPポケモン(α)第1回目の大型アプデ
プログラミング
PHP,PHPポケモン,ポケモン
PHPポケモン(α)第1回目の大型アプデ

第1回目 大型アップデートについて 本日、PHPポケモンの大型アップデートをしました。 今回のアップデートには全体的なデザインチェンジが含まれているため、今まで遊んでくれていた人も、新鮮な気持ちで初められることができるはずです。   デザインについては、PHPを使った開発をしている人からすれば馴...

お金を生む仕組みを理解しよう【知っておきたい3つのポイント】
マーケティング
お金を生む仕組みを理解しよう【知っておきたい3つのポイント】

  どんなビジネスが儲かるのか・・・   一度はこういった悩みを持ったことがあるのではないでしょうか。 それを知るためには、どういった要素がお金を生むために作用しているのかを理解しておく必要があります。 今回は「お金を生む仕組み」について、知っておいてためになる基礎的な部分をわかりやすく...

これってスパム?amazon・paypalを装う悪質メールの対処方法とは
ネットワーク
amazon,Paypal,Xserver,スパム,三井住友,楽天,迷惑メール
これってスパム?amazon・paypalを装う悪質メールの対処方法とは

定期更新、役立つコラムのコーナー!   今回の注目したのは「迷惑(悪質)メールについて」です。   以前はamazonや楽天を名乗る業者から届いた迷惑メールについて紹介、その対処方法について取り上げましたが、今回は更にレベルアップしたスパムがいくつか届いたので、それらも紹介がてら、騙されないため...

カテゴリ

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

タグ

5G Adobe AfterEffects AI ajax amazon Animate api artisan atom Automator AWS Bluetooth CSS CVR description EC-CUBE4 ECショップ ESLint Facebook feedly foreach fortify function Google Google AdSense Honeycode htaccess HTML IEEE 802.11ax Illustrator Instagram IoT JavaScript jetstream 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 独立 神戸 福祉 秘密鍵 翻訳 自己啓発 英語 見積書 計算機 認証 読書 起業 迷惑メール 配列 銀の弾丸 集客 雑学力