HTML5のCanvasでつくるダイナミックな表現―CreateJSを使う

第13回 モーションブラーと弾むアニメーション

この記事を読むのに必要な時間:およそ 7.5 分

前回つくったマウスポインタに合わせて弾けるスプライトアニメーションに,さらにふたつ表現を加えたい。第1は,アニメーションの残像に,落ちる動きのぼかしを加える。いわゆる「モーションブラー」だ。第2に,ステージの下端まで落ちたインスタンスを弾ませる。簡単な物理の考え方を採入れる。

CreateJSがアップデートされた

先にお伝えしたいのは,CreateJSが2013年9月25日付でアップデートされたことだ。EaselJSは,0.6.1から0.7.0にマイナーバージョンが上がった。今回から早速新たなライブラリを使うことにしよう。すると,script要素に読込むEaselJSライブラリのJavaScriptファイルは差替えなければならない。

<!--
<script src="http://code.createjs.com/easeljs-0.6.1.min.js"></script>
-->
<script src="http://code.createjs.com/easeljs-0.7.0.min.js"></script>

また,前回のスクリプトで用いたクラスBitmapAnimationの名前が,EaselJS 0.7.0からSpriteに変わる。今回のJavaScriptコードでは,新たなクラス名に書替える(後掲コード1参照)。

// var mySprite = new createjs.BitmapAnimation(mySpriteSheet);
var mySprite = new createjs.Sprite(mySpriteSheet);

そしてもうひとつ、SpriteSheet()コンストラクタに渡す引数のオブジェクト(data)で、アニメーションの速さを決めるプロパティが変わる。これまでのfrequencyは、Ticker.tick「イベントいくつごとにひとコマ進めるかを定め」た(第12回「スプライトシートでアニメーションをつくる」の「SpriteSheetとBitmapAnimationクラス参照)。これがspeedプロパティに改められ、デフォルトの何倍速で進めるのかという数値を与える。具体的には、frequencyの逆数(1 / frequency)となり、数値は大きいほどアニメーションが速くなる(後掲コード1参照)。

var data = {};

data.animations = {walk: {
      frames: [0, 0, 1, 2, 2, 3],
      // frequency: 3
      speed: 1 / 3
    }
  };
var mySpriteSheet = new createjs.SpriteSheet(data);

アニメーションの残像にモーションブラーを加える

まずは,アニメーションの残像にモーションブラーを加えよう図1)。ただ厳密にいうと,CreateJSにモーションブラーのぼかし機能はない。ぼかし幅が水平と垂直それぞれに定められるので,垂直方向のぼかしを強くしてそれらしく見せる。

図1 残像にモーションブラーを加える

図1 残像にモーションブラーを加える

ぼかしの前に,残像をどうつくるかというと,これはプロパティひとつで定められる。Stage.autoClearというプロパティがそれだ。デフォルト値はtrueで,ステージを描き替える前にCanvasにあるものは自動的に消される。プロパティをfalseにすると,古い描画が残ったまま重ね描きされる。つまり,残像ができることになる図2)。

図2 Stage.autoClearプロパティをfalseにするとCanvasに残像が重なる

図2 Stage.autoClearプロパティをfalseにするとCanvasに残像が重なる

では,残像のモーションブラーに移ろう。Canvasに直接残像を描いたことから想像がつくように,ぼかしはCanvasに掛ける。オブジェクトをひとつひとつぼかしていたら負荷が高い。とはいえ,Canvas全体をぼかすのも決して軽くはない。ぼかした残像を加えたアニメーションは,大きくつぎの3つの手順で進める。

  1. Canvasの残像にモーションブラーを加える。
  2. Canvasをアルファが加わった背景色で塗る。
  3. マウスポインタの動きに合わせた新たなインスタンスを加える。

第1に,残像の描かれたCanvas全体に,EaselJSのフィルタでモーションブラーを加える。第2に,Canvas全体をアルファが加わった背景色で塗る。Canvasに同じ大きさの半透明のシートを重ねる感覚だ図3)。そうすると,古い残像ほどアルファつき背景色が塗り重ねられて消えてゆく。そのうえで,最後にマウスポインタの動きに合わせた新たなインスタンスを加えればよい。

図3 モーションブラーが加わったCanvasにアルファつきの背景色を塗り重ねる

図3 モーションブラーが加わったCanvasにアルファつきの背景色を塗り重ねる 図3 モーションブラーが加わったCanvasにアルファつきの背景色を塗り重ねる

使うメソッドを確かめていこう。まず,ぼかしについては,第8回「ぼかしフィルタとアルファマスク」BoxBlurFilterクラスで画像イメージをぼかすで解説した。ただし,EaselJS 0.7.0でクラス名がBlurFilterに変わる。引数の定め方は同じだ。前述のとおり,垂直方向のぼかしを強める。また,オブジェクトひとつひとつが小さいアニメーションなので,ぼかし品質は低くして負荷を下げる。

new BlurFilter(水平ぼかし, 垂直ぼかし, ぼかし品質)

つぎに,ぼかしはCanvasに加える。その場合,canvas要素からコンテキストという描画のオブジェクトを得る。そのメソッドがgetContext()だ。メソッドの引数は,取出すコンテキストを定める識別子の文字列である。2次元の描画は"2d"を渡せばよい※1)。

canvas要素.getContext("2d")

CanvasはCreateJSのオブジェクトではないので,DisplayObject.filtersプロパティは使えない。だが,BlurFilterクラスには,Canvasの2Dコンテキストにフィルタを掛けるBlurFilter.applyFilter()メソッドが備わっている。第1引数が2Dコンテキスト,後の4引数は適用する矩形領域を与える。

BlurFilterオブジェクト.applyFilter(2Dコンテキスト, x座標, y座標, 幅, 高さ)

これで道具立ては整った。前回でき上がった第12回コード2マウスポインタの動く座標につくられたスプライトアニメーションがランダムな方向に落ちるに,JavaScriptコードを書き加えていこう。初期設定の関数(initialize())では,getContext()メソッドで2Dコンテキストの参照を変数(context)にとっておく。そして,Stage.autoClearプロパティはfalseに定めて残像を重ねる。

var context;

function initialize() {

  context = canvasElement.getContext("2d");

  stage.autoClear = false;

}

Canvasの残像をぼかしつつ背景色で消してゆく処理は新たな関数(fadeAndBlur())で定め,アニメーションの関数(animate())から呼出す。CanvasはBlurFilter.applyFilter()メソッドで垂直方向を強めにぼかす。BlurFilterインスタンスは使い回せるので,予め変数(blurFilter)に入れておいた。そして,背景色でCanvasを塗るのは,CreateJSでなくCanvasのプロパティおよびメソッドを用いた。fillStyleプロパティでアルファ(0.2)つきの塗り色(0xFFFFFF)を定め,fillRect()メソッドは与えられた矩形領域をその色で塗る(詳しくは,「<canvas>要素で定めた領域に図形を描くをお読みいただきたい)。

var blurFilter = new createjs.BlurFilter(2, 4, 1);

function animate(eventObject) {

  fadeAndBlur();

}

function fadeAndBlur() {
  blurFilter.applyFilter(context, 0, 0, stageWidth, stageHeight);
  context.fillStyle = createjs.Graphics.getRGB(0xFFFFFF, 0.2);
  context.fillRect(0, 0, stageWidth, stageHeight);
}

これでマウスポインタに合わせて落ちるオブジェクトのアニメーションに残像が重なり,モーションブラーも加わる(前掲図1参照)。書替えたスクリプト全体は,つぎのコード1のとおりだ。

コード1 オブジェクトが落ちるアニメーションの残像にモーションブラーを加える

var stage;
var animation;
var stageWidth;
var stageHeight;
var context;
var blurFilter = new createjs.BlurFilter(2, 4, 1);
function initialize() {
  var canvasElement = document.getElementById("myCanvas");
  context = canvasElement.getContext("2d");
  stageWidth = canvasElement.width;
  stageHeight = canvasElement.height;
  stage = new createjs.Stage(canvasElement);
  stage.autoClear = false;
  animation = createAnimation("images/sprite_sheet_s.png");
  stage.addEventListener("stagemousemove", addInstance);
  createjs.Ticker.addEventListener("tick", animate);
}
function addInstance(eventObject) {
  createInstance(stage.mouseX, stage.mouseY, 15);
  stage.update();
}
function createInstance(x, y, halfSpeed) {
  var speed = getRandom(-halfSpeed, halfSpeed);
  var angle = getRandom(0, Math.PI * 2);
  var instance = animation.clone();
  instance.x = x;
  instance.y = y;
  instance.scaleX = instance.scaleY = getRandom(0.4, 1);
  instance.velocityX = Math.cos(angle) * speed;
  instance.velocityY = Math.sin(angle) * speed;
  instance.velocityAlpha = getRandom(-0.07, -0.01);
  instance.gotoAndPlay("walk");
  stage.addChild(instance);
}
function animate(eventObject) {
  var count = stage.getNumChildren() - 1;
  fadeAndBlur();
  for (var i = count; i > -1; i--) {
    var child = stage.getChildAt(i);
    var newY = child.y + child.velocityY;
    var newAlpha = child.alpha + child.velocityAlpha;
    if (newAlpha <= 0 || newY > stageHeight) {
      stage.removeChildAt(i);
    } else {
      child.x += child.velocityX;
      child.y = newY;
      child.alpha = newAlpha;
      child.velocityX *= 0.98;
      child.velocityY += 2;
    }
  }
  stage.update();
}
function createAnimation(file) {
  var data = {};
  data.images = [file];
  data.frames = {width:41, height:55, regX:20, regY:27};
  data.animations = {walk: {
      frames: [0, 0, 1, 2, 2, 3],
      speed: 1 / 3
    }
  };
  var mySpriteSheet = new createjs.SpriteSheet(data);
  var mySprite = new createjs.Sprite(mySpriteSheet);
  return mySprite;
}
function fadeAndBlur() {
  blurFilter.applyFilter(context, 0, 0, stageWidth, stageHeight);
  context.fillStyle = createjs.Graphics.getRGB(0xFFFFFF, 0.2);
  context.fillRect(0, 0, stageWidth, stageHeight);
}
function getRandom(min, max) {
  var randomNumber = Math.random() * (max - min) + min;
  return randomNumber;
}

EaselJS 0.7.0について,もう少し補っておく。このバージョンのコンパクト版JavaScript(JS)ファイルeaseljs-0.7.0.min.jsには,フィルタのクラスも含まれるようになった。そのため,フィルタクラスのJSファイルをひとつひとつ読込まないでよい。また,BlurFilterになって,パフォーマンスも上がったという。

※1)
今のところ,コンテキストを定める識別子は"2d"しかない。canvas要素の扱いについては,HTML5.JP「Canvasの使い方」が参考になる。また,2Dコンテキストの詳しい仕様は,W3C「HTML Canvas 2D Context」(英文)を参照してほしい。

著者プロフィール

野中文雄(のなかふみお)

ソフトウェアトレーナー,テクニカルライター,オーサリングエンジニア。上智大学法学部卒,慶応義塾大学大学院経営管理研究科修士課程修了(MBA)。独立系パソコン販売会社で,総務・人事,企画,外資系企業担当営業などに携わる。その後,マルチメディアコンテンツ制作会社に転職。ソフトウェアトレーニング,コンテンツ制作などの業務を担当する。2001年11月に独立。Web制作者に向けた情報発信プロジェクトF-siteにも参加する。株式会社ロクナナ取締役(非常勤)。

URLhttp://www.FumioNonaka.com/

著書

コメント

コメントの記入