プログラミング

HPバーアニメーション 後編 フロント側の対応 PHPポケモン 44

PHP PHPポケモン ポケモン
HPバーアニメーション 後編 フロント側の対応 PHPポケモン 44

HPバーアニメーション

それでは前回に続き、HPバーのアニメーションづくりをしていきましょう。前回、メッセージに合わせてレスポンスを返却するというサーバー側の仕組みを作成しました。なので、今回はそれをフロント側で受け取り、タイミングよくアニメーションで再現します。

 

フロント側(js)の処理

前回も説明したとおり、画面移管したタイミングには「処理が終了している状態」です。なので、JavaScript(jQuery)を使った処理はあくまで見た目だけの演出をするだけです。

 

最初に変更する点は、HPバーの値です。こちらは現在、計算後の結果が出力されているため、前回作成した計算前のHPgetBeforeRemainingHp)をセットし直しましょう。

 

相手のHP
<?php # 敵ポケモン詳細 ?>
<div class="col-6">
    <p><?=$enemy->getName()?> Lv:<?=$enemy->getLevel()?> <?=$enemy->getSaName(false)?></p>
    <div class="form-group">
        <div class="progress">
            <div id="hpbar-enemy"
            class="progress-bar bg-success"
            role="progressbar"
            style="width:<?=$controller->getBeforeRemainingHp($enemy, 'per')?>%;"
            aria-valuenow="<?=$controller->getBeforeRemainingHp($enemy)?>"
            aria-valuemin="0"
            aria-valuemax="<?=$enemy->getStats('HP')?>"></div>
        </div>
    </div>
</div>

 

味方のHP
<?php # 自ポケモン詳細 ?>
<div class="col-6 text-center">
    <img src="/Assets/img/pokemon/dots/back/<?=get_class($pokemon)?>.gif" alt="<?=$pokemon->getName()?>">
</div>
<div class="col-6">
    <p><?=$pokemon->getNickName()?> Lv:<?=$pokemon->getLevel()?> <?=$pokemon->getSaName(false)?></p>
    <div class="form-group">
        <div class="progress">
            <?php if($controller->getBeforeRemainingHp($pokemon, 'per') <= 50) $hp_bar_class = 'bg-warning'; ?>
            <?php if($controller->getBeforeRemainingHp($pokemon, 'per') <= 20) $hp_bar_class = 'bg-danger'; ?>
            <div id="hpbar-friend"
            class="progress-bar <?=$hp_bar_class ?? 'bg-success'?>"
            role="progressbar"
            style="width:<?=$controller->getBeforeRemainingHp($pokemon, 'per')?>%;"
            aria-valuenow="<?=$controller->getBeforeRemainingHp($pokemon)?>"
            aria-valuemin="0"
            aria-valuemax="<?=$pokemon->getStats('HP')?>"></div>
        </div>
        <p class="text-right px-3">
            <span id="remaining-hp-count-friend"><?=$controller->getBeforeRemainingHp($pokemon)?></span>
            / <?=$pokemon->getStats('HP')?>
        </p>
        <?php # 経験値バー ?>
        <div class="progress" style="height:4px;">
            <div class="progress-bar bg-primary" role="progressbar" style="width:<?=$pokemon->getPerCompNexExp()?>%;" aria-valuenow="<?=$pokemon->getPerCompNexExp()?>" aria-valuemin="0" aria-valuemax="100"></div>
        </div>
    </div>
</div>

 

このHPバーが今回の操作対象となるため、それぞれに判別値としてidhpbar-enemyhpbar-friendを割り当てています。

 

パラメーターのセット

次に、パラメーターのセットです。これはメッセージに合わせてIDを振ったレスポンスが該当します。メッセージが進んだらアニメーションを実行させるので、このレスポンスデータは対象のメッセージに対して割当てしておきましょう。

 

メッセージボックス
<div class="message-box border p-3 mb-3">
    <?php # メッセージエリア ?>
    <?php foreach($controller->getMessages() as $key => list($msg, $status, $auto)): ?>
        <?php $class = $key === $controller->getMessageFirstKey() ? 'active' : ''; ?>
        <?php $last_class = $key === $controller->getMessageLastKey() ? 'last-message' : ''; ?>
        <p class="result-message <?=$class?> <?=$last_class?> <?=$status ?? ''?>"
            data-action="<?=$responses[$status]['action'] ?? ''?>"
            data-target="<?=$responses[$status]['target'] ?? ''?>"
            data-param="<?=$responses[$status]['param'] ?? ''?>"
            data-auto="<?=$auto ?? ''?>">
            <?=$msg?>
        </p>
    <?php endforeach; ?>
    <span class="message-scroll-icon">▼</span>
</div>

 

返却したパラメーターをそれぞれdata要素としてセットしました。これで、どのメッセージでどのアクションをすれば良いのか、js側でも判別することができます。

 

アニメーションの実装

それでは本格的なアニメーションを再現するために、jQueryの処理に入りましょう。今までjQueryslim版を使用していましたが、今回animateなどのメソッドを使用したいのでフル版を読み込むようにしておいてください。また、イージングなどの指定で必要になる可能性があるため、jQueryUIも追加で読み込んでいます。

 

今回カスタマイズするのはmessage.jsです。

 

メッセージ用JSPublic/Assets/js/Battle/message.js
/**
* メッセージボックスクリック時の関数
* @function click
* @return void
**/
var clickMsgBoxInit = function(){
    var click = true;
    $('.message-box').click(async function(){
        if(click === false) return;
        // メッセージボックスを処理終了まで無効化
        click = false;
        // 現在のメッセージ
        var now = $('.result-message.active');
        await actionMsgBox(now);
        // メッセージボックスを有効可
        click = true;
    });
}

 

まずはメッセージクリック時の処理からです。メッセージボックスを連打されてしまうとアニメーションが複数回実行されてしまうため、まずclick変数にtrueをセットしておきます。クリックされたらすぐにその値をチェックして、falseであれば処理をストップtrueであればfalseをセットしてから処理に移ります。

処理が終了すれば、trueを再度セットしてメッセージボックスのクリック処理を有効可します。こうして置けば、処理中に追加で処理の入力が入らないためおかしな挙動を制御することができます。

 

非同期処理とは

ここで非同期処理について簡単に触れておきます。アニメーションなど時間がかかる処理の殆どが「非同期処理」です。これはサーバー側(PHP)では馴染みがないので、慣れていない人は悩まされるポイントです。

 

今回のケースであれば、HPを減少させる際にanimateというメソッドを使用しています。これを5秒掛けて実行させた場合、その5秒間の間にjsは次の処理をどんどんと進めて行ってしまい、animateの処理は置いてけぼりとなってしまいます。そうなれば、処理終了判定がされた時点ではまだ処理が終わっていない、といったことにもなりかねません。それを「処理が終了してから次の処理へ移行する」とするために、関数手前にasyncを付与して、非同期処理が含まれている関数(actionMsgBox)の前にawaitを付与しました。

 

では、そのactionMsgBox関数の中身を見てみましょう。

/**
* メッセージアクション
* @param now element
* @return Promise
**/
var actionMsgBox = function(now){
    return new Promise( async (resolve, reject) => {
        // 最終メッセージかどうか確認
        if((now.length === 0) || now.hasClass('last-message')){
            await doLastMsg();
        }else{
            // メッセージにアクションがセットされていれば実行
            switch (now.data('action')){
                // ==============================================
                // HPバーの処理 =================================
                //
                case 'hpbar':
                await doAnimateHpBar(
                    now.data('action'),
                    now.data('target'),
                    now.data('param')
                );
                // ==============================================
            }
            // 次のメッセージへ
            await nextMsg(now);
        }
        resolve();
    });
}

 

ただawaitが付いていても、すべての処理が終わったかどうかを勝手に判断してくれるわけではありません。「完了しました」ということを伝えるためにはPromiseというクラスを使用します。

クラス内で処理が終われば、resolveというメソッドを呼び出すことで、そのPromiseクラスのステータスが完了状態へと移行してくれ、awaitに対して処理が終わったことを伝えてくれます。もしresolveが呼び出されていなければ、一生そこで処理が止まってしまうことになります。

 

更にこの処理の中でもHPバーの変動処理などは行わないため、更にPromiseクラスで実行するコールバック関数にもasyncを付与して、內部で呼び出す関数に対してawaitを付与していきます。

 

このように、アニメーションによる処理を行う際には連鎖的につなげていくことになりますjQueryは導入が簡単に出来ますが、フロント側で多くの処理をさせるためにはこれを連連と書いていかなければならなく、規模が大きくなればなるほど視認性が悪くなり保守性も落ちてしまいます。なので、本格的なアプリケーション開発ではvueなどのフレームワークの導入を検討した方が良いでしょう。PHPポケモンではjQueryで押し切る予定です。

 

残りの処理も一気に見ていきましょう。

/**
* HPバーのアニメーションを実行
* @param string action
* @param string target
* @param mixed param
* @param now element
* @return Promise
**/
var doAnimateHpBar = function(action, target, param){
    return new Promise((resolve, reject) => {
        // 対象のHPバーを取得
        var hpbar = $('#hpbar-' + target);
        var hp = hpbar.attr('aria-valuenow') - param;
        // 最小値の処理
        if(hp < 0){
            hp = 0;
        }
        // 最大値の処理
        if(hp > hpbar.attr('aria-valuemax')){
            hp = hpbar.attr('aria-valuemax');
        }
        // 処理後のHPバーの長さを算出
        var width = hp / hpbar.attr('aria-valuemax') * 100;
        // 非同期1
        var promise1 = new Promise((resolve, reject) => {
            // 長さを変更
            hpbar.animate({
                width: width + "%"
            }, {
                duration: 500,
                easing: 'easeOutQuad',
                complete: function(){
                    // 処理完了(css変更のズレがあるため0.5秒後にresolveを返却)
                    setTimeout(function() {
                        resolve();
                    }, 500);
                }
            });
        });
        // 非同期2
        var promise2 = new Promise( async (resolve, reject) => {
            // HPカウンターのアニメーション
            if(target === 'friend'){
                // 引数は数値でセット
                await countHp(
                    parseInt(hpbar.attr('aria-valuenow'), 10),
                    parseInt(hp, 10)
                );
            }
            return resolve();
        });
        Promise.all([promise1, promise2]).then(() => {
            // 残HPの値を変更
            hpbar.attr('aria-valuenow', hp);
            return resolve();
        });
        // ==============================================
    });
}
 
/**
* HPの数値カウント処理
* @param integer start
* @param integer end
* @return Promise
**/
var countHp = function(start, end){
    return new Promise((resolve, reject) => {
        // もし開始と終了が同じであれば処理不要
        var diff = start - end;
        if(diff === 0){
            return resolve();
        }
        // 数値の変動処理関数
        var counter = function(){
            // 実行回数が+かーかで数値の変動方向を判定
            if((start - end) > 0){
                // 減算(ダメージ)
                start--;
            }else{
                // 加算(回復)
                start++;
            }
            $('#remaining-hp-count-friend').text(start);
        }
        // 繰り返し関数
        var time = parseInt(1000 / diff, 10);
        var interval_id = setInterval(function(){
            counter();
            if(start === end){
                clearInterval(interval_id);
                return resolve();
            }
        }, time);
    });
}

 

パラメーターにセットされた値を取得しながら計算を行い、animate内でcsswidthを変更するといった至って単純な処理です。味方のHPバーを操作する際は、数値をカウントする必要があるのでsetIntervalを使って1ずつ回転させています。

 

この辺りに関しては、深く触れていきません。理由としては「あくまで簡易的なアニメーションであり、そこまで正確にゲームを再現できていない」からです。今回はサーバー側の処理を優先、連動させるために行なっているため、サーバー側から返却した値が正確に受け取れていればOKとしています。

 

オートメッセージ

オートメッセージについても触れておきましょう。今回メッセージの処理を関数にまとめたのは、このオートメッセージが理由の1つです。

/**
* メッセージボックスクリック時の関数
* @function click
* @return void
**/
var clickMsgBoxInit = function(){
    var click = true;
    // 変数をリセット
    auto_msg = false;
    $('.message-box').click(async function(){
        if(click === false) return;
        // メッセージボックスを処理終了まで無効化
        click = false;
        // 現在のメッセージ
        var now = $('.result-message.active');
        await actionMsgBox(now);
        // 次がオートメッセージの場合は再度実行
        while(auto_msg){
            now = now.next();
            await actionMsgBox(now);
        }
        // メッセージボックスを有効可
        click = true;
    });
}

 

whileによるループ処理を追加しました。もし現在選択されているメッセージにautoのパラメーターが付与されていれば、次に進んで再度actionMsgBoxを実行させています。こうすることで、やどりぎのタネなどの処理はボタンを押さずとも順番に自動進行してくれます。

/**
* 次のメッセージへ移行する処理
* @param now element
* @return Promise
**/
var nextMsg = function(now){
    return new Promise( async (resolve, reject) => {
        // 現在のメッセージのactiveを解除
        now.removeClass('active');
        // 次のメッセージにactiveを付与
        var next = now.next();
        next.addClass('active');
        /**
        * メッセージのステータスに合わせた分岐
        **/
        // バトル終了
        if(next.hasClass('battle-end')){
            $('#remote-form-action').val('end');
            $('#remote-form').submit();
            return;
        }
        // 最終メッセージかどうかの判別
        if(next.hasClass('last-message')){
            // 最終メッセージ
            doLastMsg();
        }else{
            // 最終メッセージではない
            doNotLastMsg();
        }
        // 次のメッセージがオートメッセージかどうかの判定
        if(next.data('auto')){
            auto_msg = true;
        }else{
            auto_msg = false;
        }
        // 処理終了
        resolve();
    });
}

 

nextMsgの関数でもPromiseを使って非同期処理として結果を返せるようにしました。

実際に動きを確認してみましょう。

 

 

大分それっぽくなってきましたね。ゲームのようになめらかな動きにはまだまだほど遠いですが、簡易的なものとしては十分です。 

 

残る問題点

ある程度遊べるレベルになってきたので、ここで現在残る問題点を挙げておきます。ちなみに、セーブ機能やアイテム、ポケモン交代など機能的に実装していないものは今回除きます

 

HPバーの色

こちらは長さに合わせてクラスを変更するだけで実装できますが、現在はまだ未実装のため残る問題としておきます。色を変えるタイミングなど、本来は50%以下になった時点で黄色、20%以下になった時点で赤と変化させなければならないのですが、クラスを変更したタイミングで変わってしまうためどうしようかと悩み中です。

HPゲージの減り時間なども、もう少し本物に近づけたいなとは考えているので、もし良い案があって暇な人はお問い合わせで教えてください。

ちなみに、ライブラリで良いものはどんどん導入していきますが、フレームワークを導入するようなことはないと思います

 

レベルアップ

こちらは経験値バーの処理も合わせてなのですが、レベルアップ処理をどうしようかと悩み中です。初代では一気に次のレベルへ押上げしていましたが、レベルは1ずつあがり経験値バーが変動してレベルアップしていく仕組みを実装したいと考えています。ただレベルアップ処理だけを実装するのであれば、HPバーの応用で再現ができそうなのですが、これに合わせて残る問題が次の2つです。

 

技の習得

レベルアップ処理に関連して起こる問題の1つが技の習得です。レベルアップに合わせて技を習得させてあげなければならないのですが、その仲介処理を挟むにはどうしたものかと悩み中です。また、現在の仕組み上は技は順番に上から上書きされるようにしていますが、通常であれば選択できなければならないため、画面移管を途中で挟む必要があります

ajaxによる書き換えをするという方法もありますが、正直こちらは最終手段としてしか考えていません。なぜならDBを使っていない関係上、ajaxapi接続するにはいろんなパラメーターを投げて変更しなければいけないのでは?と薄っすら思っています。悩ましい。

 

進化

レベルアップに関係する処理の1つとして、進化があります。現在はサーバー側で一括変更しているため、進化すれば画面移管後には進化後の姿に変化しています。こちらはバトル終了のタイミングに移すだけで良いのかも知れませんが、進化キャンセルなども含めていろんな問題やクリアしなければならない問題があり、良い案が浮かぶまでは後回しとしています。

 

ちなみに、レベルアップや進化をすると最大HPが変更になるため、そのタイミングだけ一時的に表示されている最大HPと残りHPに乖離が発生します。

  

まとめ

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

今回のPHPポケモンは「HPバーアニメーション」についてフロント側の対応方法をご紹介しました。

 

サーバー側と異なり、ほぼノー説明でのコード紹介となりましたが、その点についてはご容赦ください。なぜならjsは必要に応じて触る程度なのでそこまで詳しく説明しろと言われても難しいというのが本音です。記述しているものはもちろん分かって書いてはいますが、他に比較対象となる知識量も少なく、良し悪しや裏側では実際にどう動いているのかまで突き詰められるとそこまで詳しくは紹介できないのが理由の1つです。

 また、前述しましたがあくまでフロント側の動きはサーバー側のパラメーターを受け取って流通りにアクションを実行していくことができるかどうかを再現しただけに過ぎません。なので、ある程度方向性が固まればその際には説明できるようにしておきます。

 

最後にまとめた残る問題点についてですが、フロント側との処理と連動していくとなり色々と浮き彫りになってきました。開発者目線からしたら大きな進歩ではあるのですが、プレイヤー側からすれば「おかしくない?」となってしまう部分が多いでしょう。

実際のゲーム開発ではどの言語をどのように使っているか詳しく知りませんが、WEBプログラミングではこのように複数言語とマークアップを組み合わせながら作成しています。環境準備のしやすさや、すぐに形として見られるという点に置いては入りやすいかも知れませんが、プログラミング言語としての不備などは業界からすれば気になる人も多いはずです。

ただ、プログラミングというものに興味を持つということに関しては、良いきっかけになり学習コストやハードルも適切なものではないかと考えています。

 

いつもよりも少し長めのまとめとなりましたが、ゲーム開発やアプリケーション開発、サイト制作などプログラミングに興味がある人は、ぜひ参考にしてくださいね。

 

注目の記事

PHPポケモン「わざ編〜わるあがき〜」35
プログラミング
PHP,PHPポケモン,ポケモン
PHPポケモン「わざ編〜わるあがき〜」35

わるあがき そろそろ技のPPを実装段階にきたので、その前に「わるあがき」という技を作成しましょう。「わるあがきって何?」という人のために、どういった技なのか簡単に説明します。 全ポケモン使用可能。ただし、レベルアップなどで普通のわざとして覚えることはできず、自分の技がすべて選択不能になった場合...

本当に価値のある宣伝方法 〜多くの人が実践している【間違った努力】とは〜
マーケティング
Facebook,note,Twitter,YouTube,集客
本当に価値のある宣伝方法 〜多くの人が実践している【間違った努力】とは〜

  YouTubeやTwitter、Facebookやnoteなど、いろんなサービスを使った集客方法があります。集客という視点ではなく、それ自体に人を集めたいという人も多いでしょう。   フォロワーやチャンネル登録者数を増やすために、多くの人が様々な取り組みをしていますが、その中でもひときわ意味のないことをただ繰り返して...

PHPポケモン「野生ポケモン遭遇編」18
プログラミング
PHP,PHPポケモン,ポケモン
PHPポケモン「野生ポケモン遭遇編」18

  PHPポケモンが第18回にしていよいよバトルの第一歩、野生ポケモンとの遭遇編に突入です。 新しいコントローラーの作成と、バトル画面の作成、そしてポケモンデータの受け渡しなどを中心にご紹介します。   バトル画面の実装  ポケモンのゲームでも、野生ポケモンが現れるとバトル画面へ移管し...

パーティー実装編 戦闘に参加するポケモン PHPポケモン64
プログラミング
PHP,PHPポケモン,ポケモン
パーティー実装編 戦闘に参加するポケモン PHPポケモン64

先頭のポケモンを選出 前回パーティーのプロパティを準備して、複数(6匹)のポケモンを持ち歩けるようにすることを想定しました。 今回は、そこからバトル画面への連動をさせる部分までを作り込んでいきましょう。   複数のポケモンを所有している場合、戦闘が始まって繰り出されるのは「ひんし状態を除く一番上...

仕事用PCは2年で買い替えろ!その理由とは?
フリーランス
MacBook
仕事用PCは2年で買い替えろ!その理由とは?

  「あなたはパソコンをどのタイミングで買い替えますか?」   私は先日、2015年モデルのMacbookPro15インチから最新モデルのMacbookPro13インチに買い替えました。その理由は、メモリが32GBになったことや、15インチが持ち運びという点で見れば向いていないと判断したということ、そこそこの年数が経...

状態異常演出編 PHPポケモン 62
プログラミング
PHP,PHPポケモン,ポケモン
状態異常演出編 PHPポケモン 62

状態異常の演出 今回は「状態異常」にかかった際の動的な変更、演出を実装していきます。 どく状態になれば、メッセージに合わせて「どく」を表記、まひ状態になれば、メッセージに合わせて「まひ」をセットするという単純なものです。   返り値の変更 それではまず、状態異常をセットしてメッセージを生成して...

フォームはもう古い?サイトのCVRを高くするチャットシステムとは
マーケティング
CVR,ECショップ
フォームはもう古い?サイトのCVRを高くするチャットシステムとは

  サイトのコンバージョン率を上げたい お問い合わせフォームから連絡が来ない   ちょっとWebサイトやネットショップを立ち上げても中々思うような結果につながらない時代になりました。数多くのサイトが出回っていることはもちろん、ユーザーの閲覧頻度が増えて目が肥えているというのも大きな理由の1...

ブログ運営者のための「よくわかるSEO対策」フィード編
SEO対策
atom,feedly,rss,WebSub,WordPress,xml,フィード
ブログ運営者のための「よくわかるSEO対策」フィード編

  アメブロやFC2など優れたブログサービスが存在している中、好きなデザイン、こだわった機能を求めてWordPressを選択される方も多いですね。 また、コーポレートサイトにWordPressを用いる方もいるでしょう。ただしSEO対策がしっかりとされていなければ、提供されているサービスに太刀打ちできません。  もちろん...

カテゴリ

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