今回は少し目先が変わって、スプライトシートでアニメーションをつくる。「 スプライトシート」というのは、アニメーションのひとコマひとコマをひとつにまとめた画像ファイルだ(図1 ) 。ファイルをひとつにすると、読込みが1回で済むため効率がいい。画像からそれぞれのコマのグラフィックイメージを切り出して、アニメーションとして再生する。パフォーマンスを重く見るゲームコンテンツでも使われる仕組みだ。
図1 スプライトシートの画像ファイル
スプライトシートを用意する
これからつくるのは、前掲図1 のスプライトシートを使った簡単なアニメーションだ。でき上がりのコンテンツを先にjsdo.itでお見せしよう。
スプライトシートは、ひとつあるいは複数の画像ファイルで成立つ。スプライトシートがつくれるツールはいろいろあり、どれを使ってもよい。本稿では、Flash Professional CCを用いた[1] 。大事なのは、画像からコマごとのグラフィックイメージを切り出すということだ。つまり、それぞれのコマについて、予め矩形領域を定めておかなければならない。簡単なのは、すべてのコマを同じ大きさにすることだ(図2 ) 。また、背景を抜きたいなら画像ファイルはPNG形式がよいだろう。
図2 すべてのコマを同じ大きさでPNGファイルに納める
SpriteSheetとBitmapAnimationクラス
スプライトシートアニメーションをつくるには、SpriteSheetとBitmapAnimationのふたつのクラスを使う。まず、SpriteSheetクラスで、スプライトシートのどこからそれぞれのコマのグラフィックイメージを切り出し、どのような順序でアニメーションにするのか定めたSpriteSheetオブジェクトをつくる。引数にはアニメーションを組み立てるための情報をオブジェクトで渡す。つぎに、そのSpriteSheetオブジェクトをBitmapAnimationクラスのコンストラクタに渡して、ステージに置いて再生や停止などの制御ができるBitmapAnimationオブジェクトをつくるという段取りだ。
new createjs.SpriteSheet(情報オブジェクト)
new createjs.BitmapAnimation(SpriteSheetオブジェクト)
SpriteSheet() コンストラクタの引数に渡す情報オブジェクトには、基本としてimagesとframes、およびanimationsの3つのプロパティを与える(表1 ) 。ただ、それらに納める中身や定め方にはバリエーションがある。詳しくは、公式リファレンス「SpriteSheet 」の例(SpriteSheet Format)を参照してほしい。
表1 情報オブジェクトに定める基本のプロパティ
プロパティ 値
images 画像イメージのImageオブジェクトまたはURIの文字列をエレメントに納めた配列
frames フレーム領域の情報をプロパティに納めたObjectインスタンス
animations アニメーション名をプロパティとして、値に再生のための情報を配列またはObjectインスタンスで定める
今回のお題では、次のようなJavaScriptコードを書いた。情報オブジェクト(data)のimagesプロパティには、スプライトシートのパス(file)ひとつを配列に納めて与えた。framesプロパティには、Objectインスタンスで幅と高さ、および基準点の座標を定めた。
{width:幅, height:高さ, regX:水平基準点, regY:垂直基準点}
animationsプロパティの中身については、この後説明する。こうして設定した情報オブジェクトがSpriteSheet() コンストラクタに渡され、できあがったSpriteSheetオブジェクト(mySpriteSheet)は、さらにBitmapAnimation() コンストラクタの引数にして、BitmapAnimationオブジェクト(mySprite)がつくられている。
var data = {};
data.images = [file];
data.frames = {width:82, height:109, regX:41, regY:55};
data.animations = {walk: {
frames: [0, 0, 1, 2, 2, 3],
frequency: 3
}
};
var mySpriteSheet = new createjs.SpriteSheet (data);
var mySprite = new createjs.BitmapAnimation (mySpriteSheet);
改めて、animationsプロパティだ。アニメーションの名前(walk)をプロパティにつけて、値にはObjectインスタンスが与えてある。
そのframesプロパティには、再生するコマ(フレーム)を配列エレメントで定めた。スプライトシートから切り出すグラフィックイメージには、0から始まる連番インデックスが振られる(図3 ) 。そのインデックスを再生順に配列に納める。同じイメージを複数のコマで続けて見せたければ、その数だけインデックスを加えればよい。なお、このインデックスはフレーム番号と呼ばれることもある。
図3 スプライトシートのグラフィックイメージに振られる連番インデックス
プロパティfrequencyは、アニメーションの速さを整数で決める。ただし、値が大きいほど遅くなる。スプライトシートアニメーションは、Ticker.tick イベントで再生が進む。frequencyプロパティの整数値は、そのイベントいくつごとにひとコマ進めるかを定める。アニメーションの世界で「何コマおき 」という指定に当たる。
SpriteSheetとBitmapAnimationのふたつのクラスでスプライトシートアニメーションをつくって再生するのが、次のコード1 だ。上述のスプライトシートアニメーションのオブジェクトをつくる処理は、関数(createAnimation())に定めて初期設定の関数(initialize())から呼び出している。また、スプライトシートのアニメーションは、BitmapAnimation.gotoAndPlay() メソッド で再生する。引数には、情報オブジェクトのanimationsプロパティに定めた再生したいアニメーション名を渡す[2] 。
BitmapAnimationオブジェクト.gotoAndPlay(アニメーション名)
アニメーションする画面の書替えは、お約束どおりTicker.tick イベントのリスナー関数(animate())で行う。この関数が行っているのは、今のところStage.update() メソッドの呼出しだけだ。これで、スプライトシートのアニメーションがステージのうえで再生される(図4 ) 。
コード1 SpriteSheetとBitmapAnimationクラスでスプライトシートアニメーションをつくって再生する
var stage;
var animation;
var stageWidth;
var stageHeight;
function initialize() {
var canvasElement = document.getElementById("myCanvas");
stageWidth = canvasElement.width;
stageHeight = canvasElement.height;
stage = new createjs.Stage(canvasElement);
animation = createAnimation("images/sprite_sheet.png");
stage.addChild(animation);
animation.x = stageWidth / 2;
animation.y = stageHeight / 2;
animation.gotoAndPlay ("walk");
createjs.Ticker.addEventListener("tick", animate);
}
function animate(eventObject) {
stage.update();
}
function createAnimation(file) {
var data = {};
data.images = [file];
data.frames = {width:82, height:109, regX:41, regY:55};
data.animations = {walk: {
frames: [0, 0, 1, 2, 2, 3],
frequency: 3
}
};
var mySpriteSheet = new createjs.SpriteSheet (data);
var mySprite = new createjs.BitmapAnimation (mySpriteSheet);
return mySprite;
}
図4 スプライトシートのアニメーションが再生される
[2] スプライトシートに並べたグラフィックイメージをただその順番に再生すればよいときは、BitmapAnimation.gotoAndPlay() メソッドの引数に再生を始める連番インデックス(フレーム番号)を渡すこともできる(前掲図3 参照) 。
スプライトアニメーションをマウスポインタが動いた位置につくって落とす
さて、このスプライトアニメーションを前回の第11回コード2 に組み込んで、マウスポインタの動きに合わせて弾けさせよう。まず、インスタンスは初めからステージに置くのでなく、Stage.stagemousemove イベントのリスナー関数(addInstance())でマウスポインタの座標に加えていく。リスナー関数からさらにインスタンスをつくる関数(createInstance())が呼出されるのは、第11回コード2 と同じだ。
つぎに、インスタンスをつくる関数(createInstance())の処理も、基本は第11回コード2 と変わっていない。ただ、スプライトアニメーションを毎回頭からつくり直すのは煩わしい。そこで、変数(animation)にとっておいたBitmapAnimationオブジェクトにBitmapAnimation.clone() メソッド を呼び出して、同じ中身の新たなオブジェクトを複製することにした。また、インスタンスの大きさは伸縮のプロパティ(DisplayObject.scaleX とDisplayObject.scaleY )でランダムな値の範囲(0.4~1)に定めている。新たなBitmapAnimationインスタンスは、BitmapAnimation.gotoAndPlay() メソッドで再生するのを忘れてはいけない。
function initialize() {
animation = createAnimation("images/sprite_sheet_s.png");
/*
stage.addChild(animation);
animation.x = stageWidth / 2;
animation.y = stageHeight / 2;
animation.gotoAndPlay("walk");
*/
stage.addEventListener("stagemousemove", addInstance);
}
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 = createShape(2, 10);
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 getRandom(min, max) {
var randomNumber = Math.random() * (max - min) + min;
return randomNumber;
}
Ticker.tick イベントのリスナー関数(animate())は、第11回コード2 そのままだ。BitmapAnimationインスタンスは、ランダムに定めた初速から自由落下し、アルファや垂直位置によりステージから見えなくなったら、Stageオブジェクトの表示リストから除かれる。
function initialize() {
createjs.Ticker.addEventListener("tick", animate);
}
function animate(eventObject) {
var count = stage.getNumChildren() - 1;
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();
}
こうして書き直したスクリプト全体は、次のコード2 のとおりだ。もっとも、前掲コード1 で使ったスプライトシートは、このお題には大き過ぎる。そこで、画像ファイルの大きさは、幅・高さとも半分にした。したがって、スプライトアニメーションのオブジェクトをつくる関数(createAnimation())で、情報オブジェクト(data)のframesプロパティの値が半分に変わっている。
これで、マウスポインタの座標にスプライトシートアニメーションがランダムな設定でつぎつぎとつくられ、アルファを下げながら自由落下する(図5 ) 。jsdo.itにもコードを掲げた。今回使ったスプライトシートでは、少しばかり鬱陶しい絵面になったかもしれない。だが、でき上がりの見た目は素材次第で変わる。次回は、さらに表現の工夫を凝らしたい。
図5 マウスポインタの動く位置に表れたスプライトアニメーションが自由落下する
コード2 マウスポインタの動く座標につくられたスプライトアニメーションがランダムな方向に落ちる
var stage;
var animation;
var stageWidth;
var stageHeight;
function initialize() {
var canvasElement = document.getElementById("myCanvas");
stageWidth = canvasElement.width;
stageHeight = canvasElement.height;
stage = new createjs.Stage(canvasElement);
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;
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],
frequency: 3
}
};
var mySpriteSheet = new createjs.SpriteSheet (data);
var mySprite = new createjs.BitmapAnimation (mySpriteSheet);
return mySprite;
}
function getRandom(min, max) {
var randomNumber = Math.random() * (max - min) + min;
return randomNumber;
}