ポケモンの入れ替え
複数ポケモンの所有、そして並び替えの機能が整ったので、いよいよバトルでのポケモン交代機能を実装します。ポケモンがバトル中に交代する方法は大きく分けて以下の4つです。
- プレイヤー操作による交代
- ひんしによる交代
- 相手ポケモンの技による交代
- 味方ポケモンの技による交代
プレイヤー操作による交代
バトル操作画面で、パーティーからポケモンを選ぶことで交代が可能です。これが最も標準的な交代方法であり、今回のメイン実装部分となります。
ひんしによる交代
味方のポケモンが瀕死状態になった際に、次のポケモンを使うかどうかを問われます。このとき、味方が全滅していなければ選択肢は「にげる」か「交代」のどちらかになります。逃げるが失敗した際には、強制的に交代ポケモンを選ばせる必要があります。
相手ポケモンの技による交代
「ふきとばし」「ほえる」などの技による強制交代です。現在PHPポケモンでは野生ポケモンとのバトル機能しか実装されていないため、一旦この技による入れ替え機能は保留とします。
味方ポケモンの技による交代
「とんぼがえり」などの技を使用すると、相手にダメージを与え且つポケモンを交代することができます。ただし、初代の技で同様の効果をもつ技は存在していないため、こちらも一旦保留とします。
戦闘ポケモンの交代処理
まずはプレイヤー操作による「戦闘ポケモンの交代」について見ていきましょう。
パーティー一覧では、現在ポケモンをクリックすると詳細が表示されます。しかし、これを選択により「入れ替え」か「詳細(様子をみる)」のどちらかを選べるようにする必要があります。 なので、バトル画のパーティー画面にのみ表示されるボタンをフッター部分に追加します。
<?php # フッター(バトル画面) ?>
<?php if(getPageName() === 'battle'): ?>
<div class="modal-footer">
<?php foreach(player()->getParty() as $order => $party): ?>
<button class="btn btn-sm btn-success"
data-action="details"
data-toggle="modal"
data-dubble_modal="true"
data-target="#pokemon<?=$order?>-details-modal"
data-order="<?=$order?>"
style="display:none;">
様子を見る</button>
<?php endforeach; ?>
<button class="btn btn-sm btn-secondary" data-btn="default" data-action="details" disabled>様子を見る</button>
<form id="partner-change-form" method="post">
<?php input_token(); ?>
<input type="hidden" name="action" value="change">
<input type="hidden" name="order">
<button type="submit" class="btn btn-sm btn-secondary" data-selected="<?=battle_state()->getOrder()?>" disabled>入れ替える</button>
</form>
</div>
<?php endif; ?>
ポケモンの行選択に合わせて、該当するボタンを表示させていきます。「様子を見る」ではモーダルを起動させる必要があるため、dataプロパティが設定されています。こちらは定数のため、パーティー数ボタンを用意して表示非表示の判定で切り替えています。
※documentからの指定をすれば変数のように扱うことも可能です
交代ボタンについては、現在選出されているポケモンや、瀕死状態のポケモンには交代できないようにdisabledの判定を加えていきます。以下JavaScriptの処理です。
バトル画面用パーティーJS(/Public/Assets/js/Battle/party.js)
/*----------------------------------------------------------
// 初期化する関数
----------------------------------------------------------*/
/**
* ポケモンの選択
* @function on.click
* @return void
**/
var selectPartnerInit = function(){
$('[data-list="party"] > .table-selected-row').on('click', function(){
var order = $(this).data('order');
var fight = $(this).data('fight');
// 「入れ替える」の判定
var change_btn = $('#partner-change-form button[type="submit"]');
// 現在バトル中のポケモン番号異なり、戦闘可能
if(
order !== change_btn.data('selected') &&
fight
){
// 入れ替えボタンを有効化してinputに番号をセット
change_btn.prop('disabled', false)
.removeClass('btn-secondary')
.addClass('btn-primary');
$('#partner-change-form [name="order"]').val(order);
}else{
// 入れ替えボタンを無効化して番号をリセット
change_btn.prop('disabled', true)
.removeClass('btn-primary')
.addClass('btn-secondary');
$('#partner-change-form [name="order"]').val('');
}
// 「様子を見る」の判定
$('button[data-action="details"]').hide();
$('button[data-action="details"][data-target="#pokemon' + order + '-details-modal"]').show();
});
}
/**
* モーダル起動時の初期化
* @function change
* @return void
**/
var showPartyModalInit = function(){
$('#party-modal').on('show.bs.modal', function(){
// 選択中のポケモンを初期化
$(this).find('[data-list="party"] > .table-selected-row')
.removeClass('active');
// 「様子を見る」を初期化
$(this).find('button[data-action="details"]')
.hide();
$(this).find('[data-btn="default"][data-action="details"]')
.show();
// 「入れ替える」を初期化
$('#partner-change-form button[type="submit"]').prop('disabled', true)
.removeClass('btn-primary')
.addClass('btn-secondary');
});
}
/*----------------------------------------------------------
// 初期化
----------------------------------------------------------*/
jQuery(function($){
selectPartnerInit();
showPartyModalInit();
});
ポケモンが選択されれば、それに合わせてボタンの判定を行っています。入れ替えボタンはformに埋め込んだsubmitになっているため、選択されたポケモンが戦闘可能であれば番号をinputにセットしています。
それでは、ポケモン交代用に新しくサービスを作成しましょう。
ポケモンの入れ替えサービス(/App/Services/Battle/ChangeService.php)
<?php
$root_path = __DIR__.'/../../..';
// 親クラス
require_once($root_path.'/App/Services/Service.php');
// トレイト
require_once($root_path.'/App/Traits/Service/Battle/ServiceBattleAttackTrait.php');
require_once($root_path.'/App/Traits/Service/Battle/ServiceBattleEnemyTurnTrait.php');
require_once($root_path.'/App/Traits/Service/Battle/ServiceBattleAttackAfterTrait.php');
require_once($root_path.'/App/Traits/Service/Battle/ServiceBattleCheckTrait.php');
require_once($root_path.'/App/Traits/Service/Battle/ServiceBattleEnemyAiTrait.php');
require_once($root_path.'/App/Traits/Service/Battle/ServiceBattleOrderGenelatorTrait.php');
require_once($root_path.'/App/Traits/Service/Battle/ServiceBattleExTrait.php');
require_once($root_path.'/App/Traits/Service/Battle/ServiceBattleCalTrait.php');
/**
* 交代
*/
class ChangeService extends Service
{
use ServiceBattleAttackTrait;
use ServiceBattleEnemyTurnTrait;
use ServiceBattleAttackAfterTrait;
use ServiceBattleCheckTrait;
use ServiceBattleEnemyAiTrait;
use ServiceBattleOrderGenelatorTrait;
use ServiceBattleExTrait;
use ServiceBattleCalTrait;
/**
* @return void
*/
public function __construct()
{
//
}
/**
* @return void
*/
public function execute()
{
// バリデーション
if(!$this->validation()){
return;
}
// 交代処理
$this->change()
// 相手のターン処理
$this->enemyTurn();
}
}
/**
* 検証
* @return bool
*/
private function validation(): bool
{
/**
* 現在と同じ → false
* 存在しない → false
* ひんし → false
*/
$partner = player()->getPartner(request('order'));
if(
request('order') === battle_state()->getOrder() ||
empty($partner) ||
!$partner->isFight()
){
return false;
}
return true;
}
/**
* 交代処理
* @return void
*/
private function change(): void
{
$partner = player()->getPartner(request('order'));
// 現在のバトルポケモンのバトルステータス関係を初期化
friend()->releaseBattleStatsAll();
battle_state()->changeInit('friend');
// ポケモンを戻す演出処理
$msg_id1 = issueMsgId();
setMessage(friend()->getNickname().'、戻れ!', $msg_id1);
setResponse([
'action' => 'change-in',
'target' => 'friend'
], $msg_id1);
// バトル中のポケモンを交代してポケモン番号を変更
battle_state()->setFriend($partner, true);
// 交代後のポケモンを繰り出す演出処理
$msg_id2 = issueMsgId();
setMessage('ゆけっ!'.friend()->getNickname().'!', $msg_id2);
setResponse([
'action' => 'change-out',
'target' => 'friend',
'param' => json_encode([
'class' => get_class($partner),
'name' => $partner->getNickname(),
'level' => $partner->getLevel(),
'hp_max' => $partner->getStats('HP'),
'hp_now' => $partner->getRemainingHp(),
'hp_per' => $partner->getRemainingHp('per'),
'hp_color' => $partner->getRemainingHp('color'),
'sa' => $partner->getSaName(),
'sa_color' => $partner->getSaColor(),
'exp' => $partner->getPerCompNexExp(),
])
], $msg_id2);
}
}
受け取ったポケモン番号が、交代可能なものかをチェックして交代処理を行っています。判定はフロント側と同様で、現在と異なっているか、ひんし状態ではないかです。直接操作で「存在しない番号」が飛んでくる可能性も考慮して、バックエンドでは追加で存在確認を最初に行っています。
もしこの3つをクリアすれば、現在のポケモンのバトルステータスを全て解除、へんしんやターンダメージ等も含め全てリセットを行い、格納されているバトルポケモンを交代しています。
演出用のレスポンスですが、ポケモンを戻す際のものと、ポケモンを登場させる際に必要になるパラメーターをメッセージに合わせて返却しています。新しいポケモンのものに書き換えるためには必要な値が多いため、json形式で返却しています。
入れ替えの演出
それでは、サービス側でされた値を使って、表示ポケモンの書き換え処理を行いましょう。ポケモン入れ替えでは「戻す」と「登場」の2つの演出が必要になるので、それぞれ別の関数で作成していきましょう。
関数のグローバル化
演出の関数が多くなってきたため、このタイミングでJavaScriptの関数をグローバル化させてファイル分けしても呼び出せるようにしていきます。今回は「window」にオブジェクトを追加する方法を採用しました。
アクション関数のまとめJS(/Public/Assets/js/Battle/library-action.js)
// グローバル関数をオブジェクト化
window.actionLib = {};
/**
* 交代処理(戻す)
* @param json
* @return Promise
*/
window.actionLib.doAnimateChangeIn = function (target){
return new Promise ((resolve, reject) => {
// 対象のポケモン画像とパラメーターを非表示
$('#' + target + '-pokemon-image').hide();
$('#' + target + '-pokemon-parameter').css('opacity', 0);
resolve();
});
}
/**
* 交代処理(登場)
* @param json
* @return Promise
*/
window.actionLib.doAnimateChangeOut = function (target, param){
return new Promise ( async (resolve, reject) => {
// 交代後のポケモンの画像を生成
var img = $('#' + target + '-pokemon-image');
img.attr('src', '/Assets/img/pokemon/dots/back/' + param.class + '.gif')
// HPの生成
var hpbar = $('#hpbar-' + target);
hpbar.css('width', param.hp_per + '%');
hpbar.attr('aria-valuenow', param.hp_now);
hpbar.attr('aria-valuemax', param.hp_max);
hpbar.removeClass(function(index, className) {
// 背景色クラスを全リセット
return (className.match(/\bbg-\S+/g) || []).join(' ');
});
hpbar.addClass('bg-' + param.hp_color);
$('#remaining-hp-count-' + target).text(param.hp_now);
$('#max-hp-count-' + target).text(param.hp_max);
// 状態異常の生成
var sa = $('#sa-' + target);
sa.text('');
if(param.sa){
sa.text(param.sa)
.removeClass(function(index, className) {
// バッジ色クラスを全リセット
return (className.match(/\bbadge-\S+/g) || []).join(' ');
})
.addClass('badge-' + param.sa_color);
}
// レベル・名前
$('#level-friend').text(param.level);
$('#name-friend').text(param.name);
// 経験値
var expbar = $('#expbar-friend');
expbar.css('width', param.exp + '%');
expbar.attr('aria-valuenow', param.exp);
// ボールが開くエフェクト
$('#battle-field').append(
$($('#template-effect-ball-open').html())
.addClass('friend')
);
await timer(500);
// ボールエフェクトを非表示
$('img.capture-ball-open').hide();
// 画像とパラメーターを表示
img.show();
$('#' + target + '-pokemon-parameter').css('opacity', 100);
await timer(1000);
resolve();
});
}
windowのオブジェクトにactionLibのキーを追加し、その中に空オブジェクトを格納してから関数をセットしています。これで、別ファイルでもwindowオブジェクトを参照することで関数を自由に呼び出すことができます。
※本格的なJSを使ったアプリケーションを検討している方には、クラス管理やフレームワークでの開発をオススメします
処理自体の記述量は多いですが、内容は至って単純です。
ポケモンを戻す際には、対象のポケモンとHPバーなどを含めたブロックを非表示にしています。両方をhide(display:none)にしてしまうと、高さが変動することでガタつきが出てしまうので、高さがあるHPバーのブロックは不透明度を0にすることで擬似的に非表示状態としています。
※min-heightを使ってcssでの処理でもOKです
ポケモンの登場演出は、非表示にした各要素に対してPHPから受け取ったパラメーターを順に嫉妬していきます。HPバーや状態異常のカラーについては、一旦removeClassで全て取り除いてから、再度受け取ったカラー値をセットしています。
演出の時間を調整するため、await timer(ミリ秒)を使って調整しています。
/**
* タイマー
* @param time:integer
* @return Promise
*/
var timer = function(time){
return new Promise( async (resolve, reject) => {
setTimeout(() => { resolve(); }, time);
});
}
使用するエリアがasync関数になっていれば、setTimeoutで囲わず同期的な処理を記述することができるので、今回のような演出処理を作る際には便利です。
それでは、ポケモンの入れ替え演出について見てみましょう。
交代後に相手ポケモンの行動が問題なく行われ、入れ替え自体も問題なく終えることができました。
これで単純なバトル中のポケモン交代処理は完成です。
まとめ
いかがだったでしょうか。
今回のPHPポケモンでは「ポケモンの交代処理」の実装方法についてご紹介しました。
バトル中のポケモンをクラスによる管理に替えたことで、全体的な処理自体がスムーズになりました。
プログラミング学習中の方や、ゲームづくりに興味がある人は、是非参考にしてみてくださいね。