新しい技を習得
前編に続き、技習得時の処理分岐を作成していきましょう。今回は「新しい技を習得する」です。
前回は覚えようとしている技を諦めるだけだったので、ポケモンのオブジェクトを書き換える必要がありませんでしたね。ですが、新しく覚えようとしている技を既に覚えている技と置き換える場合は、ポケモンオブジェクトの書き換えをする必要があります。
現在は野生のポケモンを倒して技を覚えるという方法しかないため、書き換えはこのタイミングでは不要だと思うかも知れません。ですが、もしこれが相手トレーナーだった場合はいかがでしょうか?
ポケモンを倒しても、次のポケモンが出てくる可能性があるため、オブジェクトが書き換わっていなければ技選択の選択ができなくなります。また、複数の技を覚えたり、2以上のレベルアップの処理についても想定しておかなければなりません。
処理は今までとさほど変わりませんが、データの受け渡しや状況把握が今回複雑になるポイントです。しっかりとオブジェクトや配列の状態を理解しながら進めていきましょう。
忘れさせる技の選択
まずは忘れさせる技の選択です。htmlは前回と同様のモーダルを使用します。そこに必要データをjQueryを使って用意してあげましょう。
バトル画面用JS(/Public/Assets/js/Battle/fight.js)
/**
* 技テーブルクリック時の関数
* @function click
* @return void
**/
var selectForgetMoveInit = function(){
$('.forget-selectmove').on('click', function(){
// 諦めるボタンの無効化切り替え
if($(this).hasClass('new-move')){
$("#btn-abandon-move").prop('disabled', false);
$('#btn-forget-move').hide();
return;
}else{
$("#btn-abandon-move").prop('disabled', true);
}
// 技名を取得
var name = $(this).data('name');
// ボタンに技名をセット
$('#btn-forget-move').find('.move-name')
.text(name);
$('#btn-forget-move').show();
});
}
/**
* 技テーブルクリック時の関数
* @function click
* @return void
**/
var submitForgetMoveInit = function(){
$('#btn-forget-move').on('click', function(){
// 技をフォームへセット
var data = {
id: $(this).data('msg_id'),
forget: $('.forget-selectmove.active').data('num'),
level: $('#level').text(),
hp: $('#hpbar-friend').attr('aria-valuenow')
};
// フォームを用意
$.each(data, function(key, val){
// フォームの最後にパラメーターを追加
$('#remote-form').append(
'<input type="hidden" name="param[' + key + ']" value="' + val + '">'
);
})
// サブミット実行
$('#remote-form-action').val('learn_move');
$('#remote-form').submit();
});
}
「技を忘れさせる」というボタンを押すと、テーブルの行(tr)に用意したdataを取得して、リモートフォームのinputとしてセットしています。postする値は、技選択のモーダルを呼び出したメッセージIDと、忘れる技の添え番、現在のレベル、現在のHPの4つです。
現画面を再現するための必要値
ただ単にデータをポストして、技を書き換えて画面を開始させた場合、至る場所にズレが生じます。その1つが「レベル」です。6から7にアップするだけなら構いませんが、6から8にレベルアップできる量の経験値を取得して、レベル7で技を習得した場合、画面を戻した時点ではレベルが8になってしまいます。レベル7から8になる流れが飛んでしまうのです。
そうならないためにも、現状のレベルを画面再現のためにpostする必要があります。また、レベルアップに合わせてHPが変動する可能性もあります。その際にずれが生じないように現在のHP残量も画面再現用にpostしました。
メッセージIDに関しては、現在どこまで処理が進んでいるかを記録するためにポストしています。
アクションには、新しく「learn_move」という値を指定しました。
また、技習得直後の再現には現在のレスポンスデータとメッセージデータの引き継ぎ処理が必要になるためバトル画面で前画面の情報をセッションに用意しておきましょう。
バトル画面ヘッド(/Resources/Partials/Layouts/Head/battle.php)
// レスポンスデータを引き継ぎ用にセッションへ格納
$_SESSION['__data']['before_reponses'] = $responses;
$_SESSION['__data']['before_messages'] = $controller->getMessages();
技の置き換え処理
では技の置き換え処理(learn_move)の分岐とサービスを作成していきましょう。
バトルコントローラー(/App/Controllers/Battle/BattleController.php)
// ブランチメソッド内分岐
/******************************************
* 技の習得
*/
case 'learn_move':
// サービス実行
$service = new LearnMoveService(
$this->pokemon,
$_SESSION['__data']['before_reponses'],
$_SESSION['__data']['before_messages'],
$this->request('param')
);
$service->execute();
// 描画するポケモン情報を置き換え
$this->before['friend'] = $service->getTmpPokemon();
break;
技習得用サービス(/App/Services/Battle/LearnMoveService.php)
<?php
$root_path = __DIR__.'/../../..';
// 親クラス
require_once($root_path.'/App/Services/Service.php');
// トレイト
require_once($root_path.'/App/Traits/Service/Battle/ServiceBattleCheckTrait.php');
/**
* 技の習得処理
*/
class LearnMoveService extends Service
{
use ServiceBattleCheckTrait;
/**
* @var Pokemon:object
*/
protected $pokemon;
/**
* @var Pokemon:object
*/
protected $tmp_pokemon;
/**
* @var array
*/
protected $before_responses;
/**
* @var array
*/
protected $request;
/**
* @return void
*/
public function __construct($pokemon, $before_responses, $before_messages, $request)
{
$this->pokemon = $pokemon;
$this->before_responses = $before_responses;
$this->before_messages = $before_messages;
$this->request = $request;
}
/**
* @return void
*/
public function execute()
{
$this->replaceMove();
$this->tmp_pokemon = $this->createTmpPokemon();
// メッセージとレスポンスの引き継ぎ
$this->setMessage(
$this->getUntreatedResponses($this->before_messages, $this->request['id'], true)
);
$this->setResponse(
$this->getUntreatedResponses($this->before_responses, $this->request['id'])
);
}
/**
* @return Pokomon:object
*/
public function getTmpPokemon()
{
return $this->tmp_pokemon;
}
/**
* 表示用のポケモンオブジェクトを生成
* @return Pokemon:object
*/
private function createTmpPokemon()
{
$pokemon = clone $this->pokemon;
// クローンオブジェクトにレベルと残HPをセット
$pokemon->setLevel($this->request['level']);
$pokemon->setRemainingHp($this->request['hp']);
$pokemon->setDefaultExp();
return $pokemon;
}
/**
* 技の置き換え
* @return void
*/
private function replaceMove()
{
// 忘れる技を取得
$forget_move = $this->pokemon
->getMove($this->request['forget']);
// 覚えさせる技を取得
$new_move = new $this->before_responses[$this->request['id']]['move'];
// 技を覚えさせる
$this->pokemon
->setMove($new_move, $this->request['forget']);
// メッセージの返却
$this->setMessage('1 2の ……ポカン!');
$this->setMessage($this->pokemon->getNickname().'は、'.$forget_move->getName().'の使い方をキレイに忘れた!そして......');
$this->setMessage($this->pokemon->getNickname().'は新しく、'.$new_move->getName().'を覚えた!');
}
/**
* 未処理メッセージ・レスポンスの引き継ぎ処理
* @return void
*/
private function getUntreatedResponses($responses, $msg_id, $msg=false)
{
if($msg){
/**
* メッセージの処理
*/
$key = array_search(
$msg_id,
array_column($responses, 1),
true
);
// 対象メッセージを含め3つ目までを削除した配列を返却
return array_splice($responses, $key + 3);
}else{
/**
* レスポンスの処理
*/
// メッセージIDのレスポンスが入った位置を取得
$key = array_search(
$msg_id,
array_keys($responses),
true
);
// メッセージIDより後のレスポンスを返却
return array_splice($responses, $key + 1);
}
}
}
まずは技の置き換え処理の部分から見ていきましょう。
/**
* 技の置き換え
* @return void
*/
private function replaceMove()
{
// 忘れる技を取得
$forget_move = $this->pokemon
->getMove($this->request['forget']);
// 覚えさせる技を取得
$new_move = new $this->before_responses[$this->request['id']]['move'];
// 技を覚えさせる
$this->pokemon
->setMove($new_move, $this->request['forget']);
// メッセージの返却
$this->setMessage('1 2の ……ポカン!');
$this->setMessage($this->pokemon->getNickname().'は、'.$forget_move->getName().'の使い方をキレイに忘れた!そして......');
$this->setMessage($this->pokemon->getNickname().'は新しく、'.$new_move->getName().'を覚えた!');
}
覚えさせる技はについては、セッションから受け取った前画面のレスポンスから取り出します。フォームで技クラスを送信してしまうと、ユーザー側が直接書き換えられることで、覚えるはずのない技を習得してしまう可能性があるためです。
技を覚えさせる際に、第2引数で忘れさせる技番号を選択しています。もしこちらが該当しない番号であれば、初期値として0番を渡しているので、一番上にある技が削除対象となります。
技の置き換え処理後は、必要なメッセージを順番にセットしていきましょう。
ダミーのポケモンオブジェクト
他の処理では、開始時には前の状態のポケモンオブジェクトをスタート時点に描画していました。ですが、今回は処理の途中のため、このまま返却すれば前処理を終えた状態のオブジェクトが描画されてしまいます。そうしないためにも、送信したパラメーターを使って描画用のダミーのポケモンオブジェクトを用意する必要があります。
/**
* 表示用のポケモンオブジェクトを生成
* @return Pokemon:object
*/
private function createTmpPokemon()
{
$pokemon = clone $this->pokemon;
// クローンオブジェクトにレベルと残HPをセット
$pokemon->setLevel($this->request['level']);
$pokemon->setRemainingHp($this->request['hp']);
$pokemon->setDefaultExp();
return $pokemon;
}
まず、ポケモンオブジェクトをクローンで複製し、そこにパラメーターで受け取ったレベルと残りHPをセットしています。経験値バーもレベルアップ直後は0位置スタートさせるため、現在のレベルの初期経験値を再セットしました。
ここで、ポケモンのオブジェクトを新規作成せずクローンで生成している理由は、個体値と努力値の問題です。
新しく生成すれば、もちろん努力値はすべて0、個体値も異なった「別個体」を使って描画することになります。こうげきやすばやさなどのステータスは良いかも知れませんが、HPは目に見える値のためズレが生じてしまいます。
そうならないためにも、同じ個体の複製を描画用として生成しましょう。
setLevelなどprotectedで用意していたメソッドは、サービスからでも呼び出せるようにpublicに変更しておいてください。
生成したクローンは、最終的にコントローラーで置き換えています。
レスポンスの引き継ぎ
最後にレスポンスの引き継ぎです。連続レベルアップ処理などを想定した場合、リクエストデータとメッセージデータは引き継ぎしなければなりません。なので、postしたメッセージIDより後のデータだけを配列から取り出して、レスポンスとして返却してあげましょう。
/**
* 未処理メッセージ・レスポンスの引き継ぎ処理
* @return void
*/
private function getUntreatedResponses($responses, $msg_id, $msg=false)
{
if($msg){
/**
* メッセージの処理
*/
$key = array_search(
$msg_id,
array_column($responses, 1),
true
);
// 対象メッセージを含め3つ目までを削除した配列を返却
return array_splice($responses, $key + 3);
}else{
/**
* レスポンスの処理
*/
// メッセージIDのレスポンスが入った位置を取得
$key = array_search(
$msg_id,
array_keys($responses),
true
);
// メッセージIDより後のレスポンスを返却
return array_splice($responses, $key + 1);
}
}
似た処理になるので1メソッドにまとめ引数で分岐させていますが、それぞれメソッドを用意しても構いません。
メッセージの場合、メッセージIDは配列内の2つ目(1)の要素として格納されています。なので、array_columnとarray_searchを組み合わせてメッセージIDの現在位置以降を判別しています。
配列からの切り出しにはarray_splice関数を使います。
array_splice(PHP.net)
モーダル生成時には「諦めメッセージ」と空メッセージが、セットで格納されているため、取得したキー+3の位置より後のメッセージにずらして返却しています。
レスポンスはIDをキーにして格納されているので、array_columnの代わりにarray_keysとarray_searchを使って該当位置を特定しています。
- レスポンスとメッセージの前画面からの引き継ぎについてですが、現処理であれば超低確率でメッセージIDが重複する可能性があります。ですが、現状メッセージIDを引き継ぎ後に生成していませんので、こちらは一旦保留としておきます。
- 現在未実装ですが、モーダルに関しても引き継ぎ処理が必要になります。検証前なのでこちらも一旦保留としています。
それでは、新しく覚えようとしている技の習得処理を実装していきましょう。
既に覚えていた技を忘れ、新しい技を習得することができました。
これで技習得処理は完成です。
まとめ
いかがだったでしょうか。
今回・前回のPHPポケモンでは「技習得時の処理」を作成しました。
現在プログラミング学習に取り組んでいる方、ゲームづくりに興味がある人は、ぜひ参考にしてみてくださいね。