プログラミング

連続の技習得編 オブジェクトをセッションへ格納 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から考え直してみると、意外な突破口が見えてきます。

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

 

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

 

注目の記事

Laravel7系でTraitのmakeコマンドを作成する方法
プログラミング
artisan,Laravel,Linux,PHP,Trait
Laravel7系でTraitのmakeコマンドを作成する方法

Laravel7がリリースされて、さっそくそれを使った開発の機会がやってきましたので、使用頻度の高いものをまとめていきたいと思います。 5系や6系の情報は多く出回っていますが、6系がLTSということもあって7系の情報は少なめですね。   今回は「Laravel7系でTraitのmakeコマンドを作成する方法」をご紹介します...

SEO対策で役立つ!ライティングスキルを向上させる3ステップ
ライティング
SEO,ブログ
SEO対策で役立つ!ライティングスキルを向上させる3ステップ

  ブログやメール、SNSなどライティングスキルが問われる場面が多く、それは質に直結しています。各プラットフォームによって特徴はありますが、基礎的な文章力自体がついていれば多様な場面で役立つことは間違いありません。 文章の質が高くなれば、ブログであればSEOに強い価値のあるサイトになりますし、SNS...

ピカチュウから学ぶオブジェクト指向 〜ステータス導入編〜 6
プログラミング
PHP,PHPポケモン,オブジェクト指向,ポケモン
ピカチュウから学ぶオブジェクト指向 〜ステータス導入編〜 6

  PHPをピカチュウ(ポケモン)から学ぶ大人気コーナー、第6回目は「ステータス機能の導入編」です。   前回(第5回)で終了段階でのサンプルコードを公開しているので、もし本記事から始める人はぜひそちらを参考にしてください。    ※お詫び   調べたところによると、ポケモンの経験...

簡単に良質なブログ記事を量産する3箇条【ネタがないとは言わせない】
ライティング
ブロガー,ブログ
簡単に良質なブログ記事を量産する3箇条【ネタがないとは言わせない】

  ブログの毎日のテーマ決めが大変・・・ そもそも良質な記事をどうやって書けるようになるのかわからない   こんな悩みを抱えていませんか? 始めたばかりで伸び悩んでいる人には多いのではないでしょうか。 今回はブログで収益化や、アクセス数を伸ばそうと考えている人へ向けて「簡単に良質なブログ...

フリーランスになるなら知っておきたい!無料サービス3選
フリーランス
フリーランスになるなら知っておきたい!無料サービス3選

  「フリーランスになる前に何を準備すればいいの?」 「フリーランス向けの便利なサービスが知りたい」   今回はそんな悩みを抱えた人へ向けて「フリーランスになるなら知っておきたい!無料サービス3選」をご紹介します。 この記事を読んで、フリーランスにとっての悩みである以下の3つの問題を一緒...

定数と静的変数 ピカチュウとイーブイで学ぶオブジェクト指向
プログラミング
PHP,PHPポケモン,イーブイ,オブジェクト指向,ピカチュウ,ポケモン
定数と静的変数 ピカチュウとイーブイで学ぶオブジェクト指向

PHPポケモンも順調に開発が進んでいると思いきや、ふとした気づきが自分の理解力を思い知らせることとなった今日このごろです。 プログラミングは奥が深く、しっかりと段階を追って理解を進めていけば、「これ・・・便利やんけ!」ってなることがかなり多いということがわかります。   それでは、かの有名な黄色い...

フリーランスが打ち合わせで押さえておきたい3つのポイント【収入アップします】
フリーランス
フリーランス,営業
フリーランスが打ち合わせで押さえておきたい3つのポイント【収入アップします】

  フリーランスになれば、デザイナーやプログラマーといった制作をメインとする仕事の人でもクライアントと打ち合わせをしなければいけません。 特に独立や起業前のキャリアとして営業職に縁がなかった人からすれば、打ち合わせが苦手な人は多いはずです。   今回は、そういった営業ベタでやり方が分からない...

独立するならWordPress理解しておけばOK!プログラミングでフリーランスはこれ一つで成り立ちます
プログラミング
PHP,WordPress,フリーランス,独立
独立するならWordPress理解しておけばOK!プログラミングでフリーランスはこれ一つで成り立ちます

  プログラミングでフリーランスを目指すには、どの言語始めればいいの?   プログラミングの学習を始めたのに、それをどう活かせばよいか分からず、いざフリーランスで活動しようと思ってもイメージできずに断念してしまう人は多いです。 言語にも向き不向きがあるため、フリーランスとして活動するために向...

カテゴリ

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

タグ

5G Adobe AfterEffects AI ajax amazon Animate api artisan atom Automator AWS Bluetooth CSS CVR description EC-CUBE4 ECショップ ESLint Facebook feedly foreach fortify function Google Google AdSense Honeycode htaccess HTML IEEE 802.11ax Illustrator Instagram IoT JavaScript jetstream 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 独立 神戸 福祉 秘密鍵 翻訳 自己啓発 英語 見積書 計算機 認証 読書 起業 迷惑メール 配列 銀の弾丸 集客 雑学力