プログラミング

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

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

 

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

 

注目の記事

本は読まなくていいの!?物事の本質を理解する
雑記
読書
本は読まなくていいの!?物事の本質を理解する

  成功したけりゃ、1日1冊本を読め   社会人になると、本を読めと言われることは多いのではないでしょうか。 特にアクティブな活動をしていると、また独立や起業などを夢見ている人は、そういった言葉を聞くことは多いはずです。   しかし一方で、「本は読まなくても良い」という成功者たちもいます。 ...

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

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

わざマシン編 忘れさせる技の選択 PHPポケモン106
プログラミング
PHP,PHPポケモン,ポケモン
わざマシン編 忘れさせる技の選択 PHPポケモン106

忘れさせる技の選択 わざマシンによる技習得処理を作成しましたが、既に覚えている技が4つあると、モーダルが表示されて選択をしても習得することができません。これは、技習得用のサービスがホーム画面には用意されていないからです。 なので今回は、わざマシンを使った際の技の入れ替え処理を実装していきましょう...

独立してから心がけていること【社会人時代と比較して】
フリーランス
フリーランス,独立
独立してから心がけていること【社会人時代と比較して】

  今回は自分が独立してから心がけていることの中から、ある程度上手くいっていることや、オススメできるようなことを簡単に感想付きでまとめてみました。 社会人時代と比較しながらの内容なので、これからフリーランスで生きていこうと思っている人や、社会人という生活にストレスを感じている人は是非参考に...

システムエンジニア向けまとめ情報サイト IT News Checker
ネットワーク
システムエンジニア向けまとめ情報サイト IT News Checker

新年のスタートダッシュが遅れ気味に見えるかも知れませんが、年末からじっくりと作業を進めており、やっとある程度形になりました。 師走の如く作業に走り、気分転換に雪遊びもしつつ、成人式を終えた辺りでリリースしたのが「IT News Checker」です。   IT News Checkerとは 簡潔に説明すると、まと...

PHPポケモン「バトルシステム編〜状態異常1〜」30
プログラミング
PHP,PHPポケモン,ポケモン
PHPポケモン「バトルシステム編〜状態異常1〜」30

状態異常チェック 今回は少し先延ばししていた状態異常判定を一部作成していきましょう。 状態異常では「行動前」と「行動後」に判定するものに分けることができます。 行動前 まひ、ねむり、こおり  行動後 どく、もうどく、やけど   まずは簡単な行動前から実装していきます。行動前に判...

ナンパしてたら独立できた「人間力の鍛え方」
フリーランス
コミュニケーション能力,ナンパ
ナンパしてたら独立できた「人間力の鍛え方」

  経験談から、人間力を鍛える方法をご紹介します。 今回は「ナンパ」がテーマです。なので、少し男性目線の内容になります。 女性の方は、「男性はこうやって考えている人もいるんだ」といった参考にしてください。   ナンパなんて、と思う人もいるでしょう。 ハラスメント規制も強くなる現代では、安易な...

命中率補正と交代技編 PHPポケモン 45
プログラミング
PHP,PHPポケモン,ポケモン
命中率補正と交代技編 PHPポケモン 45

命中率補正とは ダメージ計算や行動順判定には補正(ランク)を計算した結果を反映していましたが、命中率補正についてはまだ未実装だったため、今回はこちらを作成していきたいと思います。 まずはwikiを参考に、計算方法を見ていきましょう。 命中(ポケモンwiki) https:// wiki.ポケモン.com/wiki/命中 第...

カテゴリ

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