第13回のPHPポケモンは「UI編」ということで、CSSの大人気フレームワークBootstrapさんにお手伝いいただきます。
また、前回実装したレスポンス機能により進化のアクションに一部不具合が出ていたので、このあたりも合わせて修正をしながら進めていきましょう。
Bootstrapを導入するにあたり、今までindex.phpに書き込んでいたものを分割するため、新しくResourcesの階層を追加しています。
最後にはコード配布とデモページを用意しているので、ぜひ最後まで読んでからご活用ください。
Bootstrapの導入
まずは見た目から見直しをしていきます。ただ、整えるためにCSSを打ち込んでいけばそれだけでかなりの量になってしまうため、前述したとおり今回は大人気CSSのフレームワーク「Bootstrap」のお力を借ります。
とはいっても、線を引いたり余白を整えたりフォームのデザインをお借りするなど、単純なクラスだけしか利用しない(予定)なので、Bootstrapを学習目的の方はその旨ご理解ください。
出力ファイル(/index.php)
# head内に記述
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
ステータス詳細の常時表示化
ステータスや詳細を表示するのに、今まではアクションで選択していましたが、これらは常時確認できるに越したことはありません。なので、もし既にポケモンを選択済みであれば出力するようにしましょう。
出力ファイル(/index.php)
<?php if(isset($controller->pokemon)): ?>
<section>
<div class="row">
<div class="col-12 col-sm-6">
<?php foreach($controller->pokemon->getDetails() as $key => $val): ?>
<dl class="row">
<dt class="col-4"><?=$key?></dt>
<?php if(is_array($val)) $val = implode(',', $val); ?>
<dd class="col-8"><?=$val?></dd>
</dl>
<?php endforeach; ?>
</div>
<div class="col-12 col-sm-6">
<?php foreach($controller->pokemon->getStats() as $key => $val): ?>
<dl class="row">
<dt class="col-4"><?=$key?></dt>
<dd class="col-8"><?=$val?></dd>
</dl>
<?php endforeach; ?>
</div>
</div>
</section>
<?php endif; ?>
技一覧の出力には、再度implodeを使用しています。出力結果は以下のとおりです。
これで選択しているポケモンの詳細がひと目で分かるようになりました。
フォームの分割
次にフォーム部分を見直ししていきます。前回は配列として項目やメソッドを用意しましたが、増えていけば自由度が制限されてしまい、それこそ保守性が悪くなってしまうため。ファイルを分割して管理していきます。
さらに、今までは全データをポストしていましたが、メソッドごとにフォームを用意することで解決ができるということが判明したので、こちらも合わせて実装していきます。
ポケモンの選択フォーム(/Resources/Parcials/Forms/select_pokemon.php)
<form action=" <?=$action_path?>" method="post">
<p>ポケモンを選択してください</p>
<div class="input-group">
<select class="form-control" id="select_pokemon" name="pokemon">
<option>--選択してください--</option>
<?php foreach($controller->getPokemonList() as $class => $pokemon): ?>
<option value="<?=$class?>"><?=$pokemon?></option>
<?php endforeach; ?>
</select>
<div class="input-group-append">
<button type="submit" class="btn btn-primary">ゲット</button>
</div>
</div>
</form>
ニックネームの変更フォーム(/Resources/Parcials/Forms/change_nickname.php)
<form action="<?=$action_path?>" method="post">
<input type="hidden" name="action" value="setNickname">
<div class="input-group mb-3">
<input type="text" class="form-control" placeholder="最大5文字" name="param" aria-describedby="submit-setNickname">
<div class="input-group-append">
<input class="btn btn-primary" type="submit" id="submit-setNickname" value="ニックネームを変更">
</div>
</div>
</form>
経験値の取得フォーム(/Resources/Parcials/Forms/add_exp.php)
<form action="<?=$action_path?>" method="post">
<input type="hidden" name="action" value="setExp">
<div class="input-group mb-3">
<input type="number" class="form-control" min="1" name="param" aria-describedby="submit-setExp">
<div class="input-group-append">
<input class="btn btn-primary" type="submit" id="submit-setExp" value="経験値を取得">
</div>
</div>
</form>
リセットフォーム(/Resources/Parcials/Forms/reset.php)
<form action="<?=$action_path?>" method="post">
<input type="hidden" name="action" value="reset">
<input class="btn btn-danger" type="submit" value="リセット">
</form>
出力用ファイル(/index.php)
<?php
require_once(__DIR__.'/Classes/Controller.php');
session_start();
$controller = new Controller($_POST, $_SESSION);
$action_path = '/';
?>
--省略
<div class="col-12 col-sm-6">
<h2 class="mb-3">メソッドの実行</h2>
<?php if(is_object($controller->pokemon ?? null)): ?>
<?php $_SESSION['pokemon'] = $controller->pokemon; # ポケモンのインスタンスをセッションに格納 ?>
<?php include('Resources/Partials/Forms/change_nickname.php'); # ニックネームの変更?>
<?php include('Resources/Partials/Forms/add_exp.php'); # 経験値の取得 ?>
<?php include('Resources/Partials/Forms/reset.php'); # リセット ?>
<?php else: ?>
<?php
// ポストとセッションをリセット
$_POST = [];
$_SESSION = [];
?>
<?php include('Resources/Partials/Forms/select_pokemon.php'); ?>
<?php endif; ?>
</div>
それぞれのフォームのアクションパス($action_path)はindex.phpで指定しています。現在はフォームの使いまわしをしていませんが、後々使い回す際に役立つために変数で管理しています。
それぞれにフォームを割り当てることで、パラメーターの指定が楽になりましたね。
- アクションが選択不要になったため、hiddenで隠しパラメーターとして用意
- パラメーターのメソッド判定が不要になったため、nameをparamに変更
この仕様に合わせてコントローラー側も修正しましょう。
コントローラー(/Classes/Controller.php)
/**
* @return void
*/
public function __construct($post, $session)
{
if(get_parent_class($session['pokemon'] ?? null) === 'Pokemon'){
// Pokemonのインスタンスに溜まったメッセージとレスポンスデータを初期化
$session['pokemon']->resetMessage();
$session['pokemon']->resetResponse();
}
if(isset($post['pokemon'])){
// ポケモンをゲットした
$this->checkPokemon($post['pokemon']);
}
if(isset($post['action'])){
// アクションを選択した
$this->pokemon = $session['pokemon'] ?? null; # 更新時エラー回避用にnullをセット
// アクションメソッドの実行
$this->action($post['action'], $post['param'] ?? null);
}
}
--省略
/**
* アクション
*
* @param string $action(method_name)
* @param mixed $param
* @return void
*/
private function action($action, $param)
{
if($action === 'reset' || is_null($this->pokemon)){
// 初期化
$this->pokemon = null;
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');
}
}
パラメーター($param)の判別が無くなった分、簡易化することができました。表示を確認してみましょう。
Bootstrapのおかげで大分使いやすくなりました。UIの重要性を改めて感じます。
進化アクションの見直し
次に進化アクションを一部見直したいと思います。前回の仕様で行くと「進化してもメッセージが引き継がれない」という問題がありました。なので、進化後にメッセージを引き継ぐ処理を追加します。
ポケモンクラス(/Classes/Pokemon.php)
/**
* インスタンス作成時に実行される処理
*
* @param object|null
* @return void
*/
public function __construct($before=null)
{
// 進化前のポケモンと一致しているかチェック
if(is_a($before, $this->before_class ?? null)){
// 進化した際の処理
$this->takeOverAbility($before);
// メッセージの引き継ぎ
$this->setMessage($before->getMessages());
$this->setMessage($this->name.'に進化した', 'success');
$this->checkMove();
}else{
// 捕まえた際の処理
$this->setLevel();
$this->setDefaultExp();
$this->setDefaultMove();
$this->setIv();
$this->setMessage($this->name.'をゲットした', 'success');
}
}
進化メッセージの前に、進化前($before)からのメッセージを引き継ぎさせます。これで進化後にメッセージが消えてしまうという不具合を解消することができました。
進化前プロパティの設定
各ポケモンに割り当てていた 進化メソッド(evolve)についても少し見直しをしましょう。
evolveというメソッドはポケモン共通項目です。もちろん進化条件などは様々ですが、現状では特に分けておく必要性もないためポケモンクラスへ移動させましょう。そしてそのためには、進化先を判別できる必要があるので、各ポケモンに進化先のプロパティを用意します。リザードのクラスを例に見てみましょう。
リザードのクラス(/Classes/Pokemon/Lezardo.php)
/**
* 進化前(クラス名)
* @var string
*/
protected $before_class = 'Hitokage';
/**
* 進化後(クラス名)
* @var string
*/
protected $after_class = 'Lizardon';
※前回までは進化前のクラス用プロパティをchild_classとしていましたが、そうなると進化先のクラス用プロパティを「parent_class」とする必要があり、意味が異なってしまうため、before_classに名称変更しました
最終進化のリザードンやライチュウなどは不要のプロパティです。では、ポケモンクラスに移動したevolveメソッドを見直していきましょう。
ポケモンクラス(/Classes/Pokemon.php)
/**
* 進化
*
* @return Classes\Pokemon\$after_class
*/
protected function evolve()
{
if(class_exists($this->after_class ?? null)){
$pokemon = new $this->after_class($this);
// 進化後のインスタンスを返却
return $pokemon;
}else{
$this->setMessage('このポケモンは進化できません', 'error');
}
}
進化前の判別と同じように、class_existsを使って正しい進化先かどうかを判定しています。これで各ポケモンに設定しているevolveメソッドは不要になりましたので、削除しておきましょう。
コードの配布
出力用ファイルを階層分けした関係で大幅な変更が出ているので、第13回終了時点でのコードをGitHubより配布します。ぜひご活用ください。
デモページ
UIの変更により大分遊びやすくなったので、興味ある人はぜひデモページで遊んでみてください。
デモページ
まとめ
いかがだったでしょうか。
今回のPHPポケモンは「UIの変更(Bootstrapの導入)」についてご紹介しました。
ゲームは見た目が重要です。PHPではバックエンドの処理がメインとなるため、どうしてもJavascriptやCSSの力に頼ることが多くなるでしょう。これらは状況に合わせて活用しながら、あくまでPHPでの処理をメインとして紹介していく予定です。
Webプログラミングに興味がある方は、ぜひ参考にしてくださいね。