システムを組むなら、仕様書や設計書はしっかり作りましょう。
ということで、またまたフォルダ移動やページ分けなどを見えないところでやりました。正直説明すると全く進まなくなりそうなので、改修部分は必要最低限にします。
結論、説明しません。(コード配布するので許してください)
そして今回は、本格的なバトルシステム実装に向けた第一歩、タイプ相性の判定を作り込んでいきます。
バトルシステムの実装
まず、ポケモンのバトルシステムについて、簡単におさらいしていきましょう。
ポケモンの第2世代からはダブルバトルという2対2で戦うシステムが追加され、その後の世代ではマルチバトルなど戦う形式も豊富になりました。ですが、今回は初代を再現するということで、1対1のバトルのみを想定して作成します。
技選択をして行われる判定は以下の通りです。(順不同)
- 素早さの判定(早い方が先に行動)
- 技タイプと相手ポケモンのタイプ相性の判定
- 技タイプと攻撃ポケモンのタイプ一致の判定
- 攻撃種別(物理・特殊・変化)の判定
- 急所判定
- 命中率(回避率)の判定
- ダメージ量の計算
- 追加効果の判定
ざっとまとめてこんな感じです。(漏れあるかも知れません)
流石にこれを一気に組み上げるとなると膨大な量になってしまうので、一つずつ組み上げていきます。
「たたかう」アクションの作成
それではまず、「たたかう」というアクションの分岐をコントローラーに追加します。
バトル用コントローラー(/Classes/Controller/BattleController.php)
/**
* アクション
*
* @param string $action
* @param mixed $param
* @return void
*/
private function action($action, $param)
{
switch ($action) {
// にげる
case 'run':
unset($_SESSION['enemy']);
header("Location: ./home.php", true, 307) ;
exit;
// たたかう
case 'fight':
$message = $this->attack($this->pokemon, $this->enemy, $param);
$this->setMessage($message);
break;
}
}
※海外版ポケモンのバトル画面を見たところ、にげるは「run」と表記されていたため、変更しています
たたかう(fight)のアクションが選択された場合は、attackというメソッドを実行します。こちらは新しくトレイトに作成しました。
今回は、すべてを作り込むわけではなく、一旦メッセージだけを返却してもらうので、受け取ったものをコントローラーでメッセージとしてセットして結果を確認しましょう。
タイプ相性の判定
では、バトルシステムの中では最も簡単(だと思われる)相性判定を行います。
先程コントローラーで記述したattackのメソッドが含まれているバトル用トレイトを見ていきましょう。
攻撃トレイト(/Traits/Battle/AttackTrait.php)
<?php
trait AttackTrait
{
/**
* 攻撃する
*
* @param object $atk_pokemon
* @param object $def_pokemon
* @param string $move_class
* @return array
*/
protected function attack($atk_pokemon, $def_pokemon, $move_class)
{
// 技のインスタンスを取得
$move = $this->getInstance($move_class);
// タイプ相性チェック
$type_comp = $this->checkTypeCompatibility($move->getType(), $def_pokemon->getTypes());
return $type_comp['message'];
}
/**
* タイプ相性チェック
*
* @param object $atk_type
* @param array $def_types
* @return array
*/
private function checkTypeCompatibility($atk_type, $def_types)
{
// ダメージ補正(初期値は等倍)
$damage_comp = 1;
// 補正判定
foreach($def_types as $def_type){
// 「こうかがない」かチェック
if(in_array($def_type, $atk_type->getAtkDoesntAffectTypes(), true)){
// ダメージ無し
$damage_comp = 0;
// ループ終了
break;
}
// 「こうかばつぐん」かチェック
if(in_array($def_type, $atk_type->getAtkExcellentTypes(), true)){
// 2倍
$damage_comp *= 2;
// 次の処理へスキップ
continue;
}
// 「こうかいまひとつ」かチェック
if(in_array($def_type, $atk_type->getAtkNotVeryTypes(), true)){
// 半減
$damage_comp /= 2;
}
}
// 補正によるメッセージの分岐
if($damage_comp === 0){
$message = 'こうかがないみたいみたいだ';
}elseif($damage_comp > 1){
$message = 'こうかはばつぐんだ!';
}elseif($damage_comp < 1){
$message = 'こうかはいまひとつだ';
}
// ダメージ補正とメッセージを配列にして返却
return [
'damage_comp' => $damage_comp,
'message' => $message ?? '',
];
}
}
それでは順番に見てきましょう。まずは親となるattackというメソッドについてです。
/**
* 攻撃する
*
* @param object $atk_pokemon
* @param object $def_pokemon
* @param string $move_class
* @return array
*/
protected function attack($atk_pokemon, $def_pokemon, $move_class)
{
// 技のインスタンスを取得
$move = $this->getInstance($move_class);
// タイプ相性チェック
$type_comp = $this->checkTypeCompatibility($move->getType(), $def_pokemon->getTypes());
// 技種類での分岐
switch ($move->getSpecies()) {
// 物理
case 'physical':
// ここに物理技の処理
break;
// 特殊
case 'special':
// ここに特殊技の処理
break;
// 変化
case 'status':
// ここに変化技の処理
break;
}
return $type_comp['message'];
}
第1引数では攻撃ポケモン、第2引数では防御ポケモン、第3引数では技のクラス名を受け取っています。
まず最初に、受け取った技クラスをgetInstanceでインスタンス化します。相性チェックでは、攻撃技のタイプと、防御ポケモンのタイプがあれば判定が可能なので、それぞれを引数としてタイプ相性チェック用のメソッド(checkTypeCompatibility)にかけます。
/**
* タイプ相性チェック
*
* @param object $atk_type
* @param array $def_types
* @return array
*/
private function checkTypeCompatibility($atk_type, $def_types)
{
// ダメージ補正(初期値は等倍)
$damage_comp = 1;
// 補正判定
foreach($def_types as $def_type){
// 「こうかがない」かチェック
if(in_array($def_type, $atk_type->getAtkDoesntAffectTypes(), true)){
// ダメージ無し
$damage_comp = 0;
// ループ終了
break;
}
// 「こうかばつぐん」かチェック
if(in_array($def_type, $atk_type->getAtkExcellentTypes(), true)){
// 2倍
$damage_comp *= 2;
// 次の処理へスキップ
continue;
}
// 「こうかいまひとつ」かチェック
if(in_array($def_type, $atk_type->getAtkNotVeryTypes(), true)){
// 半減
$damage_comp /= 2;
}
}
// 補正によるメッセージの分岐
if($damage_comp === 0){
$message = 'こうかがないみたいみたいだ';
}elseif($damage_comp > 1){
$message = 'こうかはばつぐんだ!';
}elseif($damage_comp < 1){
$message = 'こうかはいまひとつだ';
}
// ダメージ補正とメッセージを配列にして返却
return [
'damage_comp' => $damage_comp,
'message' => $message ?? '',
];
}
まず、$damage_compという変数に初期値として1をセットしています。これは、ダメージ補正値になります。タイプ相性が関係なければ、ダメージ計算をして1をかけます。攻撃技のタイプは1つですが、防御ポケモンのタイプは1〜2つが想定されます。なので、防御ポケモンのタイプをforeachで1つずつ判定していきます。
判定する方法は、攻撃する技が防御ポケモンのタイプにとって「こうかばつぐん」「こうかいまひとつ」「こうかがない」のいずれかに存在しているかを、順番にin_arrayを使って検証しています。
まず「こうかがない」のタイプ(getAtkDoesntAffectTypes)の配列に含まれているかどうかを検証します。
// 「こうかがない」かチェック
if(in_array($def_type, $atk_type->getAtkDoesntAffectTypes(), true)){
// ダメージ無し
$damage_comp = 0;
// ループ終了
break;
}
効果がないと判定されると、もう一つのタイプがどうであれダメージを与えることができません。なので、もし該当した場合は$damage_compに0をセットして、breakを使ってループ自体を終了させます。
残りの2つは順不同です。今回は「こうかばつぐん」を先にチェックしています。
// 「こうかばつぐん」かチェック
if(in_array($def_type, $atk_type->getAtkExcellentTypes(), true)){
// 2倍
$damage_comp *= 2;
// 次の処理へスキップ
continue;
}
「こうかばつぐん」であれば、ダメージ量が倍になります。もし2タイプともにこうかばつぐんの判定がでれば、1x2x2=4倍のダメージが与えられます。「こうかばつぐん」の判定がされた後、「こうかいまひとつ」と判定されることはありえないので、現在ループしているタイプでの判定を終了するため、continueを使って残りの処理はスキップします。
最後は「こうかいまひとつ」の判定をします。
// 「こうかいまひとつ」かチェック
if(in_array($def_type, $atk_type->getAtkNotVeryTypes(), true)){
// 半減
$damage_comp /= 2;
}
もしタイプが該当していれば、補正値割る2をしています。「こうかばつぐん」→「こうかいまひとつ」という判定をされた場合は、1x2/2=1になり、ダメージ補正無し(等倍)に戻ります。
補正値を計算したら、相性に対応したメッセージを出力します。
// 補正によるメッセージの分岐
if($damage_comp === 0){
$message = 'こうかがないみたいみたいだ';
}elseif($damage_comp > 1){
$message = 'こうかはばつぐんだ!';
}elseif($damage_comp < 1){
$message = 'こうかはいまひとつだ';
}
補正値が0 →「こうかがない」
補正値が1超過 →「こうかばつぐん」
補正値が1未満 →「こうかいまひとつ」
補正値が1 →「等倍(メッセージ不要)」
上記の判定をしています。メッセージ格納後は、補正値と一緒に返り値とするため、配列に格納してretrunを行います。
// ダメージ補正とメッセージを配列にして返却
return [
'damage_comp' => $damage_comp,
'message' => $message ?? '',
];
あとはattackのメソッドで受け取ったメッセージを返却するだけです。今回ダメージ計算は行わないため、補正値は使用しません。
技選択フォームの実装(jQuery)
PHPポケモンという名称ですが、(HTML,CSSを除いて)100%PHPで組み上げるようなことはしません。JavaScript(今回はjQuery)のお力も借ります。頑張れば、PHPだけでも十分に再現は可能です。しかし、私は頑張りません。
前回実装した通り、技はテーブルから選択する形式です。記述量が多くなったので、パーツとして分割しておきます。
技選択フォーム(/Resources/Partials/Battle/Forms/move.php)
<form action="" method="post" id="fight-form">
<input type="hidden" name="action" value="fight">
<input type="hidden" name="param" id="fight-form-param">
<div class="input-group mb-3">
<table class="table table-bordered table-hover mb-3" id="move-table">
<thead class="thead-light">
<tr>
<th scope="col">使える技</th>
<th scope="col">タイプ</th>
<th scope="col">PP</th>
</tr>
</thead>
<tbody>
<?php foreach($pokemon->getMove() as $move): ?>
<tr class="move-table-row" data-move_class="<?=get_class($move)?>">
<th scope="row" class="w-50"><?=$move->getName()?></th>
<td><?=$move->getType()->getName()?></td>
<td><?=$move->getPp()?>/<?=$move->getPp()?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</form>
<script>
// jQueryでsubmitを実行
$(document).ready(function() {
$('.move-table-row').click(function(){
// 技をフォームへセット
$('#fight-form-param').val($(this).data('move_class'));
// サブミット実行
$('#fight-form').submit();
});
});
</script>
※battle.phpのheadでjQueryのライブラリを読み込んでいることが前提になります
前回から追加された部分として、テーブルをfight-formというidのフォームタグで囲い、hiddenのinputとしてactionとparamを用意しています。paramには選択された技クラスを格納するため、idにfight-form-paramを設定してjQueryを使用します。
テーブル内での追加要素としては、技の行(tbody内のtr)にクラス名(move-table-row)を付け、data-move_classに技クラス名を持たせています。
それではjs内を見てみましょう。
<script>
// jQueryでsubmitを実行
$(document).ready(function() {
$('.move-table-row').click(function(){
// 技をフォームへセット
$('#fight-form-param').val($(this).data('move_class'));
// サブミット実行
$('#fight-form').submit();
});
});
</script>
move-table-rowのクラス要素がクリックされたら、fight-form-paramのid要素の値(value)に、クリックされた要素がもつdata-move_classの値をセットして、フォームをサブミット(送信)しています。
もっと簡単に説明すると、技がクリックされたら、クリックされた技のクラス名をフォームにセットして送信までしてくれているということです。
これで、データの送信処理は完了です。それでは動きを確認してみましょう。
ヒトカゲ vs フシギダネ
ピカチュウ vs フシギダネ
各技タイプに合った判定が返ってきていることがわかりますね。
これでバトルシステムにおけるタイプ判定が実装完了です。
デモページ
久々にデモページを開放します。見た目が若干良くなっていますが、システムとしてはそこまで精巧に組まれていないため、負荷をかけすぎないように節度ある楽しみ方をしてください。
コードの配布
第19回終了時点でのコードを配布します。
※ホーム画面と初期画面をわけたり、リダイレクト処理の部分については説明をスキップしているので、配布コードを参考にしてください
まとめ
いかがだったでしょうか。
今回のPHPポケモンは「バトルシステム実装編〜タイプ相性の判定〜」をご紹介しました。
サンプルコードを使って、ぜひ楽しみながらの学習に役立ててくださいね。