スプライトシートのアニメーション


JavaScriptを使用して、HTML 5キャンバス上のスプライトシートをアニメーション化してみましょう.

ちょっとしたセットアップ


まず、キャンバス要素を作成しましょう.
<canvas width="300" height="200"></canvas>
境界線を追加します.
canvas {
  border: 1px solid black;
}
とスプライトシートをロードするhttps://opengameart.org/content/green-cap-character-16x18 ). 我々はそれをしている間、キャンバスとその2 Dコンテキストへのアクセスを取得しましょう.
let img = new Image();
img.src = 'https://opengameart.org/sites/default/files/Green-Cap-Character-16x18.png';
img.onload = function() {
  init();
};

let canvas = document.querySelector('canvas');
let ctx = canvas.getContext('2d');

function init() {
  // future animation code goes here
}
The init 関数は、画像がロードされた後に呼び出されるimg.onload . これは、私たちがそれを操作しようとする前に、イメージがロードされることを保証することです.すべてのアニメーションコードはinit 関数.このチュートリアルのために、これは動きます.複数の画像を扱う場合は、おそらく何かを行う前に、それらのすべてをロードするのを待つ約束を使用したいと思います.

スプライトシート


今設定しているので、そのイメージを見てみましょう.

各行は、アニメーションサイクルを表します.最初の(行)の行は、下の方向に歩いている文字は、2番目の行を歩いている、3番目の行を左に歩いていると、4番目(下)行を右歩いている.技術的には、左のコラムは立っている(アニメーションでない)一方、中間の、そして、右のコラムはアニメーションフレームです.私は、3つのすべてをスムーズな歩行アニメーションのために使うことができると思います.😊

コンテキストのdrawimageメソッド


我々のイメージをアニメーション化する前に、見てみましょうdrawImage それが自動的にスプライトシートをスライスして、我々のキャンバスに適用するために使うものであるコンテクスト方法.
MDN docs - drawImage
WOAは、そのメソッドのパラメータがたくさんある!特に第3のフォームは、我々が使用されるものです.心配しないで、それはそれほど悪くはない.そこに論理的なグループ化があります.
drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
The image 引数はソースイメージです.次の4つsx , sy , sWidth , and sHeight ) ソースイメージ-スプライトシートに関連します.最後の4人dx , dy , dWidth , and dHeight ) 宛先に関連する-キャンバス.
"x "と"y "パラメータsx , sy , dx , dy ) スプライトシート(ソース)とキャンバス(宛先)の開始位置に関連します.それは本質的にグリッドであり、左上は(0、0)で始まり、右端と下に積極的に動く.言い換えれば、(50,30)は右画素と30画素の50画素である.
"width "と"height "パラメータsWidth , sHeight , dWidth , and dHeight ) スプライトシートとキャンバスの幅と高さを参照してください.つのセクションにそれを壊しましょう.ソースパラメータsx , sy , sWidth , sHeight ) (10、15、20、30)、開始位置(グリッド座標)は(10、15)であり、(30、45)まで伸びる.そして、終了座標はsx + sWidth , sy + sHeight ).

最初の描画


今、私たちはdrawImage 方法、実際にそれをアクションで見ましょう.
私たちのスプライトシートの文字フレームサイズはファイル名で便利にラベル付けされます16x18 ), それで、それは我々に幅と高さ属性を与えます.第1フレームは(0,0)から始まり(16,18)終了する.それをキャンバスに描きましょう.このフレームをキャンバス上で(0,0)から開始し、その比率を保つ.
function init() {
  ctx.drawImage(img, 0, 0, 16, 18, 0, 0, 16, 18);
}
そして、我々は我々の最初のフレームを持っています!でも少し小さいです.それを見やすくするために少しスケールしましょう.
これを上に変更します.
const scale = 2;
function init() {
  ctx.drawImage(img, 0, 0, 16, 18, 0, 0, 16 * scale, 18 * scale);
}
キャンバス上に描かれたイメージは、水平方向と垂直方向の両方で2倍になります.によってdWidth and dHeight 値は、我々は小さく、またはキャンバス上で大きくするには、元の画像をスケールすることができます.あなたがピクセルを扱っているように、これを行うときに注意してください、それはかなり迅速にぼやけ始めることができます.変更するscale 値を参照してください.

次のフレーム


2番目のフレームを描画するには、ソースセットの値を変更するだけです.具体的にはsx and sy . 各フレームの幅と高さは同じですので、これらの値を変更する必要はありません.実際には、これらの値をプルし、カップルスケールの値を作成し、我々の現在のフレームの右側に私たちの次の2つのフレームを描画しましょう.
const scale = 2;
const width = 16;
const height = 18;
const scaledWidth = scale * width;
const scaledHeight = scale * height;

function init() {
  ctx.drawImage(img, 0, 0, width, height, 0, 0, scaledWidth, scaledHeight);
  ctx.drawImage(img, width, 0, width, height, scaledWidth, 0, scaledWidth, scaledHeight);
  ctx.drawImage(img, width * 2, 0, width, height, scaledWidth * 2, 0, scaledWidth, scaledHeight);
}
これが今のように見えます.
現在、我々はスプライトシートの全体の一番上の列を持っています、しかし、3つの別々のフレームで.あなたが見るならばctx.drawImage 呼び出しは、現在変更される4つの値だけですsx , sy , dx , and dy .
ちょっと簡単にしましょう.我々がそれをしている間、ピクセルを扱う代わりにスプライトシートからフレーム番号を使い始めましょう.
全て置換ctx.drawImage これを呼び出します.
function drawFrame(frameX, frameY, canvasX, canvasY) {
  ctx.drawImage(img,
                frameX * width, frameY * height, width, height,
                canvasX, canvasY, scaledWidth, scaledHeight);
}

function init() {
  drawFrame(0, 0, 0, 0);
  drawFrame(1, 0, scaledWidth, 0);
  drawFrame(0, 0, scaledWidth * 2, 0);
  drawFrame(2, 0, scaledWidth * 3, 0);
}
我々drawFrame 関数はスプライトシートの数学を扱うので、フレーム番号( 0から始まり、配列のようにX , 0 , 1 , 2 )を渡すだけでよい.
キャンバスの“x”と“y”の値はまだピクセル値を取るので、我々は文字を配置する上でより良い制御があります.移動scaledWidth 関数内の乗数scaledWidth * canvasX ) すべての移動/変更全体のスケール文字の幅を一度に意味します.文字が4フレームか5ピクセルずつ動いていると言うなら、それはウォーキングアニメーションでは動きません.それで、我々はそのままそれを去ります.
また、そのリストには余分な行がありますdrawFrame 呼び出し.これは、スプライトシートのトップ3フレームを描画するのではなく、アニメーションサイクルがどのように見えるかを示すことです.アニメーションのサイクルの代わりに“左ステップ、右ステップ”を繰り返すと、それは“スタンド、左、スタンド、右”を繰り返します-それは少し良いアニメーションサイクルです.80年代のゲームの数は、2つのステップアニメーションを使用しました.
これは現在のところです.

この文字をアニメーション化しましょう!


今、我々は我々の性格をアニメーション化する準備ができています!見てみましょうrequestAnimationFrameMDN docs .
これは、ループを作成するために使用するものです.私たちはまたsetInterval , でもrequestAnimationFrame ブラウザでの最適な最適化がすでに行われていますが、これはブラウザによって60回のフレーム(またはできるだけ近い)で実行され、ブラウザ/タブがフォーカスを失うときにアニメーションループを停止させるようなものです.
本質的に、requestAnimationFrame 再帰的な関数です-私たちのアニメーションループを作成するには、私たちは電話しますrequestAnimationFrame 関数から再び引数を渡します.このような
window.requestAnimationFrame(step);

function step() {
  // do something
  window.requestAnimationFrame(step);
}
前の孤独な呼び出しwalk 関数はループを開始します.
我々がそれを使用する前に、我々が知っていて、使う必要がある1つの他の文脈メソッドがありますclearRect ( MDN docs ). キャンバスに描画するとき、我々は呼び出し続けるdrawFrame 同じ位置に、それはすでにそこに何の上に描画を維持します.簡単にするために、我々は描くだけではなく、それぞれの描画の間にキャンバス全体をクリアします.
だから、我々のドローループは明確なように見える、最初のフレームを描画し、明確な、第2のフレームを描画するなど.
つまり
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawFrame(0, 0, 0, 0);
// repeat for each frame
OK、この文字をアニメーション化しましょう!サイクルループ(0、1、0、2)のために配列を作りましょう、そして、我々がそのサイクルにあるところを追跡する何か.それから、我々は我々をつくりますstep 関数は、メインアニメーションループとして機能します.
ステップ関数はキャンバスをクリアし、フレームを描画し、サイクルループ内の我々の位置を進みますrequestAnimationFrame .
const cycleLoop = [0, 1, 0, 2];
let currentLoopIndex = 0;

function step() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  drawFrame(cycleLoop[currentLoopIndex], 0, 0, 0);
  currentLoopIndex++;
  if (currentLoopIndex >= cycleLoop.length) {
    currentLoopIndex = 0;
  }
  window.requestAnimationFrame(step);
}
アニメーションを開始するにはinit 関数.
function init() {
  window.requestAnimationFrame(step);
}
その文字が速く行く場所です!😂

そこに減速!


我々の性格が少し制御不能であるように見えます.ブラウザがそれを許すならば、キャラクタは1秒につき60のフレーム、または可能な限り近くに引かれます.それを制限しておきましょう.我々はフレームを追跡する必要があります.では、step 機能は、我々はすべての呼び出しにカウンタを進めるつもりですが、15フレーム後にのみ描画します.15フレームを渡すと、カウンタをリセットし、フレームを描画します.
const cycleLoop = [0, 1, 0, 2];
let currentLoopIndex = 0;
let frameCount = 0;

function step() {
  frameCount++;
  if (frameCount < 15) {
    window.requestAnimationFrame(step);
    return;
  }
  frameCount = 0;
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  drawFrame(cycleLoop[currentLoopIndex], 0, 0, 0);
  currentLoopIndex++;
  if (currentLoopIndex >= cycleLoop.length) {
    currentLoopIndex = 0;
  }
  window.requestAnimationFrame(step);
}
より良い!

他の方向


これまでのところ、我々はダウン方向を処理しました.どのように我々は、各方向に完全な4段階のサイクルを行うので、アニメーションを少し修正する方法は?
覚えておいてください、「ダウン」フレームは私たちのコード(スプライトシートの最初の行)、行1、左は行2、右は行3(スプライトシートの一番下の行)です.サイクルは0、1、0、2行ごとに残ります.我々は既にサイクル変更を処理しているので、変更する必要がある唯一のものはdrawFrame 関数.
我々は現在の方向を追跡する変数を追加します.それを簡単に保つために、我々は連続して(0、1、2、3、繰り返し)ので、スプライトシートの順序(下、上、左、右)に移動します.
サイクルがリセットされると、次の方向に移動します.そして、我々があらゆる方向を通り抜けたならば、我々は始めます.だから、我々の更新step 関数と関連変数は以下のようになります.
const cycleLoop = [0, 1, 0, 2];
let currentLoopIndex = 0;
let frameCount = 0;
let currentDirection = 0;

function step() {
  frameCount++;
  if (frameCount < 15) {
    window.requestAnimationFrame(step);
    return;
  }
  frameCount = 0;
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  drawFrame(cycleLoop[currentLoopIndex], currentDirection, 0, 0);
  currentLoopIndex++;
  if (currentLoopIndex >= cycleLoop.length) {
    currentLoopIndex = 0;
    currentDirection++; // Next row/direction in the sprite sheet
  }
  // Reset to the "down" direction once we've run through them all
  if (currentDirection >= 4) {
    currentDirection = 0;
  }
  window.requestAnimationFrame(step);
}
そして、我々はそれを持っている!私たちのキャラクターは、すべての4つの方向に歩いている、すべての単一のイメージからアニメーション.