JavaScript

簡単アニメーション!Pixi.jsを触ってみる!(3)複数のオブジェクトを動かしてみる

More than 1 year has passed since last update.

簡単アニメーション!Pixi.jsを触ってみる!
〜(3)複数のオブジェクトを動かしてみる〜

前回から間が空いてしまいましたが、Pixi.jsの続きです。


pixi.jsの公式サイトへ

Pixi.jsはJavascriptで簡単にアニメーションが作れるライブラリです。
前回は画像の動かし方と代表的なプロパティを触ってみました。

今回は、複数のオブジェクトを組み合わせた表現に挑戦してみたいと思います。

簡単な方法でうごかす

1つの画像をたくさん動かす

まずは簡単なところから。

ということで、1つの画像ファイルから沢山のスプライトを作成してアニメーションさせてみます。
実は、これ前回既にやっています。雪のサンプル

画像ファイルから複数のスプライトを作っている部分のコードです。

// 画像からスプライトオブジェクトを作る
var texture = PIXI.Texture.fromImage('img/snow2.png');
var MAX_SNOW = 300;
var snowimgs = [];

// 雪。
for(var cnt=0;cnt < MAX_SNOW;cnt ++){
  snowimgs.push(new PIXI.Sprite(texture));
  snowimgs[cnt].position.x = Math.random() * 600;
  snowimgs[cnt].position.y = Math.random() * 400;
  snowimgs[cnt].anchor.x = 0.5;
  snowimgs[cnt].anchor.y = 0.5;
  var base = Math.random();
  snowimgs[cnt].alpha = (base/2) + 0.4;
  snowimgs[cnt].scale.x = base/2;
  snowimgs[cnt].scale.y = base/2;
  stage.addChild(snowimgs[cnt]);
}

この例では、雪を表現するために同じ白丸がたくさん欲しいので、

  1. 画像ファイルからテクスチャを1つだけ作る
  2. テクスチャから、たくさんのスプライトを作る

という風に処理しました。

複数の画像を動かす

それでは、異なる画像ファイルを動かしてみます。
まずは、ベタな方法から。

著作権面倒くさいので、私の曲ジャケ画像を使います。
宣伝も出来て、 一石二鳥! というわけですw
密かに↓の画像には、それぞれのジャケの曲にリンクが貼ってありますので、聞きまくってください。記事の続きは聞き飽きたら読むくらいの感じでw

さて、曲を堪能いただいたところで、残念ながら記事にもどります。
これらの画像を動かしてみようと思います。

今回は、それぞれの画像から、1つづつスプライトを作ります。

var imgs = [
  "../sc/00.jpg",
  "../sc/01.jpg",
  "../sc/02.jpg",
  "../sc/03.jpg",
  "../sc/04.jpg",
  "../sc/05.jpg",
  "../sc/06.jpg",
  "../sc/07.jpg"]; 

var sprites = [];

// 画像からスプライトオブジェクトを作る
for(var cnt=0;cnt < imgs.length;cnt ++){
  var texture = PIXI.Texture.fromImage(imgs[cnt]);
  sprites.push(new PIXI.Sprite(texture));
  sprites[cnt].position.x = Math.random() * 600;
  sprites[cnt].position.y = Math.random() * 400;
  sprites[cnt].anchor.x = 0.5;
  sprites[cnt].anchor.y = 0.5;
  stage.addChild(sprites[cnt]);
}

動作サンプル

なんか気持ち悪い。。。w 酔う。。
アニメーションの方法は画像1つの場合と変わらないので省略します。

スプライトシートを使ってみる

さて、画像を沢山ならべるだけだと、前回から何の進歩もありませんね。それでは面白くありません。
今回は「スプライトシート」というものに挑戦してみたいと思います。

pixi.jsのサンプルだとコレ↓でスプライトシートを使っています。

スプライトシートとは?

私は上記pixi.jsのサンプルを見て始めて知ったド素人なのですが、 スプライトシート というのは、どうやら複数の画像を1つにまとめるための機能で、Texture atlasと呼ばれる仕組みを使ったものらしいです。

Webの人になじみの深いCSSスプライトも同じようなものですね。
ということなので、これを使うことで、1つ1つの画像をロードするより高速化できそうです。

スプライトシートでは下記2つのファイルを作ります。

  • 複数の画像を1つにまとめた大きな画像ファイル(png)
  • 大きな画像の中に含まれる小さな画像の情報を記載したjsonファイル

やってみないことにはよくわからないので、まずは作ってみましょう。

スプライトシートの画像を作る

スプライトシートを簡単に作るためのツール →TexturePackerというものがあるようです。
pixi.jsのサイトを読むと、ほとんどこれ前提で話が進んでいるようにもみえます。

が、とりあえず今回は試していません。
手持ちのモノだけでやってやれなかったら試そうという作戦です。(←ようするに面倒くさがり)

pngが作れれば良いのであれば、何でもいいはずなので、先にFireworksで↓こんなのを作りました。テキトーですねw

初スプライトシート!

もちろん、画像を作成するためのツールはTexturePackerやFireworksでなくても、PhotoshopやFlash、Illustratorなど、背景透明のpngが出力できるモノなら何でもいいはずです。

コツは2つ。

  • 俺は四角以外使わん!という天才以外は背景は透明に
  • 後で中に入れた小さな画像の座標が解らなくならないように覚えておく

座標はメンドウなので、大きさ同じで等間隔にしとくと、私のように頭が悪い人でもなんとかなります。

スプライトシートのjsonを作る

次にjsonを作ります。
pixi.jsのドキュメントを読んでもTexturePackerを使え、としか書いてないので、先ほどのpixi.jsのexample2をのぞき見します。

SpriteSheet.json
{"frames": {

"eggHead.png":
{
  "frame": {"x":2,"y":169,"w":142,"h":157},
  "rotated": false,
  "trimmed": false,
  "spriteSourceSize": {"x":0,"y":0,"w":142,"h":157},
  "sourceSize": {"w":142,"h":157}
},

:途中省略

"meta": {
  "app": "http://www.texturepacker.com",
  "version": "1.0",
  "image": "SpriteSheet.png",
  "format": "RGBA8888",
  "size": {"w":256,"h":512},
  "scale": "1",
  "smartupdate": "$TexturePacker:SmartUpdate:9e3e5afd01ea8e418afabfbdcd724485$"
}
}

ま、普通のjsonで難しいところは無いですね。CSSスプライトとあまり変わりません。
オブジェクトの名前とサイズ、どの領域をクリップするかが定義してあるだけです。

というわけで、先ほど作ったpngに対応したものを書いてみました。

text.json
{"frames": {
  "test_r1_c1.png":
  {
    "frame": {"x":0,"y":0,"w":120,"h":120},
    "rotated": false,
    "trimmed": false,
    "spriteSourceSize": {"x":0,"y":0,"w":120,"h":120},
    "sourceSize": {"w":120,"h":120}
  },
  "test_r1_c2.png":
  {
    "frame": {"x":120,"y":0,"w":120,"h":120},
    "rotated": false,
    "trimmed": false,
    "spriteSourceSize": {"x":0,"y":0,"w":120,"h":120},
    "sourceSize": {"w":120,"h":120}
  },
  "test_r1_c3.png":
  {
    "frame": {"x":240,"y":0,"w":120,"h":120},
    "rotated": false,
    "trimmed": false,
    "spriteSourceSize": {"x":0,"y":0,"w":120,"h":120},
    "sourceSize": {"w":120,"h":120}
  },
  "test_r1_c4.png":
  {
    "frame": {"x":360,"y":0,"w":120,"h":120},
    "rotated": false,
    "trimmed": false,
    "spriteSourceSize": {"x":0,"y":0,"w":120,"h":120},
    "sourceSize": {"w":120,"h":120}
  },
  "test_r2_c1.png":
  {
    "frame": {"x":0,"y":120,"w":120,"h":120},
    "rotated": false,
    "trimmed": false,
    "spriteSourceSize": {"x":0,"y":0,"w":120,"h":120},
    "sourceSize": {"w":120,"h":120}
  },
  "test_r2_c2.png":
  {
    "frame": {"x":120,"y":120,"w":120,"h":120},
    "rotated": false,
    "trimmed": false,
    "spriteSourceSize": {"x":0,"y":0,"w":120,"h":120},
    "sourceSize": {"w":120,"h":120}
  },
  "test_r2_c3.png":
  {
    "frame": {"x":240,"y":120,"w":120,"h":120},
    "rotated": false,
    "trimmed": false,
    "spriteSourceSize": {"x":0,"y":0,"w":120,"h":120},
    "sourceSize": {"w":120,"h":120}
  },
  "test_r2_c4.png":
  {
    "frame": {"x":360,"y":120,"w":120,"h":120},
    "rotated": false,
    "trimmed": false,
    "spriteSourceSize": {"x":0,"y":0,"w":120,"h":120},
    "sourceSize": {"w":120,"h":120}
  }
},
"meta": {
  "image": "test.png",
  "format": "RGBA8888",
  "size": {"w":480,"h":240},
  "scale": "1"
}
}

metaの中のTexturePackerしか使わなそうな要素は書いていません。
それでは、組み込んでみましょう。

スプライトシートを読み込んでみる

スプライトシートのpngとjsonが出来ましたので、最後にこれらをロードするjavascriptを書いてみます。

// ステージ・レンダラーを作ってDOMに配置
var stage = new PIXI.Stage(0x000000);
var renderer = PIXI.autoDetectRenderer(width, height);
document.getElementById("pixiview").appendChild(renderer.view);

// 画像からスプライトオブジェクトを作る
var framenames = [
  "test_r1_c1.png",
  "test_r1_c2.png",
  "test_r1_c3.png",
  "test_r1_c4.png",
  "test_r2_c1.png",
  "test_r2_c2.png",
  "test_r2_c3.png",
  "test_r2_c4.png"];
var sprites = [];

// Asset Loaderでスプライトシートをテクスチャフレームに読み込む
loader = new PIXI.AssetLoader(["img/test.json"]);
loader.onComplete = onComp; // 読み込みは非同期で行われます
loader.load(); // ロード開始!

// ロード後の処理 (非同期でコールバックされます)
function onComp()
{
  // テクスチャフレームからスプライトを作ってステージに並べます。
  for(var cnt=0;cnt < framenames.length;cnt ++){
    var name = framenames[cnt];
    sprites.push(PIXI.Sprite.fromFrame(name));

    : sprites[cnt]を配置する処理

    stage.addChild(sprites[cnt]);
  }
  // 次のアニメーションフレームでanimate()を呼び出してもらう
  requestAnimFrame(animate);
}

// アニメーション関数
function animate(){
  requestAnimFrame(animate); // 次の描画タイミングでanimateを呼び出す

  : アニメーション処理

  renderer.render(stage);   // 描画する
}
</script>

コードの解説

スプライトシートのロードには、AssetLoader()というメソッドを使います。
AssetLoader()のロード処理は非同期で行われますので、onCompleteにコールバック関数を登録します。

loader = new PIXI.AssetLoader(["img/test.json"]);
loader.onComplete = onComp; // 読み込みは非同期で行われます
loader.load(); // ロード開始!

AssetLoaderはスプライトシートから画像を読み込み、テキスチャをフレームと呼ぶリストに格納してから関数を呼び出してくれます。
onComplete()に登録した関数の中で、このテキスチャからスプライトを作りましょう。

フレームに格納されたテキスチャからスプライトを作るには、Sprite.fromFrame()を使います。
このメソッドにキーとして渡すのは、スプライトシートに登録した小さな画像それぞれにつけた名前です。

    var name = framenames[cnt]; // スプライト・シートに登録した画像の名前
    sprites.push(PIXI.Sprite.fromFrame(name));

このようにしてスプライトを作れば、あとはこれまで通りの方法でアニメーションが作れます。

サンプルは、こんな感じになりました。

おっと忘れてた。コンテナについて

今回は複数のオブジェクトを動かす、ということで、スプライトシートで話をしめようとしたのですが、大事なものを忘れてました。コンテナです。

コンテナって何?

コンテナは、複数のオブジェクトやスプライトをまとめて格納できる箱のようなものです。
↓コレです。

container
元画像:Wikipediaより

pixi.jsのコンテナの箱には色はついてませんw 透明です。
コンテナには、中にオブジェクトやスプライトを入れて、コンテナ自体にも動きを設定できますので、最終的なアニメーションは、 オブジェクトに設定した動き+コンテナに設定した動き といった感じにすることができます。

。。。やってみないと、よくわかりませんねw

よくわからないのでやってみる

さきほどスプライトシートのパートで作ったサンプルに2つのコンテナを追加してみます。
拡大するコンテナと縮小するコンテナです。
この2つのコンテナに、4つづつ、スプライトを入れることにします。
これで、 コンテナによる動きの追加以外は前のまま というサンプルができるはずです。

追加するコード

さっそく修正してみましょう。

// コンテナを2つつくる。(for文かよw)
var containers = [];
for (var i=0;i < 2; i++){
  containers[i] = new PIXI.DisplayObjectContainer();
  containers[i].position.x = 100+(i*300);
  containers[i].position.y = 50+(i*100);
  stage.addChild(containers[i]);
}

コンテナは、DisplayObjectContainer()メソッドで作ります。
stage.addChild()で、ステージに追加します。

次に、スプライトシートの読み込みが完了した際に呼び出されるonComp()を修正します。
これまでは、スプライトを直接stageにaddChild()していましたが、これをコンテナに対して行うように変更します。

// 前半と後半を分けてコンテナに入れる。
if(cnt < 4){
  containers[0].addChild(sprites[cnt]);
}else{
  containers[1].addChild(sprites[cnt]);
}

こんな感じになります。

最後に、アニメーション処理animate()の中で、コンテナに対して動きを付ければ完成です。

出来上がったサンプルとコードは以下のようになりました。

コンテナで動きを付けたサンプル

var width = 600;
var height = 400;

// ステージ・レンダラーを作ってDOMに配置
var stage = new PIXI.Stage(0x000000);
var renderer = PIXI.autoDetectRenderer(width, height);
document.getElementById("pixiview").appendChild(renderer.view);

// コンテナを2つつくる。(for文かよw)
var containers = [];
for (var i=0;i < 2; i++){
  containers[i] = new PIXI.DisplayObjectContainer();
  containers[i].position.x = 100+(i*300);
  containers[i].position.y = 50+(i*100);
  stage.addChild(containers[i]);
}

// 画像からスプライトオブジェクトを作る
var framenames = [
  "test_r1_c1.png",
  "test_r1_c2.png",
  "test_r1_c3.png",
  "test_r1_c4.png",
  "test_r2_c1.png",
  "test_r2_c2.png",
  "test_r2_c3.png",
  "test_r2_c4.png"];
var sprites = [];
var speed = [];

// Asset Loaderでスプライトシートをテクスチャフレームに読み込む
loader = new PIXI.AssetLoader(["img/test.json"]);
loader.onComplete = onComp; // 読み込みは非同期で行われます
loader.load(); // ロード開始!

// ロード後の処理 (非同期でコールバックされます)
function onComp()
{
  // テクスチャフレームからスプライトを作ってステージに並べます。
  for(var cnt=0;cnt < framenames.length;cnt ++){
    var name = framenames[cnt];
    sprites.push(PIXI.Sprite.fromFrame(name));
    sprites[cnt].position.x = Math.random() * 600;
    sprites[cnt].position.y = Math.random() * 400;
    sprites[cnt].anchor.x = 0.5;
    sprites[cnt].anchor.y = 0.5;

    // 前半と後半を分けてコンテナに入れる。
    if(cnt < 4){
      containers[0].addChild(sprites[cnt]);
    }else{
      containers[1].addChild(sprites[cnt]);
    }

  speed.push(Math.random());
  }

  // 次のアニメーションフレームでanimate()を呼び出してもらう
  requestAnimFrame(animate);
}

var sel1 = 0;
var sel2 = 1;
var counter = 0;

// アニメーション関数
function animate(){
  requestAnimFrame(animate); // 次の描画タイミングでanimateを呼び出す
  for(cnt=0;cnt <sprites.length;cnt++){
    sprites[cnt].rotation += (speed[cnt] / 10);
    sprites[cnt].position.x += (speed[cnt]*10);
    if(sprites[cnt].position.x > 700){
      sprites[cnt].position.x = -200;
      speed[cnt] = Math.random();
      sprites[cnt].position.y = Math.random() * 400;
    }
  }
  // containerを変形させる処理
  if(counter > 100){
    sel1 ^= 1;
    sel2 ^= 1;
    counter = 0;
  }
  containers[sel1].scale.x += 0.015;
  containers[sel1].scale.y += 0.005;
  containers[sel2].scale.x -= 0.015;
  containers[sel2].scale.y -= 0.005;
  renderer.render(stage);   // 描画する
  counter ++;
}

まとめ。次回は「パフォーマンスを測ってみる」

さて、いかがだったでしょうか。

今回は、前半完全なる個人的な宣伝から始まりw、後半スプライトシートとコンテナを触ってみました。
まだまだpixi.jsの機能のごく一部を触ってるだけですが、これだけでも、いろいろ動かせるようになってきました。

次回は、技術者の皆様が大好きな(?) パフォーマンス測定 をしてみたいと思います。

次回もよろしくお願いします!