HP計算
これまでに実装したダメージ計算ですが、計算はできていてもお互いに相手ポケモンのHPを削ることはできませんでした。なので、今回は実際のバトルのように、HPに対してダメージを与えるという仕組みを作成していきます。
残りHP
ここで必要になるのが「残りHP」という概念です。今までステータス上のHPは存在していましたが、現在どれぐらい残りがあるのかまでは用意していませんでした。なので、これをポケモンに対して持たせ、且つ状態異常などとともにステータスの引き継ぎ対象としましょう。
プロパティの設定
まずは残りHPを格納するためのプロパティをポケモンクラスに用意します。
ポケモンクラス(/Classes/Pokemon.php)
/**
* 残りHP
* @var integer
*/
protected $remaining_hp;
こちらも取得用のメソッドとしてGet格納トレイトにgetRemainingHpを作成しておいてください。
プロパティを作成したら、引き継ぎ処理にも追加しておきましょう。今回引き継ぎ処理を簡易化させました。
ポケモンクラス(/Classes/Pokemon.php)
/**
* 現在インスタンスを出力(引き継ぎ用)
*
* @return array
*/
public function export()
{
return [
'class_name' => get_class($this), # クラス名
'nickname' => $this->nickname, # ニックネーム
'level' => $this->level, # レベル
'ev' => $this->ev, # 努力値
'iv' => $this->iv, # 個体値
'exp' => $this->exp, # 経験値
'move' => $this->move, # 技
'sa' => $this->sa, # 状態異常
'remaining_hp' => $this->remaining_hp, # 残りHP
];
}
/**
* 能力引き継ぎ処理
*
* @param array $before
* @return void
*/
protected function takeOverAbility($before)
{
foreach($before as $key => $val){
$this->$key = $val;
}
}
exportには追加になった残りHPの項目を増やしています。
能力引き継ぎの処理についてですが、それぞれ値を指定するのではなく、foreachで回していきながらそれぞれキー通りのプロパティに設定するという仕様です。進化時の引き継ぎ処理としてオブジェクトの分岐をしていましたが、こちらはオブジェクトではなくexportした配列を引数として渡す仕様に、ポケモンのコンストラクタ内を変更しました。
ポケモンクラス(/Classes/Pokemon.php※__construct内)
/**
* 進化した際の処理
* @var object $before
*/
case 'object':
// 進化前のポケモンと一致しているかチェック
if(is_a($before, $this->before_class ?? null)){
$this->takeOverAbility($before->export());
// メッセージの引き継ぎ
$this->setMessage($before->getMessages());
$this->setMessage($this->name.'に進化した', 'success');
$this->checkMove();
}
break;
計算用メソッドの作成
次に、計算用のメソッドを作成しましょう。上限値と下限値が決まっているため、原則としては直接プロパティに値をセットするようなことはしません。
計算用メソッドもポケモンクラスで使用しますが、addRankやsubRankなど計算系のメソッドが多くなってきたので、新しく計算用メソッドの格納トレイトを作成しましょう。
計算用トレイト(/Traits/Pokemon/CalculationTrait.php)
<?php
trait CalculationTrait
{
/**
* ランクの加算
*
* @param string $param
* @param integer $val (min:1, max:12)
* @return string
*/
public function addRank($param, $val)
{
// 変化ランクに合わせたメッセージ
$msg = [
1 => '上がった',
2 => 'ぐーんと上がった',
3 => 'ぐぐーんと上がった',
12 => '最大まで上がった',
];
// 既にランクが最大であればfalseを返却
if($this->rank[$param] === 6){
return $this->name.'の'.transJp($param).'はもう上がらない';
}
// 加算処理
$this->rank[$param] += $val;
// 最大値は6
if($this->rank[$param] > 6){
$this->rank[$param] = 6;
}
return $this->name.'の'.transJp($param).'が'.$msg[$val] ?? $msg[3];
}
/**
* ランクの減算
*
* @param string $param
* @param integer $val (min:1, max:3)
* @return string
*/
public function subRank($param, $val)
{
// 変化ランクに合わせたメッセージ
$msg = [
1 => '下がった',
2 => 'がくっと下がった',
3 => 'がくーんと下がった',
];
// 既にランクが最低であればfalseを返却
if($this->rank[$param] === -6){
return $this->name.'の'.transJp($param).'はもう下がらない';
}
// 減算処理
$this->rank[$param] -= $val;
// 最低値は-6
if($this->rank[$param] < -6){
$this->rank[$param] = -6;
}
return $this->name.'の'.transJp($param).'が'.$msg[$val] ?? $msg[3];
}
/**
* 残りHPの計算
*
* @param string $param
* @param integer $val (default:0)
* @return integer
*/
public function calRemainingHp($param, $val=0)
{
switch ($param) {
// リセット処理
case 'reset':
// 最大HPをセット
$this->remaining_hp = $this->getStats('HP');
if(isset($this->sa['SaFainting'])){
// ひんしの場合は解除
unset($this->sa['SaFainting']);
}
break;
// 即死処理
case 'death':
$this->remaining_hp = 0;
break;
// 減算処理
case 'sub':
$this->remaining_hp -= $val;
break;
// 加算処理
case 'add':
$this->remaining_hp += $val;
// 最大HPを超えないようにする
if($this->remaining_hp > $this->getStats('HP')){
$this->remaining_hp = $this->getStats('HP');
}
break;
}
// HPが0以下になった場合の処理
if($this->remaining_hp <= 0){
// 状態異常をひんしに書き換え
$this->sa = ['SaFainting' => ''];
$this->remaining_hp = 0;
}
// 残りHPを返却
return $this->remaining_hp;
}
}
作成したトレイトはポケモンクラスで読み込んでおいてください。
それでは新しく追加した、calRemainingHpのメソッドを見ていきましょう。
/**
* 残りHPの計算
*
* @param string $param
* @param integer $val (default:0)
* @return integer
*/
public function calRemainingHp($param, $val=0)
第1引数でどういった計算をするかを指定します。今回準備したのは初期化(reset)、即死(death)、減算(sub)、加算(add)の4つです。
第2引数には値が入ります。subであればダメージ量、addであれば回復量です。
次に処理内の分岐を見ていきましょう。
switch ($param) {
// リセット処理
case 'reset':
// 最大HPをセット
$this->remaining_hp = $this->getStats('HP');
if(isset($this->sa['SaFainting'])){
// ひんしの場合は解除
unset($this->sa['SaFainting']);
}
break;
// 即死処理
case 'death':
$this->remaining_hp = 0;
break;
// 減算処理
case 'sub':
$this->remaining_hp -= $val;
break;
// 加算処理
case 'add':
$this->remaining_hp += $val;
// 最大HPを超えないようにする
if($this->remaining_hp > $this->getStats('HP')){
$this->remaining_hp = $this->getStats('HP');
}
break;
}
第1引数で受け取った$paramの値に対して分岐をかけていきます。
最初の初期化(reset)処理は、ポケモン生成時や全回復をする際に使用します。現在は用意されていませんが、ポケモンセンターやボックスへ入れて再度取り出すなどした際、「かいふくのくすり」などを選択した際に使用する予定です。
// リセット処理
case 'reset':
// 最大HPをセット
$this->remaining_hp = $this->getStats('HP');
break;
2つ目の即死(death)処理は、一撃必殺などダメージに関係なくHPを0にする際に使用します。
// 即死処理
case 'death':
$this->remaining_hp = 0;
break;
3つ目の処理は減算(sub)処理です。ダメージを受けた際に使用します。数値がマイナスになれば0に戻すという処理をしなければなりませんが、この処理内では行わずに最終的に処理します。
// 減算処理
case 'sub':
$this->remaining_hp -= $val;
break;
4つ目の処理は加算(add)処理です。アイテムによる回復や「じこさいせい」など回復技による処理を行う際に使用します。
// 加算処理
case 'add':
$this->remaining_hp += $val;
// 最大HPを超えないようにする
if($this->remaining_hp > $this->getStats('HP')){
$this->remaining_hp = $this->getStats('HP');
}
break;
最大HPを超えてしまわないように、加算後に数値のチェックを行なっています。
次に分岐後の共通処理について見ていきましょう。
// 復活判定(ひんしからの回復)
if(isset($this->sa['SaFainting']) && ($this->remaining_hp > 0)){
unset($this->sa['SaFainting']);
}
// ひんし判定
if($this->remaining_hp <= 0){
// 状態異常をひんしに書き換え
$this->sa = ['SaFainting' => null];
$this->remaining_hp = 0;
}
// 残りHPを返却
return $this->remaining_hp;
まず最初に、復活判定を行います。もしひんし状態からの回復がなされた場合、状態異常にセットしている「ひんし」を解除しなければなりません。これはresetとaddのどちらかで必要になるため、分岐後にチェックしています。
次に、HPが0になった場合のひんし判定を行います。もし残りHPが0以下であれば、状態異常をひんし(SaFainting)に上書きをして、残りHPに対して0をセットしました。こうすることで、マイナスの値が入ることはありません。
最後に返り値として残りHPを返却します。こちらは使用しないこともありますが、ダメージ判定後にHPがどうなったかを確認する際に活用できるように指定しました。
ダメージ計算
それではダメージ計算に対して、作成した残りHPの減算処理を実装しましょう。
バトルコントローラー(/Classes/Controller/BattleController.php)
/**
* アクション
*
* @param string $action
* @param mixed $param
* @return void
*/
private function action($action, $param)
{
// 敵ポケモンの技をインスタンス化
$e_move = $this->getInstance($this->aiSelectMove());
switch ($action) {
/**
* にげる
*/
case 'run':
// $this->run++;
if($this->checkRun()){
unset($_SESSION['enemy']);
unset($_SESSION['rank']);
unset($_SESSION['run']);
header("Location: ./home.php", true, 307);
exit;
}
$this->setMessage('逃げられない!');
// 敵ポケモンの攻撃
$p_damage = $this->attack($this->enemy, $this->pokemon, $e_move);
break;
/**
* たたかう
*/
case 'fight':
// 自ポケモンの技をインスタンス化
$p_move = $this->getInstance($param);
// 行動順の判定
if($this->checkFirstMove($p_move, $e_move)){
// 先行
// 自ポケモンの攻撃
$e_damage = $this->attack($this->pokemon, $this->enemy, $p_move);
$this->enemy
->calRemainingHp('sub', $e_damage);
// 敵ポケモンの攻撃
$p_damage = $this->attack($this->enemy, $this->pokemon, $e_move);
$this->pokemon
->calRemainingHp('sub', $p_damage);
}else{
// 後攻
// 敵ポケモンの攻撃
$p_damage = $this->attack($this->enemy, $this->pokemon, $e_move);
$this->pokemon
->calRemainingHp('sub', $p_damage);
// 自ポケモンの攻撃
$e_damage = $this->attack($this->pokemon, $this->enemy, $p_move);
$this->enemy
->calRemainingHp('sub', $e_damage);
}
break;
}
}
それぞれダメージ計算終了後に残りHPの減算処理を行なっています。変化技などを使いダメージが0であれば0の減算が行われることになります。
実際のバトルでは、HPが0になった時点でバトルが終了しますが、バトル終了のアクション自体がまだ出来ていないので、一旦このままで進めていきましょう。
現状はHPの回復手段がありません。なので、もしHPが0でバトルが終了すれば全回復するようにホーム画面のコントローラーに簡易策としてリセット処理を入れておきましょう。
ホームコントローラー(/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');
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');
}
}
レベルアップ・進化時の残りHP
次にレベルアップ、進化時の残りHPの計算処理を実装します。
ポケモンではレベルアップすれば、HPの上昇値に合わせてHPが回復します。これは進化した際も同じです。こうすることで、レベルアップしたのにHPが減っているという現象を防ぐことができます。
ポケモンクラス(/Classes/Pokemon.php)
/**
* レベルアップ処理
*
* @return void
*/
protected function actionLevelUp()
{
// 現在のHPを取得
$before_hp = $this->getStats('HP');
// レベルアップ
$this->level++;
// HPの上昇値分だけ残りHPを加算(ひんし状態を除く)
if(!isset($this->sa['SaFainting'])){
$this->calRemainingHp('add', $this->getStats('HP') - $before_hp);
}
$this->setMessage($this->getNickName().'のレベルは'.$this->level.'になった!', 'success');
// 現在のレベルで習得できる技があるか確認
$this->checkMove();
}
/**
* 進化
*
* @return Classes\Pokemon\$after_class
*/
protected function evolve()
{
if(class_exists($this->after_class ?? null)){
// 現在のHPを取得
$before_hp = $this->getStats('HP');
// 進化ポケモンのインスタンスを生成
$pokemon = new $this->after_class($this);
// HPの上昇値分だけ残りHPを加算(ひんし状態を除く)
if(!isset($pokemon->sa['SaFainting'])){
$pokemon->calRemainingHp('add', $pokemon->getStats('HP') - $before_hp);
}
// 進化後のインスタンスを返却
return $pokemon;
}else{
$this->setMessage('このポケモンは進化できません', 'error');
}
}
レベルアップまたは進化前に現在の最大HPを取得し、その後のステータスとの差分を算出しcalRemainingHpで加算処理を行なっています。ただし例外として、ひんし状態であれば加算処理は不要です。ひんし状態でのレベルアップは「ふしぎなアメ」などアイテムを使った際に起こりますが、これによる復活処理を回避するためです。
※初代では復活した覚えがありますがうろ覚えです
rangeを使ったビジュアル
最後にビジュアルを作成して残りHPがどれぐらいあるかを確認できるようにしましょう。CSSで作る予定でしたがinputのrangeが使えるということに気づいてしまったので、こちらを代用します。
input range(HTMLクイックリファレンス)
バトル画面(/battle.php)
<section>
<div class="row mt-3 mb-5">
<?php # 敵ポケモン詳細 ?>
<div class="col-6">
<p><?=$enemy->getName()?> Lv:<?=$enemy->getLevel()?></p>
<p><?=$enemy->getSaName()?></p>
<div class="form-group">
<input type="range" class="form-control-range" min="0" max="<?=$enemy->getStats('HP')?>" value="<?=$enemy->getRemainingHp()?>" style="pointer-events: none;">
</div>
</div>
<div class="col-6 text-center">
<img src="Resources/Assets/img/pokemon/dots/front/<?=get_class($enemy)?>.gif" alt="<?=$enemy->getName()?>">
</div>
</div>
<div class="row mb-3">
<?php # 自ポケモン詳細 ?>
<div class="col-6 text-center">
<img src="Resources/Assets/img/pokemon/dots/back/<?=get_class($pokemon)?>.gif" alt="<?=$pokemon->getName()?>">
</div>
<div class="col-6">
<p><?=$pokemon->getName()?> Lv:<?=$pokemon->getLevel()?></p>
<p><?=$pokemon->getSaName()?></p>
<div class="form-group">
<input type="range" class="form-control-range" min="0" max="<?=$pokemon->getStats('HP')?>" value="<?=$pokemon->getRemainingHp()?>" style="pointer-events: none;">
<p class="text-right px-3"><?=$pokemon->getRemainingHp()?> / <?=$pokemon->getStats('HP')?></p>
</div>
</div>
</div>
</section>
それでは出力結果を見てみましょう。
持ち手が邪魔ですがほぼ完璧です。
ポケモンでは残りHPが1/2、1/4と減っていくことでゲージの色が変わりますが、そのあたりは後ほど対応します。HPバーは変更されても数値に影響はないので構いませんが、念の為pointer-eventsを使って無効化しました。
rangeのminに0、maxに最大HP、valueに残りHPをセットするだけで自動計算してくれるという仕組みです。ちなみにですが、ひんし設定はしていますが判定はしていないので、現状お互い死んでも殴り続けることができるというサイコなゲームとなっています。
まとめ
いかがだったでしょうか。
今回のPHPポケモンは「バトルシステム編〜HP計算〜」についてご紹介しました。
ポケモン好きな人やゲームづくりに興味がある人、現在プログラミング学習に取り組んでいる人は、ぜひ参考にしてみてくださいね。