【初心者向け】jQueryでスライドパズルを作ってみた (2)

スライドパズル

こんにちは、さくらいみかです。

前回の記事では、JavaScriptのライブラリであるjQueryを使って、「スライドパズル」を生成するアプリを作りました。ブラウザ上で好きな画像のURLを読み込ませ、指定したマス目数のスライドパズルが生成される仕様です。

スライドパズル

▲横9マス、縦5マスのスライドパズル

前回のバージョンでは、ブロック移動の操作は「動かしたいブロックをクリックするのみ」でしたが、今回は「スライド操作」を実装し、よりリアルなスライドパズルにアップデートしてみました。

また、それに伴って新機能もつけてみました。

新機能

「軽い」に設定しているときは、今までとは変わらないスライドパズルなのですが、

「重い」の方向にスライダーバーを移動させていくと、徐々に錆びていき……

さらにスライダーバーを右方向へ。「重い」をMAXに設定にすると……

スライドパズルサビサビ

完全に錆びます。動きも錆びついた感じになります。

もしこのスライドパズルが金属製だとしたら、時が経てばきっと錆びてきて動かしづらくなるはず……!! という妄想の産物です。

以下がそのスライドパズルです。ぜひ遊んでみてください。

See the Pen スライドパズル(2) by Mika Sakurai (@mika-sakurai) on CodePen.0

ここからは「スライド機能をどう実装したか」「ブロックを掴んだとき、離したときの動き」「ブロックに錆びを加える方法」についてそれぞれ解説します。

1. ブロックをスライドさせる

1-1. クリック操作でスライドさせる

まず、前バージョンの「Clickイベントによるブロック移動」を簡単におさらいします。

クリック操作のみでブロック(canvas要素)を移動させていたときには、以下のような処理を実装しました。

  1. ブロックをクリックすると、イベントが発生する
  2. 動かすブロックと空きスペースが隣り合ってるかを判定する
  3. ブロックと空きスペースの座標を入れ替える

1-2. スライド操作でスライドさせる

今回は本物のスライドパズルと同じような操作によって、ブロックをスライドさせてみます。ブロックの上にマウス(タッチパネルの場合は指)を置き、スライド動作によりブロックの移動処理を行います。

ドラッグ操作のような動きをしていますが、実際にはドラッグイベントは使っていません。以下のような手順で実装しました。

  • ブロックにイベントリスナーを追加する(マウス用とタッチパネル用、2種類のイベントを追加)
// ブロックにイベントを追加する
canvas.addEventListener("mousedown", touchstartEv, true);
canvas.addEventListener("touchstart", touchstartEv, true);
  • ブロックの移動が可能か判定し前バージョンで解説済)、ブロックに「動かす・離す」イベントを与える
// 【※コードは説明に必要な箇所以外、省略してます】
functiontouchstartEv(ev) {
// ブロックの移動が可能か判定する(ver1 4-1で解説)
moveFlg = checkBlock(comp);
// 移動可能な場合、ブロックに「動かす・離す」イベントを与える
if (moveFlg) {
ev.stopPropagation();
comp.addEventListener("mousemove", moveEv, false);
comp.addEventListener("touchmove", moveEv, false);
comp.addEventListener("mouseout", touchendEv, false);
comp.addEventListener("mouseup", touchendEv, false);
comp.addEventListener("touchend", touchendEv, false);
}
  • マウス(もしくは指)の動きに連動し、ブロックが移動するようにする
function moveEv(ev) {
// 指の動いた距離(ブロックの動く距離)をセット
var move_x = pre_x-event.pageX;
var move_y = pre_y-event.pageY;
// ブロックの座標を取得
var comp_x = comp.style.left.replace("px","");
var comp_y = comp.style.top.replace("px","");
// ブロックを横に動かす場合
if ((comp.height*moveId0 == comp.height*blankId0) || !((comp.height*moveId0 <= comp_y-move_y && comp_y-move_y <= comp.height*blankId0) || (comp.height*blankId0 <= comp_y-move_y && comp_y-move_y <= comp.height*moveId0))) {
move_y = 0;
}
// ブロックを縦に動かす場合
if ((comp.width*moveId1 == comp.width*blankId1) || !((comp.width*moveId1 <= comp_x-move_x && comp_x-move_x <= comp.width*blankId1) || (comp.width*blankId1 <= comp_x-move_x && comp_x-move_x <= comp.width*moveId1))) {
move_x = 0;
}
// ブロックを移動させる
comp.style.top = comp_y-move_y + "px";
comp.style.left = comp_x-move_x + "px";
// マウスの座標を保持(次にmoveEvが呼ばれたときに利用する)
pre_x = event.pageX;
pre_y = event.pageY;
}

2. ブロックを所定の位置に戻す

スライドパズルをちょっと扱いやすくするために「ブロックをスライド途中で離すと、自動で所定の位置に戻っていく」ように実装してみます。

2-1. ブロックが到達する位置を決める

掴んでいたブロックを離したときに、より近い方のスペースに移動するようにします。離したときのブロックの位置によって、元々ブロックがあった位置・空きスペースだった位置のいずれかに移動するのです。

// 手を離した後のブロックの位置を設定
var changeFlg = false; // ブロック入れ替えフラグ
// 縦方向に動くとき
if ((moveId0 < blankId0 && comp_y > (comp.height*moveId0+comp.height/2)) || (moveId0 > blankId0 && comp_y < (comp.height*blankId0+comp.height/2))) { // 移動する場合
block_y = comp.height*blankId0; // 空きスペースの座標
changeFlg = true;
} else {
block_y = comp.height*moveId0; // 元の位置に戻す
}
// 横方向に動くとき
if ((moveId1 < blankId1 && comp_x > (comp.width*moveId1+comp.width/2)) || (moveId1 > blankId1 && comp_x < (comp.width*blankId1+comp.width/2))) { // 移動する場合
block_x = comp.width*blankId1; // 空きスペースの座標
changeFlg = true;
} else {
block_x = comp.width*moveId1; // 元の位置に戻す
}

2-2. 一定時間ごとにブロックを動かす

タイマー処理で一定時間ごとにブロックを動かし、2-1で決めた位置まで到達したら完了とします。(移動完了の処理は、ver1.の4-2で解説)

タイマーの間隔や、ブロックの移動距離の設定値を変えると、動き方が変わります(動きの滑らかさ、素早さ等)。

// ブロックが自動で動くときのタイマー呼び出し処理
var msec = 10; // ミリ秒
var distance = 5; // 呼び出すたびに移動する距離(px)
var endTimer = setInterval(function(){
if (moveId0 != blankId0) { // 縦に動くとき
var move_y = parseInt(comp.style.top.replace("px",""));
// 移動させる(5pxずつ移動)
if (block_y - comp_y > 0) { // プラス方向に戻す場合
comp.style.top = move_y+distance + "px";
} else { // マイナス方向に戻す場合
comp.style.top = move_y-distance + "px";
}
move_y = comp.style.top.replace("px",""); // 動いてる途中のブロックの位置
// ちょっとでもはみ出した場合、所定の位置に移動させて完了とする
if (!((block_y < move_y && move_y < comp_y) || (comp_y < move_y && move_y < block_y))) {
comp.style.top = block_y + "px";
clearInterval(endTimer);
complete();
}
}
if (moveId1 != blankId1) { // 横に動くとき
var move_x = parseInt(comp.style.left.replace("px",""));
if (block_x - comp_x > 0) { // プラス方向に戻す場合
comp.style.left = move_x+distance + "px";
} else { // マイナス方向に戻す場合
comp.style.left = move_x-distance + "px";
}
move_x = comp.style.left.replace("px",""); // 動いてる途中のブロックの位置
// ちょっとでもはみ出した場合、所定の位置に移動させて完了とする
if (!((block_x < move_x && move_x < comp_x) || (comp_x < move_x && move_x < block_x))) {
comp.style.left = block_x + "px";
clearInterval(endTimer);
complete(); //完了!!
}
}
} , msec);

3. ブロックに「錆(さび)」の表現を加える

「軽い・重い」のスライドバーの設定値によって、ブロックを動かすときの重さを変えてみます。また「重い」にバーを移動するに従って、ブロックを錆びた見た目にしてみましょう。

今回は重みのスライダーバーは6段階に変化するようにしました。重みレベルが1~4の場合は、単純にレベルに応じて重みを加えてるだけですが、5~6の場合は錆によるガタツキも加えてみました。

3-1. スライドに重みを加える

以下の動きを、重みに応じて変更していきます。

  • ブロックを掴んでスライドさせる動き
  • ブロックを離したあと、自動でスライドする動き

例えば、ブロックを掴んでスライドさせるとき、移動距離を重みで割り、動きが鈍くなるようにしています。

var move_x = (pre_x-event.pageX)/(omomi/2);
var move_y = (pre_y-event.pageY)/(omomi/2);

3-2. 錆びたブロックにガタツキを与える

重みレベルが5~6の場合、以下のような方法でガタツキを表現しています。

  • ブロックを掴んで動かすたびにmoveEvというFunctionが呼ばれるが、15回に一度の頻度でブロックに違う動き(ほんの少し後退)をさせる
if (omomi > 4) {
if (movecnt%15 == 0) { // mousemove、15回に一度少し後退させる(重みを出す)
if (move_x>0) {
move_x = move_x-(omomi/2);
} else if (move_x<0) {
move_x = move_x+(omomi/2);
}
if (move_y>0) {
move_y = move_y-(omomi/2);
} else if (move_y<0) {
move_y = move_y+(omomi/2);
}
}
}
  • ブロックを離したあと、タイマー処理で一定時間ごとにブロックが自動スライドするようになってるが、その間隔を大きくすることでぎこちない動きをさせる
var msec; // ミリ秒
if (omomi > 4) {
msec = omomi*10;
} else {
msec = 10;
}

3-3. ブロックの見た目を錆びさせる

各ブロックの背景に、錆テクスチャの画像(Photoshopで作成)を配置し、前面の画像を透過させることで錆びてるっぽい表現をさせています。

  • レベル1~6まで、前面の画像の透過度を配列で設定しておく。レベルが上がるほど、アルファ値は下がる
var sabiLv = [1,1,0.8,0.6,0.4,0.2];
  • Canvasに画像を配置する際に、前面画像の透過度を設定する
canvas.style.backgroundImage = "url(錆テクスチャの画像URL)";
var context = canvas.getContext("2d");
context.globalAlpha = sabiLv[$("#omomi").val()-1]; // 前面の画像を透過

まとめ

今回は主にブロックを掴んだとき、離したときの動きについてまとめてみました。前回と比べるとかなりスライドパズルっぽさが増したんじゃないでしょうか。

基本的な動きはもちろん、錆の表現もできて満足してますが、ちゃんとガタついてるように見えましたか!?

スライドパズルについては一旦これで終わりですが、今回実装した機能は今後なにか別のプロダクトにも利用できそうな気がしています。主に「オブジェクトのスライド加減を調整したい!」という人のお役に立ちそうかなと思いますが、もしほかにも参考になりそうなところがあれば幸いです。

 

前回の記事▼

SHARE

  • 広告主募集
  • ライター・編集者募集
  • WorkshipSPACE
週2日20万円以上のお仕事多数
Workship