トレーナー戦闘の追加
前回トレーナー情報を作成したので、今回はその情報をバトルシステムへ組み込んでいきます。
現在、野生ポケモンとの戦闘では「battle」という値actionの値で受け取っています。同じサービス内で分岐を作ると複雑になってしまうので、battle_trainerという新しい分岐を使ってサービス分けをしましょう。
ホームコントローラー(/App/Controllers/Home/HomeController.php)
// branchメソッド
/******************************************
* トレーナー戦
*/
case 'battle_trainer':
// バトル開始可能な状態かを確認
if($this->validationBattleTrainer()){
$_SESSION['__route'] = 'battle';
// バトルコントローラーへaction値をpostするためにトークンをセット
$_SESSION['__token'] = $_POST['__token'];
// 画面移管
$this->redirect();
}
break;
バトルコントローラー(/App/Controllers/Battle/BattleController.php)
// branchメソッド
/******************************************
* トレーナー戦 開始
*/
case 'battle_trainer':
// サービス実行
$service = new StartTrainerService;
$service->execute();
response()->setResponse(true, 'battle-start');
break;
ホームコントローラーで行なっているバリデーションメソッド(validationBattleTrainer)では、フィールド選択と同様に、トレーナー情報が現在のプレイヤーレベルに達しているかどうかを検証しています。
ホームコントローラー用トレイト(/App/Traits/Controller/HomeControllerTrait.php)
/**
* バトル開始(トレーナー)の検証
* @return boolean
*/
protected function validationBattleTrainer(): bool
{
// バトル開始可能な状態かを確認
if(!player()->isFightParty()){
response()->setMessage('バトルに参加できるポケモンがいないので、戦えません');
return false;
}
// フィールド情報・プレイヤーレベルの確認
if(
empty(config('trainer.'.request('trainer'))) ||
player()->getLevel() < config('trainer.'.request('trainer').'.level')
){
response()->setMessage('プレイヤーレベルが足りていません');
return false;
}
return true;
}
次に、battle_trainerの呼び出し先であるサービスを見てみましょう。
トレーナー戦開始用サービス(/App/Services/Battle/StartTrainerService.php)
<?php
// 親クラス
require_once(app_path('Services').'Service.php');
// クラス
require_once(root_path('Classes').'Trainer.php');
/**
* バトル開始(トレーナー戦)
*/
class StartTrainerService extends Service
{
/**
* @return void
*/
public function __construct()
{
// バトル状態の初期化
initBattleState('trainer');
}
/**
* @return void
*/
public function execute()
{
// 味方の選出
$this->electionFriend();
// トレーナーの情報の作成とポケモンの選出
$this->createTrainer();
// ポケモン図鑑への登録確認(発見)
$this->checkPokedex();
// 前ターン状態を格納
battle_state()->setBefore();
// 返却値をセット
response()->setMessage(
trainer()->getPrefixName().'が勝負を仕掛けてきた'
);
$msg_id1 = response()->issueMsgId();
response()->setMessage('相手は、'.enemy('NAME').'を繰り出してきた', $msg_id1);
response()->setResponse([
'action' => 'start',
'target' => 'enemy'
], $msg_id1);
$msg_id2 = response()->issueMsgId();
response()->setMessage('ゆけっ!'.friend()->getNickName().'!', $msg_id2);
response()->setResponse([
'action' => 'start',
'target' => 'friend'
], $msg_id2);
}
/**
* 味方を選出する処理
* @return void
*/
private function electionFriend(): void
{
// 味方ポケモンの選出
$friend = battle_state()->setFightPokemonOrder();
battle_state()->setFriend($friend);
}
/**
* トレーナー情報の作成
* @return void
*/
private function createTrainer(): void
{
// 選択されたトレーナーレベルからランダム取得
$list = glob(storage_path('Database/Trainer/'.request('trainer')).'/*.php');
$key = array_rand($list);
// トレーナーのインスタンスを作成
$trainer = new Trainer(
include($list[$key]), request('trainer')
);
battle_state()->setTrainer($trainer);
}
/**
* ポケモン図鑑への登録確認
* @return void
*/
private function checkPokedex(): void
{
if(
!player()->pokedex()
->isRegisted(enemy('NUMBER'))
){
// 未登録→発見
player()->pokedex()
->discovery(enemy());
}
}
}
選択されたトレーナーからランダムにトレーナー情報を取得、includeで配列取得した値を使ってトレーナーのインスタンスを作成することで、バトル開始状態へ移行させています。ポケモンインスタンスの生成は、前回の記事のトレーナークラスを参考にしてください。
バトル状態への格納
野生ポケモンと同様に、相手トレーナーの情報はどこかへ格納しておく必要があります。なので、バトル時専用のクラスであるBattleStateへ保管していきます。
バトル状態クラス用トレーナー管理トレイト(/App/Traits/Class/BattleState/ClassBattleStateTrainerTrait.php)
<?php
/**==================================================================
* トレーナー情報
==================================================================**/
trait ClassBattleStateTrainerTrait
{
/**
* トレーナー
* @var array
*/
private $trainer;
/**
* トレーナーが選択中のポケモン番号
* @var integer
*/
private $trainer_order = 0;
/**
* トレーナー情報の取得
* @return object
*/
public function getTrainer(): object
{
return $this->trainer;
}
/**
* トレーナー情報の格納
* @param trainer:object::Trainer
* @return void
*/
public function setTrainer(object $trainer): void
{
$this->trainer = $trainer;
// 相手が持つ最初のポケモンを選出して格納
$this->setEnemy(
$trainer->getPartner($this->trainer_order)
);
}
/**
* トレーナー情報の取得
* @return integer
*/
public function getTrainerOrder(): int
{
return $this->trainer_order;
}
/**
* 次のポケモンへ
* @return boolean
*/
public function nextOrder(): bool
{
// 戦闘可能なパーティーを取得
$party = array_filter(trainer()->getParty(), function($pokemon){
return $pokemon->isFight();
});
if(empty($party)){
// 全滅
return false;
}else{
// 戦闘可能な最初のポケモンを取得
$next = array_key_first($party);
}
// 次のポケモンをセットしてtrueを返却
$this->setEnemy(
$this->trainer->getPartner($next)
);
$this->trainer_order = $next;
return true;
}
}
次のポケモンを選出する方法は、戦闘可能状態のポケモンを配列で取得、そこから一番上のポケモンを選択しています。現段階ではポケモンを交代するAIは入れていないため、戦闘可能なポケモンがいれば順番に選出するだけとなります。
味方と同様で、ページ移管するとパーティー内のインスタンスとバトル中のポケモンのインスタンスがリンク切れしてしまいます。それを防ぐためにも、バトル開始時には再セットするようにしておきましょう。
バトルコントローラー用トレイト(/App/Traits/Controller/BattleControllerTrait.php)
/**
* バトルコントローラー用トレイト
*/
trait BattleControllerTrait
{
/**
* 引き継ぎ処理
* @return void
*/
protected function inheritance()
{
// バトル状態の引き継ぎ
if(isset($_SESSION['__data']['battle_state'])){
// グローバルにセット
setBattleState(
unserializeObject($_SESSION['__data']['battle_state'])
);
// ターン最初の状態へ初期化
battle_state()->turnInit();
// 描画用ポケモンをセット(味方・敵)
battle_state()->setBefore();
// シリアライズでリンク切れしているため、味方のオブジェクトを再セット
$order = battle_state()->getOrder();
battle_state()->setFriend(
player()->getPartner($order)
);
// もしトレーナー戦であれば、相手のオブジェクトも再セット
if(battle_state()->isMode('trainer')){
$trainer_order = battle_state()->getTrainerOrder();
battle_state()->setEnemy(
trainer()->getPartner($trainer_order)
);
}
}
}
バトル判定の分岐
バトルシステム自体は、enemyにポケモンをセットできれば既存の処理で動いてくれます。ですが、判定では残りのポケモン情報までは参照してくれないため、判定時に分岐を入れることで、戦闘可能なポケモンがいれば選出されるようにします。
バトルコントローラー用トレイト(/App/Traits/Controller/BattleControllerTrait.php)
/**
* バトル結果(勝ち)
* @return void
*/
private function judgmentWin()
{
$party = player()->getParty();
// 経験値がもらえるポケモンに経験値を割り振り
$orders = battle_state()->getEntitledExpOrders();
foreach($orders as $order){
// 経験値の計算
$exp = $this->calExp($party[$order], enemy(), count($orders));
// 経験値をポケモンにセット
$party[$order]->setExp($exp);
// 努力値を獲得
$party[$order]->setEv(enemy('REWARD_EV'));
}
// トレーナー戦の場合は、次のポケモンを選出
if(battle_state()->isMode('trainer')){
$msg_id = response()->issueMsgId();
if(battle_state()->nextOrder()){
response()->setMessage(trainer()->getPrefixName().'は、'.enemy('NAME').'を繰り出してきた', $msg_id);
response()->setResponse([
'action' => 'change-out',
'target' => 'enemy',
'param' => json_encode([
'base64' => enemy()->base64(),
'name' => enemy('NAME'),
'level' => enemy()->getLevel(),
'hp_max' => enemy()->getStats('H'),
'hp_now' => enemy()->getRemainingHp(),
'hp_per' => enemy()->getRemainingHp('per'),
'hp_color' => enemy()->getRemainingHp('color'),
'sa' => enemy()->getSaName(),
'sa_color' => enemy()->getSaColor(),
'exp' => enemy()->getPerCompNexExp(),
])
], $msg_id);
// 次のポケモンのバトル状態を初期化
enemy()->initBattleStats();
battle_state()->changeInit('enemy');
// 行動選択へ
response()->setEmptyMessage();
}else{
response()->setMessage(trainer()->getPrefixName().'との勝負に勝った', $msg_id);
response()->setResponse([
'action' => 'show-trainer',
'target' => 'enemy',
], $msg_id);
response()->setMessage(trainer()->getLine('lose'));
// 勝利時の最終処理
$this->judgmentWinLast();
}
}else{
// 勝利時の最終処理
$this->judgmentWinLast();
}
}
現段階では賞金の分配機能や、敗北時のお小遣いマイナスは未実装です。
トレーナーの表示
せっかくトレーナー戦を実装するので、バトル開始時のトレーナー演出は見れる方が良いですね。なので、こちらは先程実装したレスポンス等を使いながらバトル画面へ設置していきましょう。
バトル画面フィールド(/Resources/Partials/Battle/field.php)
<?php # 敵ポケモン詳細 ?>
<div class="col-6 position-relative">
<div id="enemy-pokemon-parameter" class="<?php if(response()->getResponse('battle-start') || $before_enemy->isFainting()) echo 'opacity-0'; ?>">
<p>
<span class="mr-2" id="name-enemy"><?=$before_enemy->getNickName()?></span>
<span class="mr-2">Lv:<span id="level-enemy"><?=$before_enemy->getLevel()?></span></span>
<span class="mr-2 badge badge-<?=$before_enemy->getSaColor(false)?>" id="sa-enemy">
<?=$before_enemy->getSaName(false)?>
</span>
</p>
<div class="form-group">
<div class="progress rounded-pill bg-gray" style="height:12px;">
<div id="hpbar-enemy"
class="progress-bar bg-success"
role="progressbar"
style="width:<?=$before_enemy->getRemainingHp('per')?>%;"
aria-valuenow="<?=$before_enemy->getRemainingHp()?>"
aria-valuemin="0"
aria-valuemax="<?=$before_enemy->getStats('H')?>"></div>
</div>
</div>
</div>
<?php # トレーナー戦(ポケモンの所有数) ?>
<?php if(battle_state()->isMode('trainer')): ?>
<div id="enemy-trainer-parameter" class="border-bottom p-2 <?php if(!response()->getResponse('battle-start')) echo 'd-soft-none' ?>">
<div class="d-flex justify-content-end">
<?php # 空き枠 ?>
<?php for($i=0;$i<trainer()->getPartyEmptyCount();$i++): ?>
<span class="icon-party-ball empty"></span>
<?php endfor; ?>
<?php # 所有数 ?>
<?php for($i=0;$i<trainer()->getPartyCount();$i++): ?>
<span class="icon-party-ball"></span>
<?php endfor; ?>
</div>
</div>
<?php endif; ?>
</div>
<div class="col-6 text-center position-relative">
<?php # ポケモン画像 ?>
<input type="image"
id="enemy-pokemon-image"
class="action-img-btn <?php if($before_enemy->isFainting() || (battle_state()->isMode('trainer') && response()->getResponse('battle-start'))) echo 'opacity-0'; ?>"
src="<?=$before_enemy->base64('front', true)?>"
alt="<?=$before_enemy::NAME?>"
data-toggle="modal"
data-target="#enemy-battle-state-modal" />
<?php # トレーナー戦(トレーナー画像) ?>
<?php if(battle_state()->isMode('trainer')): ?>
<img src="/Assets/img/npc/front/<?=trainer()->getCategory()?>.gif"
id="enemy-trainer-image"
class="<?php if(!response()->getResponse('battle-start')) echo 'd-soft-none' ?>"
alt="<?=trainer()->getCategoryName()?>" />
<?php endif; ?>
</div>
バトル開始時のみ、トレーナー画像と所有ポケモン数は表示させています。トレーナー画像は戦闘終了時にも再度表示させたいので、ノード自体を無くすのではなく非表示で設置しています。
では実際に、トレーナーとの対戦を見てみましょう。
無事、相手に設定した手持ち3体と戦うことができましたね。
これでトレーナー戦の大筋は完成です。
まとめ
いかがだったでしょうか。
今回のPHPポケモンでは「トレーナー戦」の実装方法についてご紹介しました。
ポケモンに興味がある方、プログラミングに興味がある方は、ぜひ参考にしてみてくださいね。