ポケモンの進化演出
前回に続いて、ポケモンの進化演出を実装していきます。バックエンドの処理はざっと説明をしたので、今回はフロントエンド(JavaScript)側の処理を作成していきましょう。
進化画面は新しく設けたので、こちらにもバトル画面で使っているメッセージ用JSを作成していきます。処理自体はほとんど変わりませんが、最終処理や分岐が少し異なるため、ファイル分けしています。
進化画面用メッセージJS(/Public/Assets/js/Evolve/message.js)
/*----------------------------------------------------------
// 初期化する関数
----------------------------------------------------------*/
var supportedFlag = $.keyframe.isSupported();
// 自動メッセージ判定用
var auto_msg;
// メッセージボックスのクリック判定用
var click_flg;
/**
* 画面読み込み時の関数
* @function ready
* @return void
**/
var startInit = function(){
// 現在のメッセージ
var now = $('.result-message.active');
if((now.length === 0) || now.hasClass('last-message')){
doLastMsg();
return;
}
doNotLastMsg();
}
/**
* メッセージボックスクリック時の関数
* @function click
* @return void
**/
var clickMsgBoxInit = function(){
click_flg = true;
// 変数をリセット
auto_msg = false;
$('.action-message-box').on('click', async function(){
if(click_flg === false) return;
$(".message-scroll-icon").hide();
// メッセージボックスを処理終了まで無効化
click_flg = false;
// 現在のメッセージ
var now = $('.result-message.active');
await actionMsgBox(now);
// 次がオートメッセージの場合は再度実行
while(auto_msg){
now = now.next();
await actionMsgBox(now);
}
// メッセージボックスを有効可
click_flg = true;
});
}
/**
* 進化キャンセルボタン
* @function click
* @return void
**/
var clickCancelEvolveInit = function(){
$('#cancel-evolve').on('click', async function(){
// 自身のボタンを非表示化
$(this).hide()
// アニメーションの強制終了(コールバックを無効化)
$('#pokemon-before').resetKeyframe(function(){});
$('#pokemon-before').css('opacity', 1);
await nextMsg($('.result-message.active'));
// cancelボタンで発火しないように1秒後にメッセージボックスを有効可
setTimeout(function() {
click_flg = true;
}, 1000);
});
}
/*----------------------------------------------------------
// 処理内で呼び出す関数
----------------------------------------------------------*/
/**
* メッセージアクション
* @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')){
// ==============================================
// 進化アニメーション ===========================
//
case 'evolve':
$('#cancel-evolve').show()
await evolvePokemon();
break;
// ==============================================
// 進化キャンセル ===============================
//
case 'cancel':
$('#remote-form-action').val('cancel');
$('#remote-form').submit();
break;
}
// 次のメッセージへ
await nextMsg(now);
}
resolve();
});
}
// ==============================================
// 進化演出 =====================================
//
/**
* 5秒間の進化アニメーション
* @param mixed param
* @return Promise
**/
var evolvePokemon = function(param){
return new Promise ((resolve, reject) => {
var keyframe = {};
var per = 100;
var opacity = 1;
while (per) {
if(opacity){
keyframe[per + '%'] = {opacity: 0};
opacity = 0;
}else{
keyframe[per + '%'] = {opacity: 1};
opacity = 1;
}
per -= 3;
if(per < 0){
per = 0;
}
}
keyframe['0%'] = {opacity: 1};
keyframe.name = 'evolve-animation';
// キーフレームの用意
$.keyframe.define(keyframe);
// 経験値バーのアニメーション
$('#pokemon-before').playKeyframe({
name: 'evolve-animation',
duration: '5000ms',
timingFunction: 'ease',
delay: '0s',
iterationCount: 1, // 繰り返し回数
direction: 'normal',
fillMode: 'forwards',
complete: function(){
$('#cancel-evolve').hide()
$('#remote-form-action').val('evolve');
$('#remote-form').submit();
}
});
});
}
// ==============================================
/**
* 次のメッセージへ移行する処理
* @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('last-message')){
// 最終メッセージ
await doLastMsg();
}else{
// 最終メッセージではない
doNotLastMsg();
}
// 次のメッセージがオートメッセージかどうかの判定
if(next.data('auto')){
auto_msg = true;
}else{
auto_msg = false;
}
// 処理終了
resolve();
});
}
/**
* 最終メッセージの処理
* @return void
**/
var doLastMsg = function(){
// スクロールアイコンを非表示
$('.message-scroll-icon').hide();
// 終了
$('#remote-form-action').val('');
setTimeout(function() {
$('#remote-form').submit();
}, 500);
}
/**
* 最終メッセージではない場合の処理
* @return void
**/
var doNotLastMsg = function(){
// スクロールアイコンを表示
$('.message-scroll-icon').show();
}
/*----------------------------------------------------------
// 初期化
----------------------------------------------------------*/
jQuery(function($){
startInit();
clickMsgBoxInit();
clickCancelEvolveInit();
});
それでは、1つずつ処理を見ていきましょう。
点滅処理
まずは進化時の点滅処理についてです。ピカチュウがライチュウに進化する場合、ピカチュウが点滅してライチュウがチラチラと見えている状態になりますね。これをCSSで演出するには、ライチュウの画像にピカチュウを重ね、ピカチュウの透明度を0→1とループさせることで演出ができます。
ただ、ピカチュウはgif画像のため背景がなく、ただ重ねただけでは標準状態でライチュウが見えてしまいます。そうならないためにも、ピカチュウの背景色を白にして、paddingを大きめに設定することで、後ろに用意されているライチュウを隠します。
進化画面(/Resources/Pages/Evolve.php)
<figure class="position-relative d-inline-block">
<img
src="/Assets/img/pokemon/dots/front/<?=$pokemon->getAfterClass()?>.gif"
alt="進化先"
class="bg-white p-5">
<img
id="pokemon-before"
src="/Assets/img/pokemon/dots/front/<?=get_class($pokemon)?>.gif"
alt="<?=$pokemon->getName()?>"
class="position-absolute bg-white p-5"
style="left:0;top:0;">
</figure>
点滅処理はキーフレームで行いますが、こちらはHPバーと同じくjQueryのKeyframeライブラリを使用してセットしています。
// ==============================================
// 進化演出 =====================================
//
/**
* 5秒間の進化アニメーション
* @param mixed param
* @return Promise
**/
var evolvePokemon = function(param){
return new Promise ((resolve, reject) => {
var keyframe = {};
var per = 100;
var opacity = 1;
while (per) {
if(opacity){
keyframe[per + '%'] = {opacity: 0};
opacity = 0;
}else{
keyframe[per + '%'] = {opacity: 1};
opacity = 1;
}
per -= 3;
if(per < 0){
per = 0;
}
}
keyframe['0%'] = {opacity: 1};
keyframe.name = 'evolve-animation';
// キーフレームの用意
$.keyframe.define(keyframe);
// 経験値バーのアニメーション
$('#pokemon-before').playKeyframe({
name: 'evolve-animation',
duration: '5000ms',
timingFunction: 'ease',
delay: '0s',
iterationCount: 1, // 繰り返し回数
direction: 'normal',
fillMode: 'forwards',
complete: function(){
$('#cancel-evolve').hide()
$('#remote-form-action').val('evolve');
$('#remote-form').submit();
}
});
});
}
点滅処理は透明度の0→1を単純にループさせるわけではなく、0%から100%まで0→1を刻んで設定しています。PHPポケモンの場合は3%区切りです。この設定にした理由は、playKeyframeの完了コールバックで進化処理を実行させるためです。
ポケモンでは、進化演出が始まり放っておくことで進化が確定します。操作が必要なのは中断させる場合のみです。また、演出処理は中断させる判断への余裕を持たせるためにも5秒間としています。
完了コールバックでは、リモートフォームにアクションをセットしてサブミットしています。
進化の中断
次に進化の中断処理についてです。進化演出が始まり、5秒後には進化が確定します。それを中断させられるように、演出が始まればメッセージボックス上にキャンセルボタンを配置します。
<div class="message-box action-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-toggle='<?=$responses[$status]['toggle'] ?? ''?>'
data-auto='<?=$auto ?? ''?>'>
<?=$msg?>
</p>
<?php endforeach; ?>
<span class="message-scroll-icon small">【CLICK】</span>
<button type="button" id="cancel-evolve" class="btn btn-danger" style="display:none;">進化させない</button>
</div>
操作パネルはメッセージボックス欄のみにしておきたかったため、今回メッセージボックス上にボタンを配置するというレイアウトにしました。
中断ボタンが押されれば、進化演出と送信処理を止める必要があります。なので、クリック判定でキーフレームを破棄して、事前に用意した中断時のメッセージへ進めます。
/**
* 進化キャンセルボタン
* @function click
* @return void
**/
var clickCancelEvolveInit = function(){
$('#cancel-evolve').on('click', async function(){
// 自身のボタンを非表示化
$(this).hide()
// アニメーションの強制終了(コールバックを無効化)
$('#pokemon-before').resetKeyframe(function(){});
$('#pokemon-before').css('opacity', 1);
await nextMsg($('.result-message.active'));
// cancelボタンで発火しないように1秒後にメッセージボックスを有効可
setTimeout(function() {
click_flg = true;
}, 1000);
});
}
中断後、タイミングによっては進化先のポケモンが見えてしまっている場合があります。それを防ぐためにも、キーフレーム中断後に透明度を1に変更しています。
さらに、中断後は無効化されているメッセージボックスを有効にする必要があります。そのためにグローバル化したクリックフラグを1秒後に有効化させました。
※すぐに有効化させると、中断ボタンを押した判定が同時に動いているためメッセージが2つ進むという現象が起こりました。別の対策方法もありそうですが今回は簡易的にこの方法で対処しました。
それでは実際に、PHPポケモンの進化演出を見てみましょう。
進化の中断
進化
しっかり期待通りの演出がされましたね。点滅はもっと細かく抑揚がほしいところですが、今回はこれぐらいにしておきましょう。
より本家らしく再現したい人は、ぜひ挑戦してみてください。
まとめ
いかがだったでしょうか。
今回のPHPポケモンは「進化アニメーション・演出」についてご紹介しました。
検証用に、レベルが1発で上がる経験値をくれる同レベルのカメックスを用意して、さらにササッと倒せるように「つるのムチ」の威力を245まで引き上げたのですが、まさかの確定2発というカメックスの装甲の硬さ(フシギダネの攻撃の貧弱さ)がわかる結果となりました。
プログラミングやゲーム制作に興味がある人は、ぜひ参考にしてみてくださいね。