動きのあるHPバーづくり
それではデモ公開に先立ち、HPバーの作り込みをしていきたいと思います。
現在のPHPポケモンは、ダメージ計算などが終わった結果をすべて返却しているため、技選択をして次の画面に移行すると、HPが減った状態でスタートしていました。これでは、どの技でどれぐらいのダメージを与え、状態変化等でどのぐらいダメージの変化があったのかなどはわかりません。
今回はそこもゲームと同じように再現するため、HPバーの動きを再現していきます。
ちなみにですが、動き関係についてはもちろんJavaScript(jQuery)をガッツリ使用します。
サーバー側の処理
まず注意すべき点が1つあります。それは、どこまでをフロント側(Javascript)、どこまでをサーバ側(PHP)で担当するかということです。
あくまでPHPポケモンはWEBアプリケーションのため、DOMを直接操作されてしまうと実際には起こり得ない計算結果を発生させてしまう可能性があります。そうしないためにも、ダメージ計算などはすべてサーバー側で行い、フロントではあくまで画面上の変動は演出にとどめて置かなければなりません。
・残りHP20の相手に10ダメージを与える
→ PHPで今まで通りダメージ計算を行い、相手の残りHPを10にする
→ Javascriptで20から10になる演出をさせる(残りHPをここでは計算しない)
メッセージIDの発行
ポケモンがHPに対して動きを持たせるタイミングは、メッセージに合わせてあります。以下2つの例をみてみましょう。
攻撃技を使用した場合
「ピカチュウはでんきショックを使った」
→ でんきショックのダメージ分HPバー減少
状態異常(毒)の場合
毒ダメージ分HPバー減少
→「ピカチュウは毒のダメージを受けている」
このように、状況によってメッセージのタイミングは異なりますが、メッセージの前後で行われ、ボタンを押すことで順番にアクションが進んでいきます。なので、メッセージに対してIDをもたせ、そこにパラメーターを設定することで、どのメッセージでどのアクションを実行させればよいかを判別させます。そのために、ResponseTraitに作成したメッセージとレスポンスの2つを組み合わせて作成します。
アクション判定方法
- メッセージにIDを持たせる
- そのIDに該当するパラメーターをレスポンスへ格納
- メッセージがクリックされた時に、レスポンスに格納されたパラメーター通りアクションを実行
それでは、メッセージIDを生成するための仕組みを見ていきましょう。
レスポンストレイト(/App/Traits/ResponseTrait.php)
/**
* メッセージIDの発行
*
* @return string
*/
public function issueMsgId()
{
// IDを生成
$id = 'msg'.substr(bin2hex(random_bytes(16)), 0, 16);
// ユニークになるようにチェック
while(in_array($id, $_SESSION['__message_ids'] ?? [], true)){
$id = 'msg'.substr(bin2hex(random_bytes(16)), 0, 16);
}
$_SESSION['__message_ids'][] = $id;
return $id;
}
重複をさせるために、ランダムでIDを生成後にセッションに格納しています。格納前に同値があれば、whileで再生成するという仕組みです。
ここでプロパティではなくセッションを使用している理由は「どのインスタンス内でIDが生成されるかわからない」からです。通常はサービス内で行われていますが、技の反動であればポケモンのインスタンス内や技のインスタンス内など様々です。これらを取り出してまとめてコントローラーのインスタンスにセットした時に、プロパティ内で一意にしていれば重複する可能性が出てきてしまいます。
16桁の文字数列で1桁程度の数であれば、重複する可能性はほぼ有りませんが100%ではありません。これを100%とするためにセッションを活用しました。
アクションの作成
IDの作成が出来たので、次にメッセージへのID格納と、レスポンスへのパラメーターの格納を行います。そのために、レスポンストレイトへいくつかメソッドを追加しておきましょう。
レスポンストレイト(/App/Traits/ResponseTrait.php)
/**
* アニメーション用の自動メッセージの格納
*
* @param mixed $param
* @return array
*/
public function setAutoMessage($param)
{
$this->msgs[] = ['', $param, 'auto'];
}
/**
* 空メッセージの格納
*
* @param string $param
* @return array
*/
public function setEmptyMessage($param)
{
$this->msgs[] = ['', $param, ''];
}
メッセージの格納(setMessage)では空を弾くようにしています。しかし、状態異常のダメージを行う際には空メッセージを間に挟んでいる方が簡単に作成できるため、個別に作成しました。自動メッセージの格納は次回詳しく説明しますが、アニメーションに合わせて自動送りをしたいメッセージ用として準備しています。
これでアニメーションを起こす際に必要な値を格納する準備が整いました。
しかし、アニメーションでHPの減算・加算を行うためには、敵味方お互いのターン最初のHP状態を残して置かなければなりません。現在はgetRemainingHpで取得しているので、計算後の値は取得できますが、画面以降すればその値はダメージを逆算しなければ算出することが出来ないからです。
なので、前画面からのポケモン情報を引き継いだタイミングで、コントローラーに対してスタート時のHP状況を格納するために、プロパティを用意しておきましょう。
バトルコントローラー(/App/Controller/Battle/BattleController.php)
<?php
$root_path = __DIR__.'/../../..';
require_once($root_path.'/App/Controllers/Controller.php');
// サービス
require_once($root_path.'/App/Services/Battle/StartService.php');
require_once($root_path.'/App/Services/Battle/RunService.php');
require_once($root_path.'/App/Services/Battle/FightService.php');
// トレイト
require_once($root_path.'/App/Traits/Controller/BattleControllerTrait.php');
// バトル用コントローラー
class BattleController extends Controller
{
--省略
/**
* 前ターンのHP
* @var array
*/
protected $before_remaining_hp = [
'friend' => 0,
'enemy' => 0,
];
次に格納方法です。計算が行われる前でなければなりませんので、両ポケモンも引き継ぎ処理後に、getRemainingHpの結果を格納しましょう。
バトルコントローラー用トレイト(/App/Traits/Controller/BattleControllerTrait.php)
/**
* バトルコントローラー用トレイト
*/
trait BattleControllerTrait
{
/**
* ポケモン情報の引き継ぎ
*
* @param Pokemon::export:array $pokemon
* @return void
*/
protected function takeOverPokemon($pokemon)
{
$class = $pokemon['class_name'];
$this->pokemon = new $class($pokemon);
// ランク(バトルステータス)の引き継ぎ
if(isset($_SESSION['__data']['rank'])){
$this->pokemon
->setRank($_SESSION['__data']['rank']['pokemon']);
}
// 状態変化の引き継ぎ
if(isset($_SESSION['__data']['sc'])){
$this->pokemon
->setSc($_SESSION['__data']['sc']['pokemon']);
}
// 前ターンのHP(現在の残りHP)をプロパティに格納
$this->before_remaining_hp['friend'] = $this->pokemon
->getRemainingHp();
}
/**
* 相手ポケモンの引き継ぎ
*
* @param Pokemon::export:array $enemy
* @return void
*/
protected function takeOverEnemy($enemy)
{
if(!empty($enemy)){
$this->enemy = new $enemy['class_name']($enemy);
// 前ターンのHP(現在の残りHP)をプロパティに格納
$this->before_remaining_hp['enemy'] = $this->enemy
->getRemainingHp();
}
// ランク(バトルステータス)の引き継ぎ
if(isset($_SESSION['__data']['rank'])){
$this->enemy
->setRank($_SESSION['__data']['rank']['enemy']);
}
// 状態変化の引き継ぎ
if(isset($_SESSION['__data']['sc'])){
$this->enemy
->setSc($_SESSION['__data']['sc']['enemy']);
}
}
自ポケモンの場合は通常の引継ぎ処理後に一律で取得可能ですが、相手ポケモンの場合は開始ターン引き継ぎ時点ではポケモンのインスタンスが存在しません。なので、引き継ぎができれば取得して格納するようにしましょう。
バトルコントローラー(/App/Controller/Battle/BattleController.php)
/**
* アクションに合わせた分岐
* @return void
*/
private function branch()
{
try {
// アクション分岐
switch ($this->request('action')) {
/******************************************
* 開始
*/
case 'battle':
// サービス実行
$service = new StartService;
$service->execute();
// 実行結果
$this->enemy = $service->getResponse('enemy');
// 前ターンのHP(現在の残りHP)をプロパティに格納
$this->before_remaining_hp['enemy'] = $this->enemy
->getRemainingHp();
$this->setMessage($service->getMessages());
$this->setResponse($service->getResponses());
break;
相手ポケモンの場合、開始ターンは引き継ぎ処理で格納出来ていないので、サービス実行後にインスタンスが生成された段階で格納します。
最後に、開始時のHPを取得するためのメソッドを用意しておきましょう。
バトルコントローラー用トレイト(/App/Traits/Controller/BattleControllerTrait.php)
/**
* 前のターンの残りHPを取得(HPバー用)
*
* @param Pokemon:object $pokemon
* @param string $param (per)
* @return numeric
*/
public function getBeforeRemainingHp($pokemon, $param='')
{
if($param === 'per'){
// 最大HPとの比率を%で取得(数値で返却)
return $this->before_remaining_hp[$pokemon->getPosition()] / $pokemon->getStats('HP') * 100;
}else{
return $this->before_remaining_hp[$pokemon->getPosition()];
}
}
こちらのメソッドが、HPバーの初期値で使用する値の算出用となります。
画面開始時の初期値の取得方法が整ったので、次は計算(アニメーション)用のパラメーター返却です。作成したレスポンス用メソッドを使って実装していきましょう。
攻撃用トレイト(/App/Traits/Service/Battle/ServiceBattleAttackTrait.php)
/**
* 攻撃メッセージのパラメーターとして設定するID
* @var string
*/
private $atk_msg_id;
/**
* 攻撃
* (攻撃→ダメージ計算→ひんし判定)
*
* @param object $atk_pokemon
* @param object $def_pokemon
* @param object $move
* @return void
*/
protected function attack($atk_pokemon, $def_pokemon, $move)
{
--省略
// 攻撃メッセージを格納
$this->atk_msg_id = $this->issueMsgId();
$this->setMessage($atk_pokemon->getPrefixName().'は'.$move->getName().'を使った!', $this->atk_msg_id);
--省略
/**
* 攻撃判定成功時の処理
*
* @param object $atk_pokemon
* @param object $def_pokemon
* @param object $move
* @return void
*/
private function attackSuccess($atk_pokemon, $def_pokemon, $move)
{
--省略
// このターン受けるダメージをポケモンに格納
$def_pokemon->setTurnDamage($move->getSpecies(), $damage ?? 0);
// ダメージ計算
$def_pokemon->calRemainingHp('sub', $damage ?? 0);
// HPバーのアニメーション用レスポンス
if(isset($damage)){
$this->setResponse([
'param' => $damage,
'action' => 'hpbar',
'target' => $def_pokemon->getPosition(),
], $this->atk_msg_id);
}
攻撃用トレイトであれば、メッセージのタイミングがかなり早くに発生して、ダメージ計算が別のメソッド内で行われるため、プロパティを用意してメッセージIDを格納しておきます。メッセージを格納する際にパラメーターとしてIDを格納、その後ダメージ計算後に同じIDをキーにしてsetResponseに対して必要なパラメータを持たせています。
レスポンスとして返す値には、以下の3つを用意しました。
action:アクションの種類
target:対象
param:ダメージ量(回復量)
回復技なども同じアクションで処理ができるように、アクションの種類はhpbarとしました。これが1つ目のactionです。経験値の増加アニメーションであれば、expbarとする予定です。これはJavascriptで分岐させるために使うだけなので、設定値は自由です。
2つ目のtargetは、敵か味方かを判別するための値です。こちらは防御ポケモンのgetPositionを使用の値を使って判別しています。
3つめのparamは変動値です。HPであればほとんどがダメージとなるので、減算処理では正の数、回復には負の数を指定して同じJavascriptの処理で実装します。
この要領で、状態異常によるダメージなども指定していきます。例として「やどりぎのタネ」のパラメーター格納を見てみましょう。
バトル用チェックトレイト(/App/Traits/Service/Battle/ServiceBattleCheckTrait.php)
/**
* アタック後の状態変化チェック
*
* @param object Pokemon
* @return void
*/
protected function checkAfterSc($sicked_pokemon, $enemy_pokemon)
{
--省略
/**
* やどりぎのタネ
*/
if(isset($sc['ScLeechSeed'])){
// メッセージIDの生成(ダメージ用と回復用)
$ls_msg_id1 = $this->issueMsgId();
$ls_msg_id2 = $this->issueMsgId();
// 最大HPの1/8HPを吸収する
$leech_seed = new ScLeechSeed;
// 小数点以下切り捨て
$damage = (int)($sicked_pokemon->getStats('HP') / 8);
if($damage){
// 最小ダメージ数は1
$damage = 1;
}
// ダメージ計算
$sicked_pokemon->calRemainingHp('sub', $damage);
// HPバーのアニメーション用レスポンス
$this->setResponse([
'param' => $damage,
'action' => 'hpbar',
'target' => $sicked_pokemon->getPosition(),
], $ls_msg_id1);
// 回復
$enemy_pokemon->calRemainingHp('add', $damage);
// HPバーのアニメーション用レスポンス
$this->setResponse([
'param' => $damage * -1, # 加算するため負の数に変換してセット
'action' => 'hpbar',
'target' => $enemy_pokemon->getPosition(),
], $ls_msg_id2);
// メッセージ(アニメーション用に空メッセージを2つ用意)
$this->setAutoMessage($ls_msg_id1);
$this->setAutoMessage($ls_msg_id2);
$this->setMessage($leech_seed->getTurnMessage($sicked_pokemon->getPrefixName()));
// HPが0になっていればチェック終了
if(!$sicked_pokemon->getRemainingHp()){
return;
}
やどりぎのタネのHPバーのアニメーションとメッセージの流れは
- 相手のHPが減る
- 自分のHPが増える
- メッセージ表示「やどりぎのタネが相手の〇〇から体力を吸収した」
という3ステップになります。なので、最初の2つには空の自動メッセージに合わせてIDをのパラメーターを結びつけておき、最後にメッセージを表示させています。
あとは、ダメージの種類に合わせて準備するだけです。余白を置きたいときには自動メッセージや空メッセージ、メッセージ後にアクションを起こしたい場合は通常メッセージにパラメーターをセットすれば、可能な限り近い再現が可能となります。
まとめ
いかがだったでしょうか。
今回のPHPポケモンは「HPバーアニメーション」の作り方について、サーバー側での処理方法をご紹介しました。
次回はフロント側の処理として、Javascript(jQuery)を使ったアニメーションの作り方を説明します。
現在WEBアプリケーションを学習中の方や興味がある人は、ぜひ参考にしてくださいね。