プログラミング

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プログラミングではこのように複数言語とマークアップを組み合わせながら作成しています。環境準備のしやすさや、すぐに形として見られるという点に置いては入りやすいかも知れませんが、プログラミング言語としての不備などは業界からすれば気になる人も多いはずです。

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

 

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

 

注目の記事

熟練者ほど実践するプログラミングが上達する3つの法則
プログラミング
PHP
熟練者ほど実践するプログラミングが上達する3つの法則

  「なかなかプログラミングが上達しない・・・」 「やったことはあるけど覚えられない」   プログラミングを習得しても、勉強と一緒で使っていなければ忘れてしまいます。また、どんどん上達する人や、長い間プログラミングの技術で生計を立てているような熟練者は、日頃からの取り組み方自体が違ってい...

PHPポケモン「バトルシステム編 〜バトル終了判定〜」28
プログラミング
JavaScript,jQuery,PHP,PHPポケモン,ポケモン
PHPポケモン「バトルシステム編 〜バトル終了判定〜」28

バトル終了判定 今回はバトル終了判定を実装しましょう。今までは「にげる」による戦闘離脱のみで、ひんし状態でも殴り合うことが出来たので、それを解消するためにも戦闘結果による判定を導入します。   ひんし状態の監視 まずは「ひんし」の監視です。現在は交代ポケモンどちらか一方がひんし状態になれば、そ...

PHPポケモン「オートローダー編」16
プログラミング
PHP,PHPポケモン,ポケモン
PHPポケモン「オートローダー編」16

  今までは技やタイプを一括requireという荒業で対応していましたが、フシギダネ系列の技を実装した際に「こんなん全部読み込んでられるか!」と流石になったので、簡易ながらオートローダーを実装していきます。 そして、実装したらしたで色々と問題も浮かび上がってきたので、このあたりは回を進めて行きなが...

【完全未経験OK】プログラミングの始め方 〜学習方法を解説します!〜
プログラミング
CSS,HTML,JavaScript,MAMP,PHP,XAMPP,フリーランス,プログラミング,独立,起業
【完全未経験OK】プログラミングの始め方 〜学習方法を解説します!〜

  プログラミングって難しい   そういう人は多いですね。しかしそんなことは有りません。 言語という点で比較すれば、英語やフランス語など第二言語を学ぶ方が難しいと言えます。   プログラミングの需要は年々高まり、今や最高潮とも言える域まで来ています。 なぜそこまで需要が高いのか?   ...

捕獲処理実装編 PHPポケモン 80
プログラミング
PHP,PHPポケモン,ポケモン
捕獲処理実装編 PHPポケモン 80

捕獲処理の作成 前回モンスターボールのクラスを作成したので、今回は捕獲判定までの一連の処理を仕上げていきます。サービス自体は他のアイテムと一緒にするためItemServiceを呼び出し、その中で使用されたアイテムを判断して分岐を作ります。   バトル中のアイテムサービス(/App/Services/Battle/ItemService.ph...

ゆびをふる編 PHPポケモン 69
プログラミング
PHP,PHPポケモン,ポケモン
ゆびをふる編 PHPポケモン 69

ゆびをふるとは 今回PHPポケモンで実装する技は「ゆびをふる」です。  ゆびをふる(ポケモンwiki) https://wiki.ポケモン.com/wiki/ゆびをふる   「ゆびをふる大会」というゆびをふるのみを使った大会なども開催されているということもあり、ポケモンの技の中でも初代から長く愛されてきた1つです。で...

動画編集に役立つ基本的な考え方【Adobe AfterEffects】
動画編集
Adobe,AfterEffects,PremierePro,YouTube
動画編集に役立つ基本的な考え方【Adobe AfterEffects】

  YouTubeの人気に合わせて、動画編集の需要も高まってきましたが、その大変さから挫折してしまう人も続出しています。 動画編集は奥が深く、技術的な部分に関してはプロのクリエイターであっても自分がよく使うような一部しか把握していないのが普通であり、調べても該当する情報が出てきにくいということもあ...

【Laravel】1対1リレーションをわかりやすく解説(hasOne)
プログラミング
Laravel,PHP
【Laravel】1対1リレーションをわかりやすく解説(hasOne)

  Laravelの1対1リレーションのhasOneについて、公式マニュアルでは専用単語ばかりでどうしてもわかりにくいと感じてしまっている方へ向けて、わかりやすく解説しました。 ※例で紹介しているコードについては、一部英語を日本語表記で使用している部分もありますので、コピペで使用する方は必要に応じて置...

カテゴリ

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