プログラミング

連続の技習得編 オブジェクトをセッションへ格納 PHPポケモン57

PHP PHPポケモン ポケモン
連続の技習得編 オブジェクトをセッションへ格納 PHPポケモン57

セッション経由でのオブジェクト引き継ぎ

技習得の処理が整ってきたので、ここで連続技習得・連続レベルアップ時にも問題なく動作するように作り込んでいきます。ですが、現状のモーダルをレスポンスやメッセージと同様に、そのまま引き継いだとしてもエラーが発生します。

その原因がセッション経由でのオブジェクト渡しです。

PHPポケモン「オートロード編(修正版)」17 おまけ:日本語化 PHPポケモン「オートロード編(修正版)」17 おまけ:日本語化

  前回実装したオートローダーの使い方が盛大に間違っていたので、今回その間違いの説明をしながら、正しい実装方法をご紹介します。 申し訳ありません。(誠意)    オートロードについて(再)  必要なタイミングで必要なファイルをrequireまたはincludeするあれです。   前回spl_autoload_reg...

 

第17回で取り上げましたが、オブジェクトはセッションで次のページへ受け渡しすると、不完全なオブジェクト(__PHP_Incomplete_Class)になります。技習得時のレスポンスにオブジェクトは格納されていませんでしたが、モーダルでは新しい技のオブジェクトなどが含まれていますね。

 

現状のシステム構成のまま進めていくのであれば、解決策は2通りです。

  1. オブジェクトではなく配列形式で受け渡しをする
  2. オブジェクトをシリアライズする

 

まず、配列形式での受け渡しについてです。こちらは、現在のポケモン情報のようにexportなどのメソッドを用意して、画面移管後に新しくインスタンスを生成する方法です。

ですがポケモンのオブジェクトとは違い、レスポンスやモーダルは今後も増えていく可能性が高いです。その都度、配列形式を意識したデータの受け渡しをするとなれば大変ですね。

 

なので、今回は「オブジェクトのシリアライズ」で実装していきましょう。

 

シリアライズ(文字列化)

まず、シリアライズについて簡単に説明しておきます。PHPの関数を使って、オブジェクトを文字列化させることができます。jsonに似たような形式です。

シリアライズすれば、受け取り画面ではアンシリアライズの関数を実行することでオブジェクトを復元することが可能です。

 

今回使用するシリアライズ用の関数はserializeです。

 

一括でシリアライズさせるために、トレイトを用意してコントローラーとサービスで呼び出せるようにしておきましょう。

 

シリアライズトレイト(/App/Traits/SerializeTrait.php
<?php
 
trait SerializeTrait
{
    /**
    * オブジェクトのシリアライズ化
    * @param arg:mixed
    * @return mixed
    */
    public function serializeObject($arg)
    {
        if(is_array($arg)){
            // 配列の場合はループ
            $result = [];
            foreach($arg as $key => $val){
                $result[$key] = $this->serializeObject($val);
            }
            $arg = $result;
        }else if(is_object($arg)){
            // オブジェクトの場合はシリアライズ
            $arg = ['__serialize' => serialize($arg)];
        }
        return $arg;
    }
}

 

一括の場合は配列、単体の場合はオブジェクトで受け取ることになるので、引数はmixed想定で作成しています。

 

もし配列であれば、foreachを使って再度serializeObjectを呼び出しています。

受け取った値がオブジェクトであれば、serialize関数で文字列に変換します。ここで、そのまま返してしまうと復号化(アンシリアライズ)させる時に「どれを復号化すれば良いのか」がわからなくなってしまいます。個別で行なっても構いませんが、できれば一括で復号化させたいので、配列形式で返却しましょう。判別用として、キーを「__serialize」としています。

 

コントローラーとサービスの親クラスでトレイトを読み込み、格納時にシリアライズ化しましょう。

 

バトル画面ヘッド(/Resources/Partials/Layouts/Head/battle.php
// レスポンスはシリアライズ化
$_SESSION['__data']['before_reponses'] = $controller->serializeObject(
    $controller->getResponses()
);
$_SESSION['__data']['before_messages'] = $controller->getMessages();

 

現状、メッセージにはオブジェクトを格納することがありませんので、レスポンスのみシリアライズさせています。

 

アンシリアライズ(復号化)

次にアンシリアライズ(復号化)についてです。こちらもシリアライズトレイト内に、配列形式を想定してメソッドを作成しましょう。

 

シリアライズトレイト(/App/Traits/SerializeTrait.php
/**
* オブジェクトのアンシリアライズ化
* @param arg:mixed
* @return mixed
*/
public function unserializeObject($arg)
{
    // 配列の処理
    if(is_array($arg)){
        if(
            count($arg) === 1
            && isset($arg['__serialize'])
        ){
            // 配列の中身がシリアライズされたオブジェクトであればオブジェクト化
            $arg = unserialize($arg['__serialize']);
        }else{
            // 通常配列の場合はループ
            $result = [];
            foreach($arg as $key => $val){
                $result[$key] = $this->unserializeObject($val);
            }
            $arg = $result;
        }
    }
    return $arg;
}

 

配列の場合はループをさせていますが、もし配列内の個数が1つ__serializeというキーを持っていれば、unserialize関数を呼び出しています。

 

アンシリアライズ化のメソッドは、サービスでデータを受け取った際に使用します。

 

技習得用サービス(/App/Serivces/Battle/LearnMoveService.php
/**
* @param Pokemon:object
* @param before_response:array
* @param before_messages:array
* @param request:array
* @return void
*/
public function __construct($pokemon, $before_responses, $before_messages, $request)
{
    $this->pokemon = $pokemon;
    $this->before_responses = $this->unserializeObject($before_responses);
    $this->before_messages = $before_messages;
    $this->before_modals = $this->unserializeObject($before_modals);
    $this->request = $request;
}

 

これでオブジェクトの状態でデータを引き継ぐことができます。

※シリアライズする際は、データ改ざんの可能性を想定してハッシュ化させて照合したり、様々な対策をした上で行うことが推奨されています。また、現在はjsonを使ったデータの受け渡しが主流かつ安全と言われているため、本格的なシステム開発では十分注意して導入しましょう。

  

モーダルの引き継ぎ

それでは、モーダルの引き継ぎ処理を作成していきましょう。

 まずは、レスポンスやメッセージと同様に前回情報をセッションに格納し、技習得モーダルで受け取ります。

 

バトル画面ヘッド(/Resources/Partials/Layouts/Head/battle.php
//モーダルはシリアライズ化
$_SESSION['__data']['before_modals'] = $controller->serializeObject(
    $controller->getModalss()
);

 

バトルコントローラー(/App/Controller/Battle/BattleController.php
// branchメソッドswitch分岐
/******************************************
* 技の習得
*/
case 'learn_move':
// サービス実行
$service = new LearnMoveService(
    $this->pokemon,
    $_SESSION['__data']['before_reponses'],
    $_SESSION['__data']['before_messages'],
    $_SESSION['__data']['before_modals'],
    $this->request('param')
);
$service->execute();
// 描画するポケモン情報を置き換え
$this->before['friend'] = $service->getTmpPokemon();
break;

 

技習得用サービス(/App/Serivces/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;
 
    /**
    * @param Pokemon:object
    * @param before_response:array
    * @param before_messages:array
    * @param before_modals:array
    * @param request:array
    * @return void
    */
    public function __construct($pokemon, $before_responses, $before_messages, $before_modals, $request)
    {
        $this->pokemon = $pokemon;
        $this->before_responses = $this->unserializeObject($before_responses);
        $this->before_messages = $before_messages;
        $this->before_modals = $this->unserializeObject($before_modals);
        $this->request = $request;
    }
 
    /**
    * @return void
    */
    public function execute()
    {
        // 描画用ポケモンオブジェクトの作成
        $this->tmp_pokemon = $this->createTmpPokemon();
        // 技の置き換え
        $this->replaceMove();
        // レスポンスの引き継ぎ
        $this->setResponse(
            $this->getUntreatedResponses($this->before_responses, $this->request['id'])
        );
        // メッセージの引き継ぎ
        $this->setMessage(
            $this->getUntreatedResponses($this->before_messages, $this->request['id'], 'message')
        );
        // モーダルの引き継ぎ
        $this->setModal(
            $this->getUntreatedResponses($this->before_modals, $this->request['id'], 'modal'), true
        );
    }
 
    /**
    * @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().'を覚えた!');
    }
 
    /**
    * 未処理レスポンス・メッセージ・モーダルの引き継ぎ処理
    * @param response:array
    * @param msg_id:string
    * @param param:string::response|message|modal
    * @return array
    */
    private function getUntreatedResponses(array $responses, string $msg_id, string $param='response')
    {
        $cnt = 1;
        switch ($param) {
            /********
            * メッセージの引き継ぎ
            */
            case 'message':
            $key = array_search(
                $msg_id,
                array_column($responses, 1), # メッセージIDの位置は1番目
                true
            );
            // 対象メッセージを含め3つ目までを削除
            $cnt = 3;
            break;
            /********
            * モーダルの引き継ぎ
            */
            case 'modal':
            $key = array_search(
                $msg_id,
                array_column($responses, 0), # メッセージIDの位置は0番目
                true
            );
            break;
            /********
            * レスポンスの引き継ぎ
            */
            default:
            // メッセージIDのレスポンスが入った位置を取得
            $key = array_search(
                $msg_id,
                array_keys($responses),
                true
            );
            break;
        }
        // 未処理だけを切り出して返却
        return array_splice($responses, $key + $cnt);
    }
 
}

 

現在覚えている技

ここから前回までに実装した機能の修正箇所です。まずは技モーダルの返却値についてです。

 

ポケモンクラスチェック用トレイト(/App/Traits/Class/Pokemon/ClassPokemonCheckTrait.php
// checkLevelMoveメソッド(旧:checkMove)
 
// モーダル用のレスポンスをセット
$this->setModal([
    'id' => $msg_id,
    'modal' => 'selectmove',
    'new_move' => $move
]);

 

前回は現在覚えている技に新しい技をマージして返却していましたが、それでは連続して技を覚える際に、新しく覚えた技が反映されていないオブジェクトが格納されたままになります。

これを回避するために、モーダルには現在の技+返却された技を出力させます。

 

忘れさせる技選択用モーダル(/Resources/Partials/Battle/Modals/selectmove.php
<div class="modal-body">
    <?php # 覚えている技 ?>
    <table class="table table-bordered table-sm table-hover">
        <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 $key => $move): ?>
                <tr class="move-detail-link forget-selectmove <?php if($key === 4) echo 'active new-move'; ?>"
                    data-modal="#<?=$modal['id']?>-modal"
                    data-target="#<?=$modal['id']?>_<?=get_class($move['class'])?>-content"
                    data-name="<?=$move['class']->getName()?>"
                    data-num="<?=$key?>">
                    <th scope="row" class="w-50"><?=$move['class']->getName()?></th>
                    <td><?=$move['class']->getType()->getName()?></td>
                    <td><?=$move['remaining']?>/<?=$move['class']->getPp($move['correction'])?></td>
                </tr>
            <?php endforeach; ?>
            <?php # 覚えようとしている技 ?>
            <tr class="move-detail-link forget-selectmove active new-move"
                data-modal="#<?=$modal['id']?>-modal"
                data-target="#<?=$modal['id']?>_<?=get_class($modal['new_move'])?>-content"
                data-name="<?=$modal['new_move']->getName()?>"
                data-num="<?=$key?>">
                <th scope="row" class="w-50"><?=$modal['new_move']->getName()?></th>
                <td><?=$modal['new_move']->getType()->getName()?></td>
                <td><?=$modal['new_move']->getPp()?>/<?=$modal['new_move']->getPp()?></td>
            </tr>
        </tbody>
    </table>
    <?php # 技説明 ?>
    <div class="overflow-auto p-3 border" style="height:160px;">
        <?php foreach($pokemon->getMove() as $key => $move): ?>
            <div class="move-detail-content" id="<?=$modal['id']?>_<?=get_class($move['class'])?>-content">
                <h6><?=$move['class']->getName()?></h6>
                <hr>
                <p><?=$move['class']->getDescription()?></p>
            </div>
        <?php endforeach; ?>
        <?php # 覚えようとしている技 ?>
        <div class="move-detail-content active" id="<?=$modal['id']?>_<?=get_class($modal['new_move'])?>-content">
            <h6><?=$modal['new_move']->getName()?></h6>
            <hr>
            <p><?=$modal['new_move']->getDescription()?></p>
        </div>
    </div>
</div>

 

IDの重複回避

次にIDの重複回避です。モーダルは複数呼び出されることがあるため、IDを使用する場合は重複回避対策が必須となります。

ボタン等でIDを使用していたため、こちらをクラスに置き換えています。

<div class="modal-footer">
    <?php # 忘れさせるボタン ?>
    <button type="button"
    class="btn btn-danger btn-sm btn-forget-move"
    data-modal="#<?=$modal['id']?>-modal"
    data-msg_id="<?=$modal['id']?>"
    style="display:none;">
    <span class="move-name"></span>を忘れる</button>
    <?php # 諦めるボタン ?>
    <button type="button"
    class="btn btn-secondary btn-sm action-message-box btn-abandon-move"
    data-dismiss="modal">
    <?=$modal['new_move']->getName()?>を諦める</button>
</div>

 

また、jsで対象を指定する際に別のモーダルの値を取ってきてしまわないように、クリック発火するエレメントにはdata属性のmodalに現在のモーダルの判別値を格納しています。

 

これに合わせて、js側も修正しましょう。

  

バトル画面用js/Public/Assets/js/Battle/fight.js
/*----------------------------------------------------------
// 初期化する関数
----------------------------------------------------------*/
 
/**
* 技テーブルクリック時の関数
* @function click
* @return void
**/
var clickMoveInit = function(){
    $('.move-table-row').on('click', function(){
        // 技をフォームへセット
        $('#fight-form-param').val($(this).data('key'));
        // サブミット実行
        $('#fight-form').submit();
    });
}
 
/**
* 技テーブルクリック時の関数
* @function click
* @return void
**/
var selectForgetMoveInit = function(){
    $('.forget-selectmove').on('click', function(){
        var modal = $(this).data('modal');
        // 諦めるボタンの無効化切り替え
        if($(this).hasClass('new-move')){
            $(modal).find(".btn-abandon-move")
            .prop('disabled', false);
            $(modal).find('.btn-forget-move')
            .hide();
            return;
        }else{
            $(modal).find("#btn-abandon-move")
            .prop('disabled', true);
        }
        // 技名を取得
        var name = $(this).data('name');
        // ボタンに技名をセット
        $(modal).find('.btn-forget-move .move-name')
        .text(name);
        $(modal).find('.btn-forget-move')
        .show();
    });
}
 
/**
* 技テーブルクリック時の関数
* @function click
* @return void
**/
var submitForgetMoveInit = function(){
    $('.btn-forget-move').on('click', function(){
        var modal = $(this).data('modal');
        // 技をフォームへセット
        var data = {
            id: $(this).data('msg_id'),
            forget: $(modal).find('.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();
    });
}
 
/*----------------------------------------------------------
// 初期化
----------------------------------------------------------*/
jQuery(function($){
    clickMoveInit();
    selectForgetMoveInit();
    submitForgetMoveInit();
});

 

これで連続レベルアップと複数の技習得にも対応させることができました。実際に動きを見てみましょう。

 

 

1つのレベルに数個の技、レベルアップ時後にも技の習得を設定していましたが、問題なく動作してくれました。

これで技習得処理は完成です。

 

まとめ

いかがだったでしょうか。

今回のPHPポケモンではセッションを経由したオブジェクトの引き渡し方法をご紹介しました。

 

システム開発では方法を模索することがとても大切です。そして、複雑化しそうなときは1から考え直してみると、意外な突破口が見えてきます。

今回もオブジェクトをどう処理しようか、既にモーダルにセットした技を更新するにはどうすれば良いか、などを考えていましたが、考えれば考えるほど処理が複雑化しそうだったので、一度最初から考え直し、そもそも「すべての技を格納しておく必要がない」という答えに行き着きました

 

現在プログラミング学習に取り組んでいる方、ゲームづくりに興味がある方は、ぜひ参考にしてみてくださいね。

 

注目の記事

放物線アニメーション編 PHPポケモン 81
プログラミング
PHP,PHPポケモン,ポケモン
放物線アニメーション編 PHPポケモン 81

ボールアニメーション 前回までに作成した捕獲判定処理を使って、ボールのアニメーションを作成します。 捕獲演出は以下の通りです。  味方側から相手に向かってボールを投げる 相手ポケモンの前でボールを開く 捕獲判定で算出した揺れ回数分ボールを揺らす   捕まえた際は、ボールの揺れをストップ...

PHPポケモン(α)第1回目の大型アプデ
プログラミング
PHP,PHPポケモン,ポケモン
PHPポケモン(α)第1回目の大型アプデ

第1回目 大型アップデートについて 本日、PHPポケモンの大型アップデートをしました。 今回のアップデートには全体的なデザインチェンジが含まれているため、今まで遊んでくれていた人も、新鮮な気持ちで初められることができるはずです。   デザインについては、PHPを使った開発をしている人からすれば馴...

そらをとぶ&あなをほる編 PHPポケモン46
プログラミング
PHP,PHPポケモン,ポケモン
そらをとぶ&あなをほる編 PHPポケモン46

チャージ中の回避技 以前は「ロケットずつき」や「ソーラービーム」をサンプルとしてチャージ技を実装しましたが、今回は少し特別な効果をもったチャージ技を実装します。それが「そらをとぶ」と「あなをほる」です。これらは初代ポケモンでも重宝される技であり、チャージ中に相手からの攻撃を回避することができま...

ナンパしてたら独立できた「人間力の鍛え方」
フリーランス
コミュニケーション能力,ナンパ
ナンパしてたら独立できた「人間力の鍛え方」

  経験談から、人間力を鍛える方法をご紹介します。 今回は「ナンパ」がテーマです。なので、少し男性目線の内容になります。 女性の方は、「男性はこうやって考えている人もいるんだ」といった参考にしてください。   ナンパなんて、と思う人もいるでしょう。 ハラスメント規制も強くなる現代では、安易な...

両隣のポケモン取得編(配列の隣の要素)PHPポケモン 92
プログラミング
PHP,PHPポケモン,ポケモン
両隣のポケモン取得編(配列の隣の要素)PHPポケモン 92

両隣のポケモンを判別する パーティーの中からID指定でポケモンを特定するためのメソッドは実装されていましたが、その両隣にどんなポケモンが控えているのかを判別する手段がありませんでしたね。 なので、今回は両隣のポケモンを判別するためのメソッドを準備して活用していきましょう。   パーティー まずは...

バズる!ビジネスの見つけ方 〜何で起業するか悩んでいませんか?〜
マーケティング
ビジネス,起業
バズる!ビジネスの見つけ方 〜何で起業するか悩んでいませんか?〜

  独立や起業を志している9割以上の人が「何をビジネスにするか?」を悩んでいます しかし、それは負のサイクルです。 起業するためにビジネスを考えるという行為自体が”矛盾している”ということに気づかなければ、このサイクルからは抜け出すことができません。 その結果、行動できずに終わってしまう人は多...

アウトプットのための3つの習慣【3対7を成立させよう】
雑記
アウトプットのための3つの習慣【3対7を成立させよう】

  インプットとアウトプットの比率は3対7がベストだと言われています。 しかし、簡単にできるインプットに比べて、アウトプットは習慣化させておくことが大切です。それができていない人の多くが、膨大に本を読んだり学習に取り組んでも身につかず、趣味のレベルで終わってしまうのです。   今回は、そんな...

レベニューシェアとは?利益報酬・共同事業に潜む罠
フリーランス
レベニューシェアとは?利益報酬・共同事業に潜む罠

  レベニューシェアという言葉を聞いたことがありますか?   ビジネスの世界にいる人なら、意味は知らなくてもやったことがある方が意外と多いはずです。特にフリーランスの人や、プログラミングやデザインといったスキルを持っている人はレベニューシェアでは重宝されるため多い傾向があります。ここ最近...

カテゴリ

SEO対策 イベント デザイン ネットワーク ビジネスモデル フリーランス プログラミング マーケティング ライティング 動画編集 雑記

タグ

5G Adobe AfterEffects AI ajax amazon Animate api artisan atom Automator AWS Bluetooth CSS CVR description EC-CUBE4 ECショップ ESLint Facebook feedly foreach function Google Google AdSense Honeycode htaccess HTML IEEE 802.11ax Illustrator Instagram IoT JavaScript jQuery jQuery UI keyword LAN Laravel Linux MacBook MAMP meta MLM MySQL NoCode note OS OSI参照モデル Paypal Photoshop PHP phpMyAdmin PHPポケモン PremierePro rss SEO SEO対策 Sequel Pro Skype SNS SSH Symfony TCP/IP title Toastr Trait Twig Twitter UCC V系 WAN WebSub Wi-Fi wiki Windows WordPress XAMPP xml Xserver YouTube YouTuber Zoom アーティスト アウトプット アクセス層 アニメーション アフィリエイト イーブイ インターネット インプット エンジニア オブジェクト指向 お金配り クリック単価 クリック数 コミュニケーション能力 コロナ コンサルティング サムネイル システムエンジニア スタートアップ スタイルシート スパム データベース ディープフェイク デザイナー デザイン テレワーク ナンパ ニュース ネットワークモデル ノマドワーク バナー ピカチュウ ビジネス フィード フリーランス ブロガー ブログ プログラマー プログラミング プログラミング学習 プログラミング教育 プロトコル ホームページ制作 ポケモン マークアップ マーケティング メール リモートワーク レンダリング 三井住友 三宮 仕事依頼 児童デイ 児童デイサービス 児童発達支援 公開鍵 初心者 助成金 勉強法 営業 広告 広告収入 必勝マニュアル 放課後等デイサービス 朝活 楽天 深層学習 無線LAN 独立 神戸 福祉 秘密鍵 翻訳 自己啓発 英語 見積書 計算機 読書 起業 迷惑メール 配列 銀の弾丸 集客 雑学力