プログラミング

連続の技習得編 オブジェクトをセッションへ格納 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ポケモン 59
プログラミング
PHP,PHPポケモン,ポケモン
進化アニメーション 前編 PHPポケモン 59

進化アニメーションの実装 今回は後回しにしていた進化アニメーションの作り込みをしていきます。今までもレベルに達すれば進化はしていましたが、その演出はありませんでした。また、ポケモンではBボタンを押すことで進化のキャンセルをすることができます。なので、この辺りも実際のゲームを再現していきましょう。...

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

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

ノーコード開発で将来プログラミングは不要?我々は何を学ぶべきなのか
プログラミング
amazon,AWS,Honeycode,NoCode,プログラミング学習
ノーコード開発で将来プログラミングは不要?我々は何を学ぶべきなのか

  近年、プログラミングの需要は増え勢いを増していますが、それ以上にノーコードというソースコードを打たなくでもアプリ開発ができるというソフトウェアやプラットフォームが普及してきました。 これにより一部の期待感とは別に、プログラマーという職業は今後不要になるのではないかと危機感を抱く人が出て...

非公開ディレクトリ画像表示編 PHPポケモン 91
プログラミング
PHP,PHPポケモン,ポケモン
非公開ディレクトリ画像表示編 PHPポケモン 91

非公開ディレクトリの画像を表示する 今回は、β版に向けての取り組みの1つとして、表示させる画像のアクセス先を非公開ディレクトリに変更します。 現在は公開ディレクトリ(Public)内のAssetsフォルダ内に配置していますが、これをルート直下においているStorageに移動させるのが目的となります。   gifのbas...

なぜ(・・? あなたはMacを使うのか?
雑記
AWS,Linux,MacBook,OS,Windows
なぜ(・・? あなたはMacを使うのか?

  あなたが使っているPCはなんですか?   たまに聞かれることがあります。 そこで「Macを使っています」と答えると「何故?」という次の問いが返ってくることがあります。   あなたがもしMacを使っているのであれば、このときになんと答えますか?   今回のテーマは、「何故Macを使っているの?」と...

レポート(試験)編 PHPポケモン 94
プログラミング
PHP,PHPポケモン,ポケモン
レポート(試験)編 PHPポケモン 94

今回の内容は、あくまで試験的なものとなります。実際にそのままの仕組みで導入するかは未定です。 ※セーブ機能の実装自体は予定しております   また、今回はセーブするための仕組みの部分にのみにフォーカスを当てています。非公開ディレクトリやパーミッション等による最低限の対応は施していますが、試験的にア...

大半のネットワークビジネスが成立しない理由とは【権利収入の落とし穴】
マーケティング
MLM
大半のネットワークビジネスが成立しない理由とは【権利収入の落とし穴】

  誘われたけど、欠点が上手く説明できない 権利収入って本当にもらえるの?   ネットワークビジネスと聞けば、良い印象を抱かない人がほとんどでしょう。ですが、「何故?」と問われて説明できる人は意外にも少数派です。 そして、SNSを積極的に活用している人ならば、一度は誘いを受けたことがあるの...

PHPポケモン「行動順判定+敵ポケモン攻撃編」25
プログラミング
PHP,PHPポケモン,ポケモン
PHPポケモン「行動順判定+敵ポケモン攻撃編」25

行動順の判定 ポケモンの行動順は以下の通りです。 技の優先度 すばやさの実数値(補正有り) 同速の場合は50%の乱数   この順番で比較を行い、先行後攻を決めます。ただし、これは両者ともに攻撃を選択した場合のみです。アイテムの使用や交代は技よりも優先されますし(※一部技を除く)、にげる...

カテゴリ

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 独立 神戸 福祉 秘密鍵 翻訳 自己啓発 英語 見積書 計算機 読書 起業 迷惑メール 配列 銀の弾丸 集客 雑学力