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

第28回 物理演算エンジンBox2Dでボールを落とす

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

今回から3回ほどにわたって,物理計算にもとづくアニメーションをつくってみたい。物理空間に置かれたものが,落ちたり互いにぶつかり合ったりしたときの位置や速度はライブラリで計算する。今回使うのは,さまざまな言語に移植されていて定評のあるBox2Dという2次元の物理演算エンジンだ。つぎのjsdo.itのアニメーションをお題とする。

Box2dWebを使う

Box2DはもともとC++で開発された2次元の物理演算エンジンだ※1)。重力と物体の質量や摩擦,弾性にもとづく位置と動きの物理計算を行い,シミュレーションしてくれる。ActionScript 3.0やJava,C#,Pythonなど,さまざまな言語に移植されてきた。JavaScriptのライブラリとしても,いくつか公開されている。その中でも対応するバージョンが新しく,解説(英文)も整っているBox2dWebを使ってみたい。

まずは,インストールについて簡単に紹介しておこう。Box2dWebのサイトの左カラムに「Downloads」のリンクがある図1)。

図1 Box2dWebのサイト

図1 Box2dWebのサイト

ダウンロードしたZip圧縮ファイル(Box2dWeb-2.1a.3.zip)を展開すると,ふたつのJavaScript(JS)ファィルが入っている図2)。どちらかひとつのJSファイルをライブラリ用の適切なフォルダに納めればよい。サーバーに上げるにはmin(コンパクト)版が容量をくわない。実装を調べるときは通常版を用いるとよいだろう。

図2 ダウンロードして展開したBox2dWebのライブラリJSファィル

図2 ダウンロードして展開したBox2dWebのライブラリJSファィル

そして,HTMLドキュメントにscript要素で,ライブラリに置いたBox2dWeb(以下「Box2D」とする)のJSファイルを読込む。CreateJSからはEaselJSのほか,ボールに用いる画像を読込むためにPreloadJSも加える。

<script src="http://code.createjs.com/easeljs-0.7.1.min.js"></script>
<script src="http://code.createjs.com/preloadjs-0.4.1.min.js"></script>
<script src="lib/Box2dWeb-2.1.a.3.min.js"></script>

また,body要素のonLoad属性にJavaScriptコードの初期化の関数(initialize())を定めるのはいつもどおりだ。なお,Canvasの幅(400)と高さ(300)はいつもより多めにした。

<body onLoad="initialize()">
  <canvas id="myCanvas" width="400" height="300"></canvas>
</body>
※1
Box2Dを使った有名なアプリケーションとしては,少し古いがCrayon Physics Deluxeが挙げられる。日本語の記事では,クレヨン画が物理演算で動く『Crayon Physics』がかいつまんで紹介している。

ボールの画像をBitmapオブジェクトに読み込む

物理演算エンジンを初めて使うとき,まず知っておかなければならないのは,ライブラリは物体の位置や動きの数値計算をするだけで,何も動かないし見えないということだ。目に見えるオブジェクトは別につくっておいて,演算結果を与えることで,初めて物理シミュレーションが表現できる。Box2Dとオブジェクトとの間は,人形劇の黒子と人形のような関係だと捉えればよい。

先に,目に見える人形役からつくってしまおう。ボールのPNG画像ファイル(Pen.png)はアルファを抜いてフォルダ(images)に入れた図3)。画像ファイルの読み込み待ちには,PreloadJSを用いる。PreloadJSの具体的な使い方については,第2回「トゥイーンをランダムに定める」CreateJS新バージョンに対応した修正を加えるで解説したので,こちらをお読みいただきたい。

図3 ボールのPNGファイルはアルファを抜いてフォルダに納めておく

図3 ボールのPNGファイルはアルファを抜いてフォルダに納めておく

Box2Dでボールをひとつ落とすスクリプトは,後にコード1としてまとめた。その中で,PreloadJSで読込だ画像ファイルを,Bitmapインスタンスに与えてステージに置くまでが以下の抜書きだ。PreloadJSにより外部ファイルを読込む関数(preloadImage())は,初期設定の関数(initialize())から呼び出される。

画像ファイルのURLを引数に受取った関数(preloadImage())は,LoadQueueクラスでロードし始め,読込み終えたときのリスナー関数(loadFinished())を定める。リスナー関数は,画像の(Image)オブジェクトや半径などを変数(ballImageおよびimageRadius)に納めるとともに,Ticker.tickイベントにリスナーを加えた。そして,画像からつくったBitmapインスタンスをステージに置く関数(addBall())が呼び出される。

画像のオブジェクトをステージに置く関数(addBall())は,さらに別の関数(createVisualBall())でBitmapインスタンスをつくっている。物理シミュレーションでは物体の重心を座標の原点とするため,この関数でBitmapオブジェクトの基準点を中心に定めた。また,引数に渡す半径(radius)で,ボールの大きさが伸縮できるようにしてある。

処理の行数はさほどないのに関数が多いのは,後あと書き加えたいからだ。読込んだ画像からつくられたBitmapインスタンスは,とくに座標を与えられていないため,中心の基準点がステージの左上角に置かれる図4)。Bitmapオブジェクトは人形であって,位置は黒子のBox2Dが決めるので,このままにしておいて構わない。

図4 人形役のボールがデフォルトの左上角に置かれた

図4 人形役のボールがデフォルトの左上角に置かれた

var stage;

var ballImage;
var imageRadius;
function initialize() {
  var canvasElement = document.getElementById("myCanvas");

  stage = new createjs.Stage(canvasElement);

  createjs.Ticker.timingMode = createjs.Ticker.RAF;
  preloadImage("images/Pen.png");
}

function tick(eventObject) {

  stage.update();
}
function addBall() {
  var ball = createVisualBall(imageRadius);
  stage.addChild(ball);
}

function createVisualBall(radius) {
  var ball = new createjs.Bitmap(ballImage);
  ball.regX = ballImage.width / 2;
  ball.regY = ballImage.height / 2;
  ball.scaleX = ball.scaleY = radius / imageRadius;

  return ball;
}
function preloadImage(file) {
  var loader = new createjs.LoadQueue(false);
  loader.addEventListener("fileload", loadFinished);
  loader.loadFile(file);
}
function loadFinished(eventObject) {
  ballImage = eventObject.result;
  imageRadius = ballImage.width / 2;
  createjs.Ticker.addEventListener("tick", tick);
  addBall();
}

著者プロフィール

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

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

URLhttp://www.FumioNonaka.com/

著書

コメント

コメントの記入