プログラミング

PHPポケモン「行動順判定+敵ポケモン攻撃編」25

PHP PHPポケモン ポケモン
PHPポケモン「行動順判定+敵ポケモン攻撃編」25

行動順の判定

ポケモンの行動順は以下の通りです。

  1. 技の優先度
  2. すばやさの実数値(補正有り)
  3. 同速の場合は50%の乱数

 

この順番で比較を行い、先行後攻を決めます。ただし、これは両者ともに攻撃を選択した場合のみです。アイテムの使用や交代は技よりも優先されますし(※一部技を除く)、にげるはそれよりも先に行動することになります。

現在はアイテムと交代システムは実装出来ていないため、技選択における行動順序の比較のみを行います。

 

相手ポケモンの行動

 先手と後手を決めるためには、相手にも技の選択をして貰う必要があります。なぜなら、まず「技の優先度」を比較しなければならないからです。そのためには、簡易ながらも相手ポケモン(CP)の行動AIが必要になります。

  

簡易行動AIの作成

 では、バトルコントローラーのアクションメソッドの最初に以下の記述を追加しましょう。

 

バトルコントローラー(/Classes/Controller/BattleController.php
/**
* アクション
*
* @param string $action
* @param mixed $param
* @return void
*/
private function action($action, $param)
{
    // 敵ポケモンの技をインスタンス化
    $e_move = $this->getInstance($this->aiSelectMove());

 

相手の技については、自分がどの行動を行なったとしても基本的には実行されます。なので、最初にAIを使って技を選択させ、インスタンスを作成しておきましょう。

新しく登場したaiSelectMoveというメソッドは新しくAI用のトレイトを作成してそこに作り込んでいきましょう。

 

AIトレイト(/Traits/Battle/EnemyAiTrait.php
<?php
// 敵ポケモンの行動AI
trait EnemyAiTrait
{
    /**
    * 技の選択
    *
    * @return string
    */
    protected function aiSelectMove()
    {
        // 技の一覧を配列形式で取得
        $move = $this->enemy
        ->getMove('array');
        // ランダムで1つ返却
        return $move[array_rand($move)];
    }
 
}

 

初代ポケモンでは、ポケモンのタイプ相性に合わせて技が選択されていましたが、そのせいで攻撃技ではなく変化技を連打してしまうなどといった現象が起こってしまい、一種の不具合とまで言われていました。PHPポケモンではそういったことを防ぐためにも、独自のAIを組み上げていきますが、非常に作り込みが重視される部分のため本格的な作り込みは後回しにします。

 

今回実装した技選択のAIは、array_randを使って技をランダムで1つ選ぶというだけのものです。getMoveではインスタンス化した技の一覧を返却していましたが、すべてをインスタンス化する必要はないのでプロパティに格納された状態(配列)のまま受け取れるように引数を設定しました。

 

Get格納トレイト
/**
* 覚えている技の一覧を取得する
* @param string (object|array)
* @return array
*/
public function getMove($param='object')
{
    switch ($param) {
        case 'object':
        // オブジェクトで返却
        // array_mapで配列内の技クラスをインスタンス化
        return array_map([$this, 'getInstance'], $this->move);
        case 'array':
        // 配列で返却(そのまま)
        return $this->move;
        default:
        // オブジェクト(デフォルト)
        return array_map([$this, 'getInstance'], $this->move);
    }
 
}

※今更ですが、残り技ポイントの設定が抜けていました。こちらを変更するとポケモンの技回りの処理の大幅変更が予想されるため、後ほど実装予定です。

 

先行の取得

 それでは行動順の判定を実装します。ダブルバトルが実装されていない関係上、先行か後攻かを判定できれば行動順を決めることができます。なので「自分が先行かどうか」を判定するためのメソッドを作成し、その結果に合わせた分岐を作成します。

 

バトルコントローラー(/Classes/Controller/BattleController.php
/**
* たたかう
*/
case 'fight':
// 自ポケモンの技をインスタンス化
$p_move = $this->getInstance($param);
// 行動順の判定
if($this->checkFirstMove($p_move, $e_move)){
    // 先行
    // 自ポケモンの攻撃
    $e_damage = $this->attack($this->pokemon, $this->enemy, $p_move);
    // 敵ポケモンの攻撃
    $p_damage = $this->attack($this->enemy, $this->pokemon, $e_move);
}else{
    // 後攻
    // 敵ポケモンの攻撃
    $p_damage = $this->attack($this->enemy, $this->pokemon, $e_move);
    // 自ポケモンの攻撃
    $e_damage = $this->attack($this->pokemon, $this->enemy, $p_move);
}
break;

※敵ポケモン同様に、自ポケモンの技のインスタンス化もコントローラー内で実行する仕様に変更しました

 

checkFirstMoveというメソッドを使い、自分が「先行かどうか」をtrueまたはfalseで判定します。true(先行)が返ってくれば自→敵という順番でattackを実行、false(後攻)であれば敵→自という順番でattackを実行するという単純な仕組みです。

attackメソッドを複数回使用することになるため、ダメージはresponseに格納せず返り値として受け取っています。

 

では、checkFirstMoveメソッドの処理をみてみましょう。

 

バトルコントローラー(/Classes/Controller/BattleController.php
/**
* 先手の判定
*
* @param object 自ポケモンの技 $p_move
* @param object 敵ポケモンの技 $e_move
* @return boolean (pokemon > enemy):true (pokemon < enemy):false
*/
private function checkFirstMove($p_move, $e_move)
{
    /**
    * 優先度の比較
    */
    // 判定
    if($p_move->getPriority() > $e_move->getPriority()){
        // 優先度が高い
        return true;
    }elseif($p_move->getPriority() < $e_move->getPriority()){
        // 優先度が低い
        return false;
    }
    /**
    * すばやさの比較
    */
    // 自ポケモンの素早さ(補正あり実数値)
    $p_speed = $this->pokemon
    ->getStats('Speed', true);
    // 敵ポケモンの素早さ(補正あり実数値)
    $e_speed = $this->enemy
    ->getStats('Speed', true);
    // 判定
    if($p_speed > $e_speed){
        // 素早さが上回っている
        return true;
    }elseif($p_speed < $e_speed){
        // 素早さが下回っている
        return false;
    }elseif($p_speed === $e_speed){
        // 同速(50%の乱数)
        if(random_int(0, 1)){
            return true;
        }else{
            return false;
        }
    }
}

 

優先度ランクの比較

 まず最初に、技の優先度をそれぞれgetPriorityで取得して比較します。

/**
* 優先度の比較
*/
// 判定
if($p_move->getPriority() > $e_move->getPriority()){
    // 優先度が高い
    return true;
}elseif($p_move->getPriority() < $e_move->getPriority()){
    // 優先度が低い
    return false;
}

 

ここで差があれば、その時点で行動順が決定します。もし優先度が同じであれば、素早さの判定をしなければならないため、elseを使用せずelseifで分岐するようにしておいてください。

 

素早さの比較

 次に素早さの比較です。

/**
* すばやさの比較
*/
// 自ポケモンの素早さ(補正あり実数値)
$p_speed = $this->pokemon
->getStats('Speed', true);
// 敵ポケモンの素早さ(補正あり実数値)
$e_speed = $this->enemy
->getStats('Speed', true);
// 判定
if($p_speed > $e_speed){
    // 素早さが上回っている
    return true;
}elseif($p_speed < $e_speed){
    // 素早さが下回っている
    return false;
}elseif($p_speed === $e_speed){
    // 同速(50%の乱数)
    if(random_int(0, 1)){
        return true;
    }else{
        return false;
    }
}

 

それぞれの素早さを補正値込みで取得し、その差を比較しています。こちらもelseifで判定しながら、同速($p_speed === $e_speed)であれば50%の乱数で判定しました。

※最後の判定はelseでも構いません。今回はわかりやすくするためにelseifを使って比較式を記述しました。

 

50%の乱数の求め方は、今までの用にmt_randを使っても構いませんが、PHP7からはrandom_intという暗号化でも使用できる疑似乱数生成方法があったのでそちらを採用しました。 

 

/2を求めるだけであれば、最小値(第1引数)を0、最大値(第2引数)を1でifに掛けるだけで判定することができます。

 

最終的な出力結果を確認するために、setResponseswitch外に移動させて返却データを増やしておきましょう。 

// 結果の返却
$this->setResponse([
    '受けたダメージ' => $p_damage ?? 0,
    'ランク' => $this->pokemon->getRank(),
], $this->pokemon->getName());
$this->setResponse([
    '受けたダメージ' => $e_damage ?? 0,
    'ランク' => $this->enemy->getRank(),
], $this->enemy->getName());

 

出力結果は以下の通りです。

 

  

敵ポケモンも素早さに合わせて攻撃を仕掛けてきましたね。ピカチュウのステータスに変化があることも確認ができます。

 

にげる失敗時の相手の行動

 それではにげる失敗時の行動についても合わせて設定しておきましょう。もし失敗した場合、相手から技を受けることになります。なので、相手の攻撃処理を追加しましょう。

 

バトルコントローラー(/Classes/Controller/BattleController.php
/**
* にげる
*/
case 'run':
// $this->run++;
if($this->checkRun()){
    unset($_SESSION['enemy']);
    unset($_SESSION['rank']);
    unset($_SESSION['run']);
    header("Location: ./home.php", true, 307);
    exit;
}
$this->setMessage('逃げられない!');
// 敵ポケモンの攻撃
$p_damage = $this->attack($this->enemy, $this->pokemon, $e_move);
break;

 

これで「にげる」に失敗すると、相手から技を受けることになります。

 

 

 

以下がバトルコントローラーの最終コードになります。

 

バトルコントローラー(/Classes/Controller/BattleController.php
<?php
require_once(__DIR__.'/../Controller.php');
require_once(__DIR__.'/../../Traits/Battle/AttackTrait.php');
require_once(__DIR__.'/../../Traits/Battle/EnemyAiTrait.php');
 
// バトル用コントローラー
class BattleController extends Controller
{
    use AttackTrait;
    use EnemyAiTrait;
 
    /**
    * 敵ポケモン格納用
    * @var object
    */
    protected $enemy;
 
    /**
    * 逃走を試みた回数
    * @var integer
    */
    public $run = 1;
 
    /**
    * @return void
    */
    public function __construct()
    {
        // 親コンストラクタの呼び出し
        parent::__construct();
        // 自ポケモンの格納
        $this->myPokemon($_SESSION['pokemon']);
        // 敵ポケモンの格納
        if(isset($_SESSION['enemy'])){
            $this->enemyPokemon($_SESSION['enemy']);
        }else{
            $this->enemyPokemon();
        }
        // ランク(バトルステータス)の引き継ぎ
        if(isset($_SESSION['rank'])){
            $this->pokemon
            ->setRank($_SESSION['rank']['pokemon']);
            $this->enemy
            ->setRank($_SESSION['rank']['enemy']);
        }
        // にげるの実行回数を引き継ぎ
        if(isset($_SESSION['run'])){
            $this->run = $_SESSION['run'];
        }
        // アクションが選択された
        if(isset($_POST['action'])){
            // アクションメソッドの実行
            $this->action(htmlspecialchars($_POST['action']), htmlspecialchars($_POST['param'] ?? null));
            return;
        }
    }
 
    /**
    * 自ポケモンの格納
    *
    * @param array $pokemon
    * @return void
    */
    private function myPokemon($pokemon)
    {
        $this->pokemon = new $pokemon['class_name']($pokemon);
    }
 
    /**
    * 敵ポケモンの格納
    *
    * @param array|null $pokemon
    * @return void
    */
    private function enemyPokemon($pokemon=null)
    {
        if(is_null($pokemon)){
            $this->enemy = new Fushigidane();
            $this->setMessage('野生の'.$this->enemy->getName().'が現れた!');
        }else{
            $this->enemy = new $pokemon['class_name']($pokemon);
        }
    }
 
    /**
    * アクション
    *
    * @param string $action
    * @param mixed $param
    * @return void
    */
    private function action($action, $param)
    {
        // 敵ポケモンの技をインスタンス化
        $e_move = $this->getInstance($this->aiSelectMove());
        switch ($action) {
            /**
            * にげる
            */
            case 'run':
            // $this->run++;
            if($this->checkRun()){
                unset($_SESSION['enemy']);
                unset($_SESSION['rank']);
                unset($_SESSION['run']);
                header("Location: ./home.php", true, 307);
                exit;
            }
            $this->setMessage('逃げられない!');
            // 敵ポケモンの攻撃
            $p_damage = $this->attack($this->enemy, $this->pokemon, $e_move);
            break;
            /**
            * たたかう
            */
            case 'fight':
            // 自ポケモンの技をインスタンス化
            $p_move = $this->getInstance($param);
            // 行動順の判定
            if($this->checkFirstMove($p_move, $e_move)){
                // 先行
                // 自ポケモンの攻撃
                $e_damage = $this->attack($this->pokemon, $this->enemy, $p_move);
                // 敵ポケモンの攻撃
                $p_damage = $this->attack($this->enemy, $this->pokemon, $e_move);
            }else{
                // 後攻
                // 敵ポケモンの攻撃
                $p_damage = $this->attack($this->enemy, $this->pokemon, $e_move);
                // 自ポケモンの攻撃
                $e_damage = $this->attack($this->pokemon, $this->enemy, $p_move);
            }
            break;
        }
        // 結果の返却
        $this->setResponse([
            '受けたダメージ' => $p_damage ?? 0,
            'ランク' => $this->pokemon->getRank(),
        ], $this->pokemon->getName());
        $this->setResponse([
            '受けたダメージ' => $e_damage ?? 0,
            'ランク' => $this->enemy->getRank(),
        ], $this->enemy->getName());
    }
 
    /**
    * 敵ポケモン情報の取得
    *
    * @return object
    */
    public function getEnemy()
    {
        return $this->enemy;
    }
 
    /**
    * にげる判定
    * F = (A × 128 / B) + 30 × C
    * Fを256で割った値 → 逃走成功率
    * @var A 味方ポケモンのすばやさ(ランク補正有り)
    * @var B 相手ポケモンのすばやさ(ランク補正無し)
    * @var C 逃走を試みた回数
    * @return boolean
    */
    private function checkRun()
    {
        // 味方の素早さを取得(ランク補正有り)
        $a = $this->pokemon
        ->getStats('Speed', true);
        // 相手の素早さを取得(ランク補正無し)
        $b = $this->enemy
        ->getStats('Speed');
        // 逃走を試みた回数
        $c = $this->run;
        // 計算式への当てはめ
        $f = ($a * 128 / $b) + 30 * $c;
        // 確率計算
        if(round($f / 256, 2) * 100 >= mt_rand(0, 100)){
            return true;    # 逃走成功
        }else{
            return false;   # 逃走失敗
        }
    }
 
    /**
    * 先手の判定
    *
    * @param object 自ポケモンの技 $p_move
    * @param object 敵ポケモンの技 $e_move
    * @return boolean (pokemon > enemy):true (pokemon < enemy):false
    */
    private function checkFirstMove($p_move, $e_move)
    {
        /**
        * 優先度の比較
        */
        // 判定
        if($p_move->getPriority() > $e_move->getPriority()){
            // 優先度が高い
            return true;
        }elseif($p_move->getPriority() < $e_move->getPriority()){
            // 優先度が低い
            return false;
        }
        /**
        * すばやさの比較
        */
        // 自ポケモンの素早さ(補正あり実数値)
        $p_speed = $this->pokemon
        ->getStats('Speed', true);
        // 敵ポケモンの素早さ(補正あり実数値)
        $e_speed = $this->enemy
        ->getStats('Speed', true);
        // 判定
        if($p_speed > $e_speed){
            // 素早さが上回っている
            return true;
        }elseif($p_speed < $e_speed){
            // 素早さが下回っている
            return false;
        }elseif($p_speed === $e_speed){
            // 同速(50%の乱数)
            if(random_int(0, 1)){
                return true;
            }else{
                return false;
            }
        }
    }
 
}

 

まとめ

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

今回のPHPポケモンは「行動判定」と「敵ポケモンの攻撃」についてご紹介しました。

現在はランダムの技選択のため戦略性は乏しいかも知れませんが、AIを作り込んでいけばそれだけでかなり高難度のバトルシステムを再現することも可能です。

ゲームづくりに興味がある人は、ぜひ参考にしてくださいね。

 

注目の記事

データ軽量化編 β版へ向けて  PHPポケモン 90
プログラミング
PHP,PHPポケモン,ポケモン
データ軽量化編 β版へ向けて PHPポケモン 90

β版の実装に向けて 大型アップデートにより、ある程度機能改善や実装箇所も増えてきましたが、それと同時に次の段階への移行が本格的に見えてきました。それがβ版です。   PHPポケモンは2020年12月現在α版となっており、完全な試作段階のWEBアプリケーションです。セーブ機能はなく、セッションの有効期...

HPバーアニメーション前編 サーバー側の対応 PHPポケモン 43
プログラミング
PHP,PHPポケモン,ポケモン
HPバーアニメーション前編 サーバー側の対応 PHPポケモン 43

動きのあるHPバーづくり それではデモ公開に先立ち、HPバーの作り込みをしていきたいと思います。 現在のPHPポケモンは、ダメージ計算などが終わった結果をすべて返却しているため、技選択をして次の画面に移行すると、HPが減った状態でスタートしていました。これでは、どの技でどれぐらいのダメージを与え、状態変...

フィールド効果技編(しろいきり) PHPポケモン 50
プログラミング
PHP,PHPポケモン,ポケモン
フィールド効果技編(しろいきり) PHPポケモン 50

フィールド効果技とは ポケモンの技の中には、ポケモンに対して状態変化や異常を与えるもの以外に、フィールド自体に効果を持たせるものがいくつかあります。PHPポケモンでは未実装ですが、そういったフィールド効果技はポケモンを交代したとしても場に効果が残り続けます。  場の状態(ポケモンwiki) https:/...

本当に価値のある宣伝方法 〜多くの人が実践している【間違った努力】とは〜
マーケティング
Facebook,note,Twitter,YouTube,集客
本当に価値のある宣伝方法 〜多くの人が実践している【間違った努力】とは〜

  YouTubeやTwitter、Facebookやnoteなど、いろんなサービスを使った集客方法があります。集客という視点ではなく、それ自体に人を集めたいという人も多いでしょう。   フォロワーやチャンネル登録者数を増やすために、多くの人が様々な取り組みをしていますが、その中でもひときわ意味のないことをただ繰り返して...

毎日継続をするためのコツ
雑記
毎日継続をするためのコツ

定期的にコラムを書きたくなるので、今回は「毎日継続をするためのコツ」というテーマで自分が意識していることや、感じたことを書き綴っていきます。 決して開発や業務で追われていたり、PHPポケモンの大幅見直しを迫られているわけではありません。   毎日継続するために  ブログや学習など、毎日継続...

放物線アニメーション編 PHPポケモン 81
プログラミング
PHP,PHPポケモン,ポケモン
放物線アニメーション編 PHPポケモン 81

ボールアニメーション 前回までに作成した捕獲判定処理を使って、ボールのアニメーションを作成します。 捕獲演出は以下の通りです。  味方側から相手に向かってボールを投げる 相手ポケモンの前でボールを開く 捕獲判定で算出した揺れ回数分ボールを揺らす   捕まえた際は、ボールの揺れをストップ...

【無料】早起きをして神戸へ行こう!「為になる雑談朝活」
イベント
三宮,朝活,神戸
【無料】早起きをして神戸へ行こう!「為になる雑談朝活」

  朝活を実施することになりましたので、その目的な概要をまとめました。 神戸三宮での開催を予定しておりますので、もしお近くにお住まいの方で日時が会いましたらご参加ください。 土日祝辺りで週1日程度の不定期開催を予定しています。学びにつながる、けど参加しやすい雑談形式ということを主としています...

PHPポケモン「バトルシステム編 〜バトル終了判定〜」28
プログラミング
JavaScript,jQuery,PHP,PHPポケモン,ポケモン
PHPポケモン「バトルシステム編 〜バトル終了判定〜」28

バトル終了判定 今回はバトル終了判定を実装しましょう。今までは「にげる」による戦闘離脱のみで、ひんし状態でも殴り合うことが出来たので、それを解消するためにも戦闘結果による判定を導入します。   ひんし状態の監視 まずは「ひんし」の監視です。現在は交代ポケモンどちらか一方がひんし状態になれば、そ...

カテゴリ

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