バトル終了判定
今回はバトル終了判定を実装しましょう。今までは「にげる」による戦闘離脱のみで、ひんし状態でも殴り合うことが出来たので、それを解消するためにも戦闘結果による判定を導入します。
ひんし状態の監視
まずは「ひんし」の監視です。現在は交代ポケモンどちらか一方がひんし状態になれば、その時点でバトルが終了します。なので、攻撃→ダメージ計算→ひんし判定の流れを1セットとして処理を行い、その結果がtrue(相手をひんし状態にした)であれば処理を終了するという流れを作成します。
バトルコントローラー(/Classes/Controller/BattleController.php)
/**
* たたかう
*/
case 'fight':
// 自ポケモンの技をインスタンス化
$p_move = $this->getInstance($param);
// 行動順の判定
if($this->checkFirstMove($p_move, $e_move)){
// 先行
// 自ポケモンの攻撃
$e_fainting = $this->actionFightToFainting($this->pokemon, $this->enemy, $p_move);
if($e_fainting){
// 相手をひんし状態にした
break;
}
// 敵ポケモンの攻撃
$p_fainting = $this->actionFightToFainting($this->enemy, $this->pokemon, $e_move);
if($p_fainting){
// 味方がひんし状態になった
break;
}
}else{
// 後攻
// 敵ポケモンの攻撃
$p_fainting = $this->actionFightToFainting($this->enemy, $this->pokemon, $e_move);
if($p_fainting){
// 味方がひんし状態になった
break;
}
// 自ポケモンの攻撃
$e_fainting = $this->actionFightToFainting($this->pokemon, $this->enemy, $p_move);
if($e_fainting){
// 相手をひんし状態にした
break;
}
}
$this->setMessage('行動を選択してください');
break;
攻撃からの一連の流れをactionFightToFaintingというメソッドにまとめました。
バトルコントローラー(/Classes/Controller/BattleController.php)
/**
* 攻撃→ダメージ計算→ひんし判定
*
* @param object $atk
* @param object $def
* @param object $move
* @return boolean
*/
private function actionFightToFainting($atk, $def, $move)
{
// 攻撃処理
$result['damage'] = $this->attack($atk, $def, $move);
// ダメージ計算
$def->calRemainingHp('sub', $result['damage']);
// ひんしチェック
if($this->checkFainting($def)){
// 倒した
return true;
}else{
// 倒せていない
return false;
}
}
引数に合わせて行動を行い、その結果をtrueかfalseで返却します。trueを受け取ればそのタイミングで処理を終了させるため、fightの分岐でbreakを行います。これで戦闘不能になったのに相手に攻撃をしてしまうという流れを回避することができます。
次にactionFightToFaintingメソッド内で呼び出しているcheckFaintingのメソッドについてです。こちらで防御側のポケモンがひんしかどうかを判定します。check関係のメソッドが多くなってきたので、まとめてトレイトに格納しましょう。
Check格納トレイト(/Traits/Battle/CheckTrait.php)
<?php
// チェック関係格納トレイト
trait CheckTrait
{
/**
* にげる判定
* F = (A × 128 / B) + 30 × C
* Fを256で割った値 → 逃走成功率
* @var A 味方ポケモンのすばやさ(ランク補正有り)
* @var B 相手ポケモンのすばやさ(ランク補正無し)
* @var C 逃走を試みた回数
* @return boolean
*/
protected 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
*/
protected 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;
}
}
}
/**
* ひんし判定
*
* @param object $pokemon
* @return boolean
*/
protected function checkFainting($pokemon)
{
if($pokemon->getSa() === 'SaFainting'){
// ひんし状態
$this->setMessage($pokemon->getPrefixName().'は倒れた');
if($pokemon->getPosition() === 'friend'){
// 味方がひんし状態になった
$this->setMessage('目の前が真っ暗になった');
}else{
// 相手をひんし状態にした
// 経験値取得
$this->setMessage('経験値をゲットした');
}
// バトル終了判定用メッセージの格納
$this->setMessage(' ', 'battle-end');
return true;
}else{
// ひんし状態ではない
return false;
}
}
}
それではひんし判定の処理について見ていきましょう。
/**
* ひんし判定
*
* @param object $pokemon
* @return boolean
*/
protected function checkFainting($pokemon)
{
if($pokemon->getSa() === 'SaFainting'){
// ひんし状態
$this->setMessage($pokemon->getPrefixName().'は倒れた');
if($pokemon->getPosition() === 'friend'){
// 味方がひんし状態になった
$this->setMessage('目の前が真っ暗になった');
}else{
// 相手をひんし状態にした
// 経験値取得
$this->setMessage('経験値をゲットした');
}
// バトル終了判定用メッセージの格納
$this->setMessage(' ', 'battle-end');
return true;
}else{
// ひんし状態ではない
return false;
}
}
引数でチェックするポケモンを受け取り、その状態異常の状態からひんし状態かどうかを判別します。もし味方がひんし状態であれば「目の前が真っ暗になった」、相手がひんし状態であれば「経験値をゲットした」といったメッセージを返却しています。
※経験値計算処理は今回作成しません
どちらかがひんし状態になってすぐにバトル終了(画面移管)をするわけにはいきません。なので、バトルが終了したということをフロント側で受け取れるように空(空白が必要)メッセージにバトル終了を判定するためのパラメーターとしてbattle-endをセットして返却しておきます。
バトル終了処理
バトル終了処理は、前回実装したjQueryのメッセージアクションを活用します。まずはメッセージの出力部分から修正していきましょう。
バトル画面(/battle.php)
<div class="message-box border p-3 mb-3">
<?php foreach($controller->getMessages() as $key => list($msg, $status)): ?>
<?php $class = $key === $controller->getMessageFirstKey() ? 'active' : ''; ?>
<?php $last_class = $key === $controller->getMessageLastKey() ? 'last-message' : ''; ?>
<p class="result-message <?=$class?> <?=$last_class?> <?=$status ?? ''?>"><?=$msg?></p>
<?php endforeach; ?>
<span class="message-scroll-icon">▼</span>
</div>
ステータスをクラス名に書き出す仕様に変更しました。これでjQueryのでhasClassを使って判定することができます。前回idに格納していたlast-messageもクラスに変更しています。
※idにする必要性を感じなかったためです
リモートフォームの活用
bodyの最下部にjsでリモート操作する用の隠しフォームを用意しましょう。
<?php # 遠隔操作用隠しフォーム ?>
<form action="" method="post" id="remote-form">
<input type="hidden" name="action" id="remote-form-action">
</form>
それではjsファイルにバトル終了判定を追記しましょう。
メッセージ用js(/Resources/Assets/js/Battle/message.js)
/**
* 画面読み込み時の関数
* @function ready
* @return void
**/
var startInit = function(){
// 現在のメッセージ
var now = $('.result-message.active');
if((now.length === 0) || now.hasClass('last-message')){
doLastMsg();
return;
}
doNotLastMsg();
}
/**
* メッセージボックスクリック時の関数
* @function click
* @return void
**/
var clickMsgBoxInit = function(){
$('.message-box').click(function(){
// 現在のメッセージ
var now = $('.result-message.active');
// 最終メッセージかどうか確認
if((now.length === 0) || now.hasClass('last-message')){
doLastMsg();
return;
}
// 現在のメッセージのactiveを解除
now.removeClass('active');
// 次のメッセージにactiveを付与
var next = now.next();
next.addClass('active');
/**
* メッセージのステータスに合わせた分岐
**/
// バトル終了
if(next.hasClass('battle-end')){
$('#remote-form-action').val('end');
return $('#remote-form').submit();
}
// 最終メッセージ
if(next.hasClass('last-message')){
doLastMsg();
return;
}
// どれにも該当しない
doNotLastMsg();
});
}
last-messageの判定をhasClassに変更し、その判定の前にバトル終了判定をするためbattle-endの分岐を追加しました。
もしbattle-endのクラスが付与されているメッセージに進んだら、先程作成したリモート操作用フォームのinput actionにendをセットしてsubmit(送信)してくれるという仕組みです。
PHPポケモンという名称ですが、この辺りをPHPだけで再現するのは非常に手間なのでガッツリjs(jQuery)だよりです。縛り開発はDBだけで十分です。
最後にendアクションの分岐をコントローラーに追加しましょう。バトル終了処理はポケモンのインスタンス化など一連の流れがすべて不要になるので、コンストラクタの最初に記述します。
バトルコントローラー(/Classes/Controller/BattleController.php)
/**
* @return void
*/
public function __construct()
{
// バトル終了
if(isset($_POST['action']) && ($_POST['action'] === 'end')){
$this->endBattle();
}
// 親コンストラクタの呼び出し
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 void
*/
private function endBattle()
{
unset($_SESSION['enemy']);
unset($_SESSION['rank']);
unset($_SESSION['run']);
header("Location: ./home.php", true, 307);
exit;
}
ホームコントローラーにも、endアクションで移管してきた際の処理を追加しておきましょう。
ホームコントローラー(/Classes/Controller/HomeController.php)
/**
* アクション
*
* @param string $action(method_name)
* @param mixed $param
* @return void
*/
private function action($action, $param)
{
// リセットの処理
if($action === 'reset' || is_null($this->pokemon)){
header("Location: ./index.php", true, 307) ;
exit;
}
// にげるの処理
if($action === 'run'){
$this->setMessage('上手く逃げ切れた', 'success');
return;
}
// バトル終了
if($action === 'end'){
if($this->pokemon->getRemainingHp() <= 0){
// ひんし状態ならHPを全回復させる
$this->pokemon
->calRemainingHp('reset');
}
return;
}
// 呼び出せるメソッドか判別
if(is_callable([$this->pokemon, $action])){
// メソッド実行結果を$resultに格納
$result = $this->pokemon
->$action($param);
switch ($action) {
// 経験値の取得
case 'setExp':
$this->pokemon = $result;
break;
}
// Pokemonクラスに溜まったメッセージを取得
$this->setMessage($this->pokemon->getMessages());
}else{
$this->setMessage('このアクションは使用できません', 'error');
}
}
ひんし状態のままでは困るので、にげる判定で使用していた全回復処理を移し替えました。実際にバトルで動きを確認してみましょう。
無事バトルを終了することができました。あとは経験値の処理を作成し、特殊な技の判定を追加すればポケモンのバトルを成立させることができます。
まとめ
いかがだったでしょうか。
今回のPHPポケモンは「バトル終了判定」の実装方法についてご紹介しました。
これでバトル一連の流れが完成したことになります。大分遊べるレベルになったのではないでしょうか。
ゲームづくりに興味がある人、現在プログラミング学習に取り組んでいる方は、ぜひ参考にしてみてくださいね。