戦闘不能による交代
ポケモンが戦闘不能になった際、もし手持ちに戦える状態のポケモンが残っていれば「交代」か「逃げる」の2択になります。今までは手持ちポケモンが1匹のみで判定を行っていたので、今回は交代の選択肢も含めて勝負の判定の見直しをしていきます。
パーティーを含めた勝負判定
味方または相手のポケモンがひんし状態になると、コントローラーのjudgmentメソッドで勝ち負けの分岐を行っています。ここに、パーティーを含めた判定処理を加え、もしまだ戦える状態であれば選択肢が出るように新しい分岐を追加します。
バトルコントローラー用トレイト(/App/Traits/Controller/BattleControllerTrait.php)
/**
* バトル結果判定
* @return void
*/
private function judgment(): void
{
// 味方がひんし状態になった
if(battle_state()->isFainting('friend')){
// 戦闘可能なパーティーを確認
if(player()->isFightParty()){
// パーティーが残っている
if(battle_state()->isFainting('enemy')){
// 相手が瀕死状態 → バトル終了
$this->judgmentWin();
}else{
// 相手が瀕死状態ではない → ポケモン交代の確認
$msg_id = issueMsgId();
setMessage('次のポケモンを使いますか?', $msg_id);
// レスポンスデータをセット
setResponse([
'toggle' => 'modal',
'target' => '#'.$msg_id.'-modal'
], $msg_id);
// モーダル用のレスポンスをセット
setModal([
'id' => $msg_id,
'modal' => 'change-or-run'
]);
waitForceModal($msg_id);
}
}else{
// 全滅(負け)
$this->judgmentLose();
}
}else if(battle_state()->isFainting('enemy')){
// 相手がひんし状態になった(味方はひんし状態ではない)
// 勝ち
$this->judgmentWin();
}
}
/**
* バトル結果(負け)
* @return void
*/
private function judgmentLose()
{
// 全滅
setMessage(player()->getName().'は、目の前が真っ暗になった');
// バトル終了判定用メッセージの格納
setEmptyMessage('battle-end');
}
/**
* バトル結果(勝ち)
* @return void
*/
private function judgmentWin()
{
// 経験値の計算
$party = player()->getParty();
$order = battle_state()->getOrder();
// パーティー取得
$exp = $this->calExp(friend(), enemy());
// 経験値をポケモンにセット
$party[$order]->setExp($exp);
// 努力値を獲得
$party[$order]->setEv(enemy()->getRewardEv());
// もしポケモンが「へんしん状態」であれば変更後の状態を引き継ぎ
if(friend()->checkSc('ScTransform')){
friend()->judgmentTransform($party[$order]);
}
// 散らばったお金の取得
$money = battle_state()->getMoney();
if($money){
setMessage(player()->getName().'は、'.$money.'円拾った');
player()->addMoney($money);
}
// バトル終了判定用メッセージの格納
setEmptyMessage('battle-end');
}
もし味方ポケモンが瀕死状態になり、パーティーに戦える状態のポケモンが残っていれば、「逃げる」または「次のポケモンを選ぶ」の選択肢を表示したモーダルを起動させています。
モーダルは以下の通りです。
交代または逃げるの選択モーダル(/Resources/Partials/Battle/Modals/change-or-run.php)
<!-- Modal -->
<div class="modal fade" id="<?=$modal['id']?>-modal" tabindex="-1" role="dialog" aria-labelledby="<?=$modal['id']?>-modal-title" aria-hidden="true" data-keyboard="false" data-backdrop="static">
<div class="modal-dialog modal-dialog-centered modal-sm" role="document">
<div class="modal-content">
<div class="modal-body">
<p class="mb-0">次のポケモンを選びますか?</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-sm btn-success" data-toggle="modal" data-target="#party-modal">ポケモンを選ぶ</button>
<button type="button" class="btn btn-sm btn-danger" data-submit_remote="run">逃げる</button>
</div>
</div>
</div>
</div>
強制表示モーダル
選択しなければモーダルは原則閉じれないようにしていますが、もし更新等をされた場合は再度判定処理が行われてしまいます。これでは、画面の更新を行えば逃げられない相手であっても回数を重ねることで逃げられるようになり、ゲーム性が失われてしまいます。
これを防ぐためにも、強制的に選択を問うモーダルは「強制表示モーダル」として登録、更新後はモーダルを強制起動させる必要があります。
そのために、モーダルセット後に以下のメソッドを呼び出しています。
waitForceModal(識別ID);
強制表示させるとしても、その場で強制状態にすれば更新などイレギュラーな読み込み以外でもメッセージを無視して表示されてしまいます。それを防ぐためにも、通常セット後には強制表示を待機状態として用意、画面の読み込みが全て終わったあとに待機状態から強制表示へと切り替えます。
レスポンスクラス(/Classes/Response.php)
/**
* 強制表示モーダルの初期化
* @return void
*/
public function initForceModal(): void
{
unset($_SESSION['__data']['force_modal']);
}
/**
* 強制表示モーダルのセット
* @param id:string
* @return boolean
*/
public function setForceModal($id): bool
{
// 強制表示させるモーダルを取得
$key = array_search($id, array_column($this->modals, 'id'));
// 見つかればセッションへ格納
if($key !== false){
$_SESSION['__data']['force_modal'] = $this->modals[$key];
// ID重複回避のためモーダル内から取り除く
unset($this->modals[$key]);
return true;
}
return false;
}
/**
* 強制表示モーダルを待機状態にする(更新等された際に強制表示)
* @param id:string
* @return void
*/
public function waitForceModal($id): void
{
$this->wait_force_modal = $id;
}
/**
* 強制表示モーダルを待機状態にする(更新等された際に強制表示)
* @param id:string
* @return boolean
*/
public function setWaitForceModal(): bool
{
// 待機中の強制表示モーダルがあれば、セッションへ格納
if($this->wait_force_modal){
$this->setForceModal($this->wait_force_modal);
$this->wait_force_modal = '';
return true;
}
// 待機中なし
return false;
}
/**
* 強制表示モーダルの存在確認
* @return boolean
*/
public function isForceModal(): bool
{
if(isset($_SESSION['__data']['force_modal'])){
return true;
}
return false;
}
/**
* 強制表示モーダルの取得
* @return array
*/
public function getForceModal(): array
{
return $_SESSION['__data']['force_modal'] ?? [];
}
/**
* 強制表示モーダルの確認
* @return boolean
*/
public function isForceModalTarget($target): bool
{
if(
isset($_SESSION['__data']['force_modal']['existing_modal']) &&
$_SESSION['__data']['force_modal']['existing_modal'] === $target
){
return true;
}
return false;
}
クラス内保管では再読み込み時にデータが失われてしまうため、セッションを活用しています。待機状態で登録したモーダルは、画面最終でセッションへ移動させる必要があるので、コアファイルの最終部分で置き換え用のメソッド(setWaitForceModal)を起動しています。
これで、もし再読み込みをされても強制モーダルを起動することで選択肢を問うことができます。
強制モーダルはフッターで存在チェックをしてから読み込み、存在する場合はjsで強制起動させましょう。
フッター(/Resources/Partials/Layouts/Foot/footer.php)
# 強制モーダルの読み込み
if(isForceModal()){
$modal = getForceModal();
// 既存モーダルを使用しない場合は読み込み
if(isset($modal['modal'])){
include($root_path.'/Resources/Partials/'.getPageName(true).'/Modals/'.$modal['modal'].'.php');
}
echo '<input type="hidden" id="force-modal" value="'.($modal['existing_modal'] ?? '#'.$modal['id'].'-modal').'">';
}
共通JS(/Public/Assets/common.js)
/**
* 強制モーダルの起動
* @function modal
*/
var showForceModalInit = function(){
$(document).ready(function() {
var force_modal = $('#force-modal');
if(force_modal.length){
$(force_modal.val()).modal('show');
}
});
}
戦闘からの離脱
選択肢の1つである「逃げる」は、戦えるポケモンを選ばずにバトルを終了させることができます。ただし、通常時の逃げると同様で逃走判定が行われ、失敗すれば次のポケモンを選出しなければなりません。
このときの逃げる判定は、ひんし状態になったポケモンのステータスを参考に算出するため、RunServiceを少しカスタマイズすることで実装します。
逃げるサービス(/App/Services/Battle/RunService.php)
/**
* @return void
*/
public function execute()
{
// にげるのカウントを進める
battle_state()->run();
if($this->checkRun()){
// 逃走成功
setMessage('上手く逃げ切れた');
// バトル終了判定用メッセージの格納
setEmptyMessage('battle-end');
}else{
// 逃走失敗
$msg_id = issueMsgId();
setMessage('逃げられない!', $msg_id);
if(friend()->isFight()){
// 相手のターン処理
$this->enemyTurn();
}else{
// ひんし状態での逃走失敗
setResponse([
'toggle' => 'modal',
'target' => '#party-modal'
], $msg_id);
setModal([
'id' => $msg_id,
'existing_modal' => '#party-modal' # 既存モーダルの使用
]);
// 強制表示モーダルを待機状態にする
waitForceModal($msg_id);
// 判定不要処理
battle_state()->judgeFalse();
}
}
// バトルポケモンが瀕死状態なら、強制モーダルを初期化
if(!friend()->isFight()){
initForceModal();
}
}
もし味方が瀕死状態でこのサービスが呼び出された際は、ひんし状態での2択による処理ということが判別できます。その際は逃走失敗後に相手の行動を行わず、ポケモン選択のモーダルを強制表示待機状態でセットして返却しています。
サービス終了後、コントローラーでは瀕死状態をチェックして判定処理を行っています。このまま返却すれば再度判定処理が実施されてしまうので、回避用にバトル状態クラスへ判定不要フラグを追加、状態を確認してjudgementを制御しましょう。
バトル状態クラス(/Classes/BattleState.php)
/**==================================================================
* 判定確認用フラグ
==================================================================**/
/**
* 判定不要にする処理
* @return void
*/
public function judgeFalse(): void
{
$this->judgement = false;
}
/**
* 要判定にする処理
* @return void
*/
public function judgeTrue(): void
{
$this->judgement = true;
}
/**
* 判定有無の確認
* @return boolean
*/
public function isJudge(): bool
{
return $this->judgement;
}
次のポケモンを選ぶ
最後に今回の本題となるポケモンの選出についてです。ポケモンを選ぶためのモーダルは、パーティーモーダルをそのまま使用しています。
交代処理は通常時と同じくChangeServiceを活用できるように、こちらも味方が瀕死状態かどうかを確認することで、相手の行動が行われないようにします。
ポケモン交代サービス(/App/Services/Battle/ChangeService.php)
/**
* @return void
*/
public function execute()
{
// バリデーション
if(!$this->validation()){
return;
}
// 瀕死チェック後に交代処理を行う(friendのポケモンが入れ替わるため)
if(!friend()->isFight()){
// 瀕死状態からの交代
$this->change();
// モーダル初期化
initForceModal();
}else{
// 通常の交代処理
$this->change();
// 相手のターン処理
$this->enemyTurn();
}
}
それでは、ひんし状態から交代の流れを確認してみましょう。
ひんし後の判定から交代まで正常に機能していますね。これでポケモン交代の一連の流れが完成です。
まとめ
いかがだったでしょうか。
今回のPHPポケモンでは、「戦闘不能による交代」の実装方法をご紹介しました。
WEBプログラミングでフォームを活用する際には、画面移管による初期状態の生成が厄介なポイントです。これを解決するためには、全体の構成に気をつけることの他に、JSを主軸としたSPA(シングルページアプリケーション)による構築が候補となります。
現在プログラミング学習に取り組んでいる方は、ぜひ参考にしてみてくださいね。