プログラミング

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ステップ 〜新時代を生き抜くために〜
フリーランス
ビジネス,独立,起業
個人の時代で成功するための起業への3ステップ 〜新時代を生き抜くために〜

  起業ってどのタイミングですればいいかわからない・・・   令和時代での起業の考え方は、昭和や平成とは大きく変わっています。個人の時代と呼ばれる現代では、今までのようにリスクを背負って起業することは失敗の確率はより高く、オススメできません。   今回は、起業を志している人たちへ向けて私...

XserverにSequel ProでSSH接続する方法を4ステップでわかりやすく解説
ネットワーク
phpMyAdmin,Sequel Pro,SSH,Xserver,データベース,公開鍵,秘密鍵
XserverにSequel ProでSSH接続する方法を4ステップでわかりやすく解説

  AWSなどが注目される中、Xserver(エックスサーバー)は操作やセットアップが簡単で、ポートフォリオや業務、個人ブログ(WordPess)で使用している方に需要が高いサービスです。 そしてMacのPCを使っている方はデータベースの管理ツールとして、無料で使えるSequel Proが人気があり、多くのユーザーが使用し...

甘い誘惑に気をつけよう「わからないことへの対処法」
雑記
甘い誘惑に気をつけよう「わからないことへの対処法」

  最近は開発記事が多めだったので、今回は気分転換も込めてみんな大好きコラムのコーナーです。開発疲れという理由ももちろんありますが、久々に考えを書き綴りたくなったというのが本音です。   今回のテーマは「甘い誘惑に気をつけよう」です。高校生ぐらいまではあまり縁がなかったことでも、大学生・社会...

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

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

TwitterでYouTubeのリンク付きサムネイルを表示させる方法【超実践的Webプログラミング活用法】
プログラミング
HTML,JavaScript,PHP,Twitter,YouTube
TwitterでYouTubeのリンク付きサムネイルを表示させる方法【超実践的Webプログラミング活用法】

  Twitter(ツイッター)をブログや商品、イベントの宣伝目的で使用している人は多いです。そして、そのためのマーケティング方法や戦略は数多く練られています。 今回は、その中でもYouTubeの告知をするために特化させた内容をまとめました。   一般的な方法と、プログラミングの知識(HTMLやJavascript等)があ...

フレンドリィショップ編 アイテムの販売 PHPポケモン 76
プログラミング
PHP,PHPポケモン,ポケモン
フレンドリィショップ編 アイテムの販売 PHPポケモン 76

リュックの作成 前回はフレンドリィショップへ商品を並べ、計算機を作成するところまで作成しました。ですが、商品が購入できたとしても、それを保管しておくためのスペースがなければ意味がありません。 なので、プレイヤー情報に対してアイテムを格納できるように機能拡張をしましょう。   プレイヤークラス(/C...

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

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

賢い集客でボロ儲け!?仕事や案件に困らない基本戦略・3選
マーケティング
賢い集客でボロ儲け!?仕事や案件に困らない基本戦略・3選

  「仕事はどうすればもらえるの?」 「集客って何すればいいかわからない」 「告知ってどんな媒体をどう使えばいいの?」   フリーランスで仕事の取り方がわからず苦戦している人や、サービスや商品は作ったものの集客の方法がわからず悩んでいる人のほとんどが、集客のテクニックばかりに目線がいってしま...

カテゴリ

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