プログラミング

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ポケモン64
プログラミング
PHP,PHPポケモン,ポケモン
パーティー実装編 戦闘に参加するポケモン PHPポケモン64

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

忘れさせる技選択 前編(覚えるのを諦める) PHPポケモン 55
プログラミング
PHP,PHPポケモン,ポケモン
忘れさせる技選択 前編(覚えるのを諦める) PHPポケモン 55

忘れさせる技の選択 PHPポケモンでは技習得時に忘れさせる技の選択が出来ず、古いものから順番に消えていっていました。ですが、これでは主力技として使っているものが、覚えたくもない技に消されてしまうということが起こってしまいます。 実際のゲームでも、技を覚えようとした際に既に最大数の4枠が埋まっていれ...

起業・独立を考えている人に向けた具体的なアドバイス3選
雑記
起業・独立を考えている人に向けた具体的なアドバイス3選

  「どのタイミングで起業すべき?」 「会社を作るためにはどういったことをすればいいの?」   あなたはこんな悩みを抱えていませんか? 今回は、これから独立・起業をしようと考えている方や、そういった野望をいだいている人に向けて、自らの経験を元に具体的なアドバイスを3つピックアップしてご紹...

ブログで生活するための7つの道のり 〜収益化と拡散の方法教えます〜
ライティング
Facebook,Google AdSense,Twitter,ノマドワーク
ブログで生活するための7つの道のり 〜収益化と拡散の方法教えます〜

  ブログで生活したい   毎日数時間、ブログを書くだけで生きていける、そんな夢のような生活を実現させたいと思い描く人は多いですが、簡単なことでは有りません。 ですが、やらなければいけないことがわかっていれば、収益化するのは簡単です。   今回は「ブログで生活するための7つの道のり」をテ...

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

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

AIが人類にもたらす驚異!深層学習が与える影響とは
ネットワーク
AI,ディープフェイク,深層学習
AIが人類にもたらす驚異!深層学習が与える影響とは

  海外の記事でAIについて記述した興味深いものが掲載されました。   今回は「AIが人類にもたらす驚異!深層学習が与える影響とは」について、上記サイトを参考にまとめましたのでご紹介します。 ※直訳ではありません、あくまで記事の内容を参考にして自らの考えを記述したものです     AIの驚異と...

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