|
CreateJS Workshop
|
【coming soon】
WebクリエイターのためのCreateJSスタイルブック
- 著者:野中文雄
- 定価:本体価格2,980円+税
- ISBN978-4-8399-4517-6
- 仕様:B5変型、344ページ(予定)
- 刊行:2013年6月下旬予定
*画像はイメージです。
|
EaselJSでBox2Dを使った物理シミュレーションを試す
先に、インストールについて簡単に紹介する。Box2dWebのサイトに「Downloads」のリンクがある(図001)。
図001■Box2dWebのサイト
ダウンロードしたZip圧縮ファイル(Box2dWeb-2.1a.3.zip)を展開すると、ふたつのJavaScript(JS)ファィルが入っている(図002)。どちらかひとつのJSファイルをライブラリ用の適切なフォルダに納めればよい。サーバーに上げるにはmin(コンパクト)版が少ない容量で済む。実装を調べるときは通常版を用いる。
図002■ダウンロードして展開したBox2Dライブラリのファィル
01 物理空間と剛体を定める
Box2Dのスクリプティングにかかる前に、物理演算シミュレーションで落とすオブジェクトをひとつCanvasに置く。矩形のShapeインスタンスを使うことにする。位置は定めないので、ステージの基準点である左上角に描かれる(図03-03-004)。後に全体を掲げるコード001から抜出したscript要素は以下のとおり。
図003■Canvas左上角に青い正方形のShapeオブジェクトを置く
- <script>
- var createjs = window;
- </script>
- <script src="easeljs/utils/UID.js"></script>
- <script src="easeljs/geom/Matrix2D.js"></script>
- <script src="easeljs/events/EventDispatcher.js"></script>
- <script src="easeljs/events/MouseEvent.js"></script>
- <script src="easeljs/display/DisplayObject.js"></script>
- <script src="easeljs/display/Container.js"></script>
- <script src="easeljs/display/Stage.js"></script>
- <script src="easeljs/display/Graphics.js"></script>
- <script src="easeljs/display/Shape.js"></script>
- <script>
- var stage;
- var boxEdge = 20;
- function initialize() {
- var canvasElement = document.getElementById("myCanvas");
- stage = new Stage(canvasElement);
- initializeBox2D(canvasElement.width);
- stage.update();
- }
- function initializeBox2D(nWidth) {
- var myShape = createShape(bodyDef, boxEdge, boxEdge, "#0000FF");
- stage.addChild(myShape);
- }
- function createShape(bodyDef, nWidth, nHeight, nColor) {
- var myShape = new Shape();
- draw(myShape.graphics, nWidth, nHeight, nColor);
- myShape.regX = nWidth / 2;
- myShape.regY = nHeight / 2;
- return myShape;
- }
- function draw(myGraphics, nWidth, nHeight, nColor) {
- myGraphics.beginFill(nColor);
- myGraphics.drawRect(0, 0, nWidth, nHeight);
- }
- </script>
|
物理演算は計算なので、結果は数値。数値はステージに描いたオブジェクトのプロパティに当てはめて、初めてオブジェクトの動きとして目に見える。ステージのオブジェクトとBox2Dとの間は、人形と黒子のような関係になる。
Box2dWebライブラリのJavaScript(JS)ファイルBox2dWeb-2.1.a.3.min.jsを、script要素で読込んでおく(ファイルを納めたフォルダはlibとした)。
- <script src="lib/Box2dWeb-2.1.a.3.min.js"></script>
|
物理演算を行うにために、まずは3つの仕込みを行う。
【物理演算シミュレーションの準備】
- 物理空間をつくる
- 剛体を定義する
- 剛体定義に表示オブジェクトを関連づける
01-01 物理空間をつくる
物理空間は、Box2Dのb2Worldクラスのオブジェクトとしてつくる。コンストラクタの第1引数に渡す重力は、2次元のベクトルを表すb2Vec2オブジェクトで定める。第2引数のスリープはブール(論理)値で、trueを渡すと動かなくなった剛体はシミュレーションから外して(スリープして)効率を高める。
var world = new Box2D.Dynamics.b2World(重力, スリープ);
Box2dWebは、数多くのクラスをJavaScriptファイルでは分けず、代わりに名前空間により分類・識別する。ひとつの関数の中で同じクラスを何度も用いる場合には、いちいち名前空間から書上げるのは煩わしく、コードも見にくい。そういうときは、クラスを予め変数に入れて参照すると便利だ。
var b2World = Box2D.Dynamics.b2World;
var world = new b2World(重力, スリープ);
重力は通常垂直(y軸)方向の力なので、b2Vec2()コンストラクタの第1引数に渡すx座標値は0にした(第30行目)。また、垂直方向のy座標値は、変数(gravityVertical)に定めている(第18行目)。生成したb2Worldインスタンスは、変数(world)に納めた(第17および第30行目)。
- var world;
- var gravityVertical = 10;
- function initialize() {
- initializeBox2D(canvasElement.width);
- }
- function initializeBox2D(nWidth) {
- var b2Vec2 = Box2D.Common.Math.b2Vec2;
- var b2World = Box2D.Dynamics.b2World;
- world = new b2World(new b2Vec2(0, gravityVertical), true);
- }
|
01-02 剛体を定義する
後で落下させる予定の剛体は、1辺20ピクセルの正方形とし、その長さを変数(boxEdge)に置く(第19行目)。剛体を定義する関数(defineBody())は別に定めた(第35〜41行目)。関数の第1および第2引数(nXとnY)は、剛体のxy座標だ。第3引数(nType)は剛体の種類で、b2Bodyクラスの定数(静的プロパティ)として備わる整数で定める(表001)。
表001■剛体の種類を定めるb2Body定数
マウスイベント |
剛体の種類 |
値 |
b2_dynamicBody
|
動的
|
2
|
b2_kinematicBody
|
キネマティック
|
1
|
b2_staticBody
|
静的
|
0
|
剛体を定義する関数(defineBody())は、b2BodyDef()コンストラクタでインスタンスをつくったら、剛体の位置と種類を決める(第36〜39行目)。位置はb2BodyDef.positionプロパティでb2Vec2オブジェクトを参照し、b2Vec2.Set()メソッドにより座標を決める。剛体の種類はb2BodyDef.typeプロパティに、b2Body.b2_dynamicBodyを定めた。関数は、定義の済んだb2BodyDefインスタンスを返す(第40行目)。
- var SCALE = 1 / 30;
- var boxEdge = 20;
- function initialize() {
- var canvasElement = document.getElementById("myCanvas");
- initializeBox2D(canvasElement.width);
- }
- function initializeBox2D(nWidth) {
- var b2Body = Box2D.Dynamics.b2Body;
- var bodyDef = defineBody(nWidth / 2, boxEdge, b2Body.b2_dynamicBody);
- }
- function defineBody(nX , nY, nType) {
- var b2BodyDef = Box2D.Dynamics.b2BodyDef;
- var bodyDef = new b2BodyDef();
- bodyDef.position.Set(nX * SCALE, nY * SCALE);
- bodyDef.type = nType;
- return bodyDef;
- }
|
なお、Box2Dは物理学と同じメートル(m)・キログラム(kg)・秒の単位にもとづいてシミュレーションする。そこで、ピクセルからメートルへの換算比率(メートル/ピクセル)を定数(SCALE)にした(上記コード第16行目)。したがって、ピクセルで与えられた数値をBox2Dの座標として渡すときには、この定数値を乗じる(第38行目)。
01-03 剛体定義に表示オブジェクトを関連づける
ここまでの物理空間と剛体の定義は、Box2Dの仕込だった。ステージ上で動かそうとしているオブジェクトは、まだ蚊帳の外だ。そこで、Shapeオブジェクトをつくる関数(createShape())に、剛体定義とのつながりを加える。関数の第1引数には剛体定義のb2BodyDefオブジェクトが渡される(第32および第42行目)。そのb2BodyDef.userDataプロパティにShapeオブジェクト(myShape)を与えた(第47行目)。
- function initialize() {
- initializeBox2D(canvasElement.width);
- }
- function initializeBox2D(nWidth) {
- var bodyDef = defineBody(nWidth / 2, boxEdge, b2Body.b2_dynamicBody);
- var myShape = createShape(bodyDef, boxEdge, boxEdge, "#0000FF");
- }
- function defineBody(nX , nY, nType) {
- var b2BodyDef = Box2D.Dynamics.b2BodyDef;
- var bodyDef = new b2BodyDef();
- return bodyDef;
- }
- function createShape(bodyDef, nWidth, nHeight, nColor) {
- bodyDef.userData = myShape;
- return myShape;
- }
|
3つの準備を整えたscript要素全体は、つぎのコード03-03-001のとおりだ。もっとも、まだ物理演算のシミュレーションは行っていないので、青い正方形のShapeインスタンスがCanvasの左上角に表れるだけだ(前掲図003)。
コード001■Box2Dの物理空間に剛体を定義する
- <script>
- var createjs = window;
- </script>
- <script src="lib/Box2dWeb-2.1.a.3.min.js"></script>
- <script src="easeljs/utils/UID.js"></script>
- <script src="easeljs/geom/Matrix2D.js"></script>
- <script src="easeljs/events/EventDispatcher.js"></script>
- <script src="easeljs/events/MouseEvent.js"></script>
- <script src="easeljs/display/DisplayObject.js"></script>
- <script src="easeljs/display/Container.js"></script>
- <script src="easeljs/display/Stage.js"></script>
- <script src="easeljs/display/Graphics.js"></script>
- <script src="easeljs/display/Shape.js"></script>
- <script>
- var stage;
- var SCALE = 1 / 30;
- var world;
- var gravityVertical = 10;
- var boxEdge = 20;
- function initialize() {
- var canvasElement = document.getElementById("myCanvas");
- stage = new Stage(canvasElement);
- initializeBox2D(canvasElement.width);
- stage.update();
- }
- function initializeBox2D(nWidth) {
- var b2Vec2 = Box2D.Common.Math.b2Vec2;
- var b2Body = Box2D.Dynamics.b2Body;
- var b2World = Box2D.Dynamics.b2World;
- world = new b2World(new b2Vec2(0, gravityVertical), true);
- var bodyDef = defineBody(nWidth / 2, boxEdge, b2Body.b2_dynamicBody);
- var myShape = createShape(bodyDef, boxEdge, boxEdge, "#0000FF");
- stage.addChild(myShape);
- }
- function defineBody(nX , nY, nType) {
- var b2BodyDef = Box2D.Dynamics.b2BodyDef;
- var bodyDef = new b2BodyDef();
- bodyDef.position.Set(nX * SCALE, nY * SCALE);
- bodyDef.type = nType;
- return bodyDef;
- }
- function createShape(bodyDef, nWidth, nHeight, nColor) {
- var myShape = new Shape();
- draw(myShape.graphics, nWidth, nHeight, nColor);
- myShape.regX = nWidth / 2;
- myShape.regY = nHeight / 2;
- bodyDef.userData = myShape;
- return myShape;
- }
- function draw(myGraphics, nWidth, nHeight, nColor) {
- myGraphics.beginFill(nColor);
- myGraphics.drawRect(0, 0, nWidth, nHeight);
- }
- </script>
|
02 剛体を落下させる − 物理演算シミュレーションの実行
Box2Dで剛体の物理演算をするには、まず剛体定義のb2BodyDefオブジェクトをb2World.CreateBody()メソッドの引数に渡して、物理空間に剛体(Bodyオブジェクト)をつくらなければならない。メソッドからは、剛体定義にもとづいたBodyオブジェクトが返される。
b2Worldオブジェクト.CreateBody(b2BodyDefオブジェクト)
これから手を加えるJavaScriptのステートメントは、後にコード002として全体を掲げる。b2World.CreateBody()メソッドは、新たに定める関数(createBody())から呼出す(第61行目)。そして、この関数の呼出しは、Box2Dを初期設定する関数(initializeBox2D())に加える(38行目)。引数にはb2Worldとb2BodyDefのオブジェクト(worldとbodyDef)を渡す。
つぎに、物理シミュレートするため、物理空間のb2Worldオブジェクトに対して時間を進めるメソッドが、b2World.Step()だ。第1引数には、シミュレーションのために進める時間を秒数で渡す。第2および第3引数は、物理的な制約に合わせて調整の再計算をさせる回数だ。前者は速度、後者が位置についての演算を定める。
b2Worldオブジェクト.Step(経過秒数, 速度再計算, 位置再計算)
人形のオブジェクトはTickerクラスで動かす(第14および第29行目)。リスナー関数(update())から、b2World.Step()メソッドを呼出す(第63〜64行目)。物理シミュレーションで進める秒数は予め変数(time)にとり(第22行目)、b2World.Step()メソッドの第1引数に渡すとともに、Ticker.setInterval()メソッド(前掲Syntax 02-01-005)でTicker.tickイベントの間隔も合わせた(第28行目)。
物理演算の時間を進めるたら、剛体の幾何情報を人形のオブジェクトに伝える。物理空間に加えられた剛体のうち初めのb2Bodyオブジェクトを取出すのが、b2World.GetBodyList()メソッドだ。剛体の位置は、b2Body.GetPosition()メソッドでb2Vec2オブジェクトとして得られる。また、その回転角は、b2Body.GetAngle()メソッドによりラジアン角が返される。
なお、剛体定義のb2BodyDef.userDataプロパティに与えた人形のオブジェクトの参照は、定義からつくられた剛体のオブジェクトにb2Body.GetUserData()メソッドを呼出すと得られる。また、DisplayObject.rotationプロパティの単位は度数なので、ラジアン角はMatrix2D.DEG_TO_RAD定数で除す。
- <script src="easeljs/utils/Ticker.js"></script>
- <script>
- var velocityIterations = 8;
- var positionIterations = 3;
- var time = 1 / 24;
- function initialize() {
- initializeBox2D(canvasElement.width);
// stage.update();
- Ticker.setInterval(time * 1000);
- Ticker.addEventListener("tick", update);
- }
- function initializeBox2D(nWidth) {
- world = new b2World(new b2Vec2(0, gravityVertical), true);
- var bodyDef = defineBody(nWidth / 2, boxEdge, b2Body.b2_dynamicBody);
- createBody(world, bodyDef);
- }
- function createBody(world, bodyDef) {
- var body = world.CreateBody(bodyDef);
- }
- function update() {
- world.Step(time, velocityIterations, positionIterations);
- var body = world.GetBodyList();
- var myObject = body.GetUserData();
- if (myObject) {
- var position = body.GetPosition();
- myObject.x = position.x / SCALE;
- myObject.y = position.y / SCALE;
- myObject.rotation = body.GetAngle() / Matrix2D.DEG_TO_RAD;
- stage.update();
- }
- }
- </script>
|
これでBox2Dの物理演算シミュレーションにもとづいて、インスタンスが重力により自由落下する(図004)。ここまでの書替えをすべて加えたscript要素の全体は、つぎのコード002のとおりだ。
図004■正方形のインスタンスが自由落下する
コード002■オブジェクトをBox2Dの物理シミュレーションに合わせて落下させる
- <script>
- var createjs = window;
- </script>
- <script src="lib/Box2dWeb-2.1.a.3.min.js"></script>
- <script src="easeljs/utils/UID.js"></script>
- <script src="easeljs/geom/Matrix2D.js"></script>
- <script src="easeljs/events/EventDispatcher.js"></script>
- <script src="easeljs/events/MouseEvent.js"></script>
- <script src="easeljs/display/DisplayObject.js"></script>
- <script src="easeljs/display/Container.js"></script>
- <script src="easeljs/display/Stage.js"></script>
- <script src="easeljs/display/Graphics.js"></script>
- <script src="easeljs/display/Shape.js"></script>
- <script src="easeljs/utils/Ticker.js"></script>
- <script>
- var stage;
- var SCALE = 1 / 30;
- var world;
- var gravityVertical = 10;
- var velocityIterations = 8;
- var positionIterations = 3;
- var time = 1 / 24;
- var boxEdge = 20;
- function initialize() {
- var canvasElement = document.getElementById("myCanvas");
- stage = new Stage(canvasElement);
- initializeBox2D(canvasElement.width);
- Ticker.setInterval(time * 1000);
- Ticker.addEventListener("tick", update);
- }
- function initializeBox2D(nWidth) {
- var b2Vec2 = Box2D.Common.Math.b2Vec2;
- var b2Body = Box2D.Dynamics.b2Body;
- var b2World = Box2D.Dynamics.b2World;
- world = new b2World(new b2Vec2(0, gravityVertical), true);
- var bodyDef = defineBody(nWidth / 2, boxEdge, b2Body.b2_dynamicBody);
- var myShape = createShape(bodyDef, boxEdge, boxEdge, "#0000FF");
- createBody(world, bodyDef);
- stage.addChild(myShape);
- }
- function defineBody(nX , nY, nType) {
- var b2BodyDef = Box2D.Dynamics.b2BodyDef;
- var bodyDef = new b2BodyDef();
- bodyDef.position.Set(nX * SCALE, nY * SCALE);
- bodyDef.type = nType;
- return bodyDef;
- }
- function createShape(bodyDef, nWidth, nHeight, nColor) {
- var myShape = new Shape();
- draw(myShape.graphics, nWidth, nHeight, nColor);
- myShape.regX = nWidth / 2;
- myShape.regY = nHeight / 2;
- bodyDef.userData = myShape;
- return myShape;
- }
- function draw(myGraphics, nWidth, nHeight, nColor) {
- myGraphics.beginFill(nColor);
- myGraphics.drawRect(0, 0, nWidth, nHeight);
- }
- function createBody(world, bodyDef) {
- var body = world.CreateBody(bodyDef);
- }
- function update() {
- world.Step(time, velocityIterations, positionIterations);
- var body = world.GetBodyList();
- var myObject = body.GetUserData();
- if (myObject) {
- var position = body.GetPosition();
- myObject.x = position.x / SCALE;
- myObject.y = position.y / SCALE;
- myObject.rotation = body.GetAngle() / Matrix2D.DEG_TO_RAD;
- stage.update();
- }
- }
- </script>
|
03 静的な剛体を加える − 衝突のシミュレーション
ステージの下端に床を静的な剛体として加える。b2BodyDef.typeプロパティに与える定数はb2Body.b2_staticBodyだ(前掲表001「剛体の種類を定めるb2Body定数」参照)。床の静的な剛体と落下させる動的な剛体は、それぞれ別の関数(createStaticFloor()とcreateDynamicBox())に分けてつくる。ともに剛体の定義(defineBody())と作成(createBody())、およびShapeオブジェクト作成(createShape)の3つの関数を呼出す。今のところ、つくられる剛体の種類が静的か動的かというほかに違いはない。
- function initialize() {
- var canvasElement = document.getElementById("myCanvas");
// initializeBox2D(canvasElement.width);
- initializeBox2D(canvasElement.width, canvasElement.height);
- }
// function initializeBox2D(nWidth) {
- function initializeBox2D(stageWidth, stageHeight) {
// var bodyDef = defineBody(nWidth / 2, boxEdge, b2Body.b2_dynamicBody);
// var myShape = createShape(bodyDef, boxEdge, boxEdge, "#0000FF");
// createBody(world, bodyDef);
- var centerX = stageWidth / 2;
- var floorWidth = stageWidth * 0.8;
- var floorHeight = boxEdge;
- var floorShape = createStaticFloor(centerX, stageHeight - floorHeight, floorWidth, floorHeight, "#CCCCCC");
- stage.addChild(floorShape);
- var myShape = createDynamicBox(centerX, boxEdge, boxEdge, boxEdge, "#0000FF");
- stage.addChild(myShape);
- }
- function createStaticFloor(nX, nY, nWidth, nHeight, nColor) {
- var b2Body = Box2D.Dynamics.b2Body;
- var bodyDef = defineBody(nX, nY, b2Body.b2_staticBody);
- var myShape = createShape(bodyDef, nWidth, nHeight, nColor);
- createBody(world, bodyDef);
- return myShape;
- }
- function createDynamicBox(nX, nY, nWidth, nHeight, nColor) {
- var b2Body = Box2D.Dynamics.b2Body;
- var bodyDef = defineBody(nX, nY, b2Body.b2_dynamicBody);
- var myShape = createShape(bodyDef, nWidth, nHeight, nColor);
- createBody(world, bodyDef);
- return myShape;
- }
|
物理空間の中の初めのb2Bodyオブジェクトは、b2World.GetBodyList()メソッドから参照を得た。その後、物理空間から順につぎの剛体を取出すには、b2Bodyオブジェクトに対してb2Body.GetNext()メソッド(前掲03-03-001-2)を呼出す。
そこで、Ticker.tickイベントのリスナー関数(update())で複数の剛体が扱えるように、while文を加えて繰返し処理するように書替えた(第67〜76行目)。ループの終わりにb2Body.GetNext()メソッドでつぎの剛体を取出し、オブジェクトがあればシミュレーションを続ける。
- function update() {
- world.Step(time, velocityIterations, positionIterations);
- var body = world.GetBodyList();
- while (body) {
- var myObject = body.GetUserData();
- if (myObject) {
- var position = body.GetPosition();
- myObject.x = position.x / SCALE;
- myObject.y = position.y / SCALE;
- myObject.rotation = body.GetAngle() / Matrix2D.DEG_TO_RAD;
// stage.update();
- }
- body = body.GetNext();
- }
- stage.update();
- }
|
なお、矩形と床の剛体はTicker.tickイベントごとにまとめて描画すればよい。そのため、Stage.update()メソッドの呼出しは、whileループの外に出した(第77行目)。
ここまでの手を加えたJavvaScriptコードが、以下のコード003だ。試してみると、落下したオブジェクトが床を抜けるイリュージョンになってしまう(図005)。
図005■落下するオブジェクトが床をすり抜ける
コード003■落下する剛体と静的な剛体の物理シミュレーション
- var stage;
- var SCALE = 1 / 30;
- var world;
- var gravityVertical = 10;
- var velocityIterations = 8;
- var positionIterations = 3;
- var time = 1 / 24;
- var boxEdge = 20;
- function initialize() {
- var canvasElement = document.getElementById("myCanvas");
- stage = new Stage(canvasElement);
- initializeBox2D(canvasElement.width, canvasElement.height);
- Ticker.setInterval(time * 1000);
- Ticker.addEventListener("tick", update);
- }
- function initializeBox2D(stageWidth, stageHeight) {
- var b2Vec2 = Box2D.Common.Math.b2Vec2;
- var b2World = Box2D.Dynamics.b2World;
- world = new b2World(new b2Vec2(0, gravityVertical), true);
- var centerX = stageWidth / 2;
- var floorWidth = stageWidth * 0.8;
- var floorHeight = boxEdge;
- var floorShape = createStaticFloor(centerX, stageHeight - floorHeight, floorWidth, floorHeight, "#CCCCCC");
- stage.addChild(floorShape);
- var myShape = createDynamicBox(centerX, boxEdge, boxEdge, boxEdge, "#0000FF");
- stage.addChild(myShape);
- }
- function createStaticFloor(nX, nY, nWidth, nHeight, nColor) {
- var b2Body = Box2D.Dynamics.b2Body;
- var bodyDef = defineBody(nX, nY, b2Body.b2_staticBody);
- var myShape = createShape(bodyDef, nWidth, nHeight, nColor);
- createBody(world, bodyDef);
- return myShape;
- }
- function createDynamicBox(nX, nY, nWidth, nHeight, nColor) {
- var b2Body = Box2D.Dynamics.b2Body;
- var bodyDef = defineBody(nX, nY, b2Body.b2_dynamicBody);
- var myShape = createShape(bodyDef, nWidth, nHeight, nColor);
- createBody(world, bodyDef);
- return myShape;
- }
- function defineBody(nX , nY, nType) {
- var b2BodyDef = Box2D.Dynamics.b2BodyDef;
- var bodyDef = new b2BodyDef();
- bodyDef.position.Set(nX * SCALE, nY * SCALE);
- bodyDef.type = nType;
- return bodyDef;
- }
- function createShape(bodyDef, nWidth, nHeight, nColor) {
- var myShape = new Shape();
- draw(myShape.graphics, nWidth, nHeight, nColor);
- myShape.regX = nWidth / 2;
- myShape.regY = nHeight / 2;
- bodyDef.userData = myShape;
- return myShape;
- }
- function draw(myGraphics, nWidth, nHeight, nColor) {
- myGraphics.beginFill(nColor);
- myGraphics.drawRect(0, 0, nWidth, nHeight);
- }
- function createBody(world, bodyDef) {
- var body = world.CreateBody(bodyDef);
- }
- function update() {
- world.Step(time, velocityIterations, positionIterations);
- var body = world.GetBodyList();
- while (body) {
- var myObject = body.GetUserData();
- if (myObject) {
- var position = body.GetPosition();
- myObject.x = position.x / SCALE;
- myObject.y = position.y / SCALE;
- myObject.rotation = body.GetAngle() / Matrix2D.DEG_TO_RAD;
- }
- body = body.GetNext();
- }
- stage.update();
- }
|
04 物理シミュレーションにフィクスチャを加える
私たちが「矩形」とか「床」とわかるのは、Shapeインスタンスのかたちを見ているからだ。しかし、黒子のBox2Dには、剛体定義の関数(defineBody())で座標と動くか動かないかしか伝えていない。剛体のかたちや大きさは、フィクスチャというオブジェクトで定めなければならない。
剛体にフィクスチャを加えたコード03-03-004は後にまとめて掲げる。フィクスチャの定義をb2FixtureDefオブジェクトでつくり、b2FixtureDef.shapeプロパティにかたちを与える。かたちを定めるのはBox2Dのシェイブ(b2Shape)オブジェクトだ。矩形はb2PolygonShapeオブジェクトにb2PolygonShape.SetAsBox()メソッドで幅と高さを引数に渡してつくる。
剛体をつくる関数(createBody())の引数には、b2FixtureDefオブジェクトを加えた(第77行目)。剛体にフィクスチャを与えるのはb2Body.CreateFixture()メソッドで、b2FixtureDefオブジェクトを引数に渡す(第79行目)。
- function createStaticFloor(nX, nY, nWidth, nHeight, nColor) {
- var boxShape = createBoxShape(nWidth / 2, nHeight / 2);
- var fixtureDef = defineFixture(boxShape);
// createBody(world, bodyDef);
- createBody(world, bodyDef, fixtureDef);
- }
- function createDynamicBox(nX, nY, nWidth, nHeight, nColor) {
- var boxShape = createBoxShape(nWidth / 2, nHeight / 2);
- var fixtureDef = defineFixture(boxShape);
// createBody(world, bodyDef);
- createBody(world, bodyDef, fixtureDef);
- }
- function defineFixture(myShape) {
- var b2FixtureDef = Box2D.Dynamics.b2FixtureDef;
- var fixtureDef = new b2FixtureDef();
- fixtureDef.shape = myShape;
- return fixtureDef;
- }
- function createBoxShape(nX, nY) {
- var b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape;
- var myPolygonShape = new b2PolygonShape();
- myPolygonShape.SetAsBox(nX * SCALE, nY * SCALE);
- return myPolygonShape;
- }
// function createBody(world, bodyDef) {
- function createBody(world, bodyDef, fixtureDef) {
- var body = world.CreateBody(bodyDef);
- body.CreateFixture(fixtureDef);
- }
|
これでBox2Dにふたつの剛体のかたちは伝わり、互いの衝突がわかるということです。JavaScriptコード全体は、以下のコード004のとおりだ。けれど、矩形は床に落ちても弾みもせず、まるで完璧な体操の着地のごとく、床に吸いつくように止まる(図006)。
図006■落下したオブジェクトは床の上に吸いつくように着地する
コード004■剛体のかたちをフィクスチャで定めた物理シミュレーション
- var stage;
- var SCALE = 1 / 30;
- var world;
- var gravityVertical = 10;
- var velocityIterations = 8;
- var positionIterations = 3;
- var time = 1 / 24;
- var boxEdge = 20;
- function initialize() {
- var canvasElement = document.getElementById("myCanvas");
- stage = new Stage(canvasElement);
- initializeBox2D(canvasElement.width, canvasElement.height);
- Ticker.setInterval(time * 1000);
- Ticker.addEventListener("tick", update);
- }
- function initializeBox2D(stageWidth, stageHeight) {
- var b2Vec2 = Box2D.Common.Math.b2Vec2;
- var b2World = Box2D.Dynamics.b2World;
- world = new b2World(new b2Vec2(0, gravityVertical), true);
- var centerX = stageWidth / 2;
- var floorWidth = stageWidth * 0.8;
- var floorHeight = boxEdge;
- var floorShape = createStaticFloor(centerX, stageHeight - floorHeight, floorWidth, floorHeight, "#CCCCCC");
- stage.addChild(floorShape);
- var myShape = createDynamicBox(centerX, boxEdge, boxEdge, boxEdge, "#0000FF");
- stage.addChild(myShape);
- }
- function createStaticFloor(nX, nY, nWidth, nHeight, nColor) {
- var b2Body = Box2D.Dynamics.b2Body;
- var bodyDef = defineBody(nX, nY, b2Body.b2_staticBody);
- var myShape = createShape(bodyDef, nWidth, nHeight, nColor);
- var boxShape = createBoxShape(nWidth / 2, nHeight / 2);
- var fixtureDef = defineFixture(boxShape);
- createBody(world, bodyDef, fixtureDef);
- return myShape;
- }
- function createDynamicBox(nX, nY, nWidth, nHeight, nColor) {
- var b2Body = Box2D.Dynamics.b2Body;
- var bodyDef = defineBody(nX, nY, b2Body.b2_dynamicBody);
- var myShape = createShape(bodyDef, nWidth, nHeight, nColor);
- var boxShape = createBoxShape(nWidth / 2, nHeight / 2);
- var fixtureDef = defineFixture(boxShape);
- createBody(world, bodyDef, fixtureDef);
- return myShape;
- }
- function defineBody(nX , nY, nType) {
- var b2BodyDef = Box2D.Dynamics.b2BodyDef;
- var bodyDef = new b2BodyDef();
- bodyDef.position.Set(nX * SCALE, nY * SCALE);
- bodyDef.type = nType;
- return bodyDef;
- }
- function createShape(bodyDef, nWidth, nHeight, nColor) {
- var myShape = new Shape();
- draw(myShape.graphics, nWidth, nHeight, nColor);
- myShape.regX = nWidth / 2;
- myShape.regY = nHeight / 2;
- bodyDef.userData = myShape;
- return myShape;
- }
- function draw(myGraphics, nWidth, nHeight, nColor) {
- myGraphics.beginFill(nColor);
- myGraphics.drawRect(0, 0, nWidth, nHeight);
- }
- function defineFixture(myShape) {
- var b2FixtureDef = Box2D.Dynamics.b2FixtureDef;
- var fixtureDef = new b2FixtureDef();
- fixtureDef.shape = myShape;
- return fixtureDef;
- }
- function createBoxShape(nX, nY) {
- var b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape;
- var myPolygonShape = new b2PolygonShape();
- myPolygonShape.SetAsBox(nX * SCALE, nY * SCALE);
- return myPolygonShape;
- }
- function createBody(world, bodyDef, fixtureDef) {
- var body = world.CreateBody(bodyDef);
- body.CreateFixture(fixtureDef);
- }
- function update() {
- world.Step(time, velocityIterations, positionIterations);
- var body = world.GetBodyList();
- while (body) {
- var myObject = body.GetUserData();
- if (myObject) {
- var position = body.GetPosition();
- myObject.x = position.x / SCALE;
- myObject.y = position.y / SCALE;
- myObject.rotation = body.GetAngle() / Matrix2D.DEG_TO_RAD;
- }
- body = body.GetNext();
- }
- stage.update();
- }
|
前掲コード004では、落とした剛体が弾むような材質であることを、まだBox2Dに教えていない。フィクスチャの定義には、つぎの表002のように密度と摩擦および弾性の3つの性質がプロパティで定められる。
表002■フィクスチャを定義するb2FixtureDefオブジェクトに定めるプロパティと値
フィスクチャに質感を加えよう。JavaScriptコード全体は後にコード03-03-005として掲げる。つぎのように、密度と摩擦および弾性の3つを新たな関数(setFixtureDef())で定め(第81〜85行目)、落下する剛体をつくる関数(createDynamicBox())から呼出した(第46行目)。
- var density = 1;
- var friction = 0.1;
- var restitution = 0.5;
- function createDynamicBox(nX, nY, nWidth, nHeight, nColor) {
- var fixtureDef = defineFixture(boxShape);
- setFixtureDef(fixtureDef, density, friction, restitution);
- }
- function setFixtureDef(fixtureDef, density, friction, restitution) {
- fixtureDef.density = density;
- fixtureDef.friction = friction;
- fixtureDef.restitution = restitution;
- }
|
これで、動的な剛体に弾力などの質感が加わった。落ちたオブジェクトは床で軽く何度か弾んだ後、やがて止まる(図007)。できあがったJavaScriptコード全体は、以下のコード005のとおりだ。
図007■然落下したオブジェクトが床で弾む
コード005■動的な剛体を静的な剛体の上に落下させる物理シミュレーション
- var stage;
- var SCALE = 1 / 30;
- var world;
- var gravityVertical = 10;
- var velocityIterations = 8;
- var positionIterations = 3;
- var time = 1 / 24;
- var boxEdge = 20;
- var density = 1;
- var friction = 0.1;
- var restitution = 0.5;
- function initialize() {
- var canvasElement = document.getElementById("myCanvas");
- stage = new Stage(canvasElement);
- initializeBox2D(canvasElement.width, canvasElement.height);
- Ticker.setInterval(time * 1000);
- Ticker.addEventListener("tick", update);
- }
- function initializeBox2D(stageWidth, stageHeight) {
- var b2Vec2 = Box2D.Common.Math.b2Vec2;
- var b2World = Box2D.Dynamics.b2World;
- world = new b2World(new b2Vec2(0, gravityVertical), true);
- var centerX = stageWidth / 2;
- var floorWidth = stageWidth * 0.8;
- var floorHeight = boxEdge;
- var floorShape = createStaticFloor(centerX, stageHeight - floorHeight, floorWidth, floorHeight, "#CCCCCC");
- stage.addChild(floorShape);
- var myShape = createDynamicBox(centerX, boxEdge, boxEdge, boxEdge, "#0000FF");
- stage.addChild(myShape);
- }
- function createStaticFloor(nX, nY, nWidth, nHeight, nColor) {
- var b2Body = Box2D.Dynamics.b2Body;
- var bodyDef = defineBody(nX, nY, b2Body.b2_staticBody);
- var myShape = createShape(bodyDef, nWidth, nHeight, nColor);
- var boxShape = createBoxShape(nWidth / 2, nHeight / 2);
- var fixtureDef = defineFixture(boxShape);
- createBody(world, bodyDef, fixtureDef);
- return myShape;
- }
- function createDynamicBox(nX, nY, nWidth, nHeight, nColor) {
- var b2Body = Box2D.Dynamics.b2Body;
- var bodyDef = defineBody(nX, nY, b2Body.b2_dynamicBody);
- var myShape = createShape(bodyDef, nWidth, nHeight, nColor);
- var boxShape = createBoxShape(nWidth / 2, nHeight / 2);
- var fixtureDef = defineFixture(boxShape);
- setFixtureDef(fixtureDef, density, friction, restitution);
- createBody(world, bodyDef, fixtureDef);
- return myShape;
- }
- function defineBody(nX , nY, nType) {
- var b2BodyDef = Box2D.Dynamics.b2BodyDef;
- var bodyDef = new b2BodyDef();
- bodyDef.position.Set(nX * SCALE, nY * SCALE);
- bodyDef.type = nType;
- return bodyDef;
- }
- function createShape(bodyDef, nWidth, nHeight, nColor) {
- var myShape = new Shape();
- draw(myShape.graphics, nWidth, nHeight, nColor);
- myShape.regX = nWidth / 2;
- myShape.regY = nHeight / 2;
- bodyDef.userData = myShape;
- return myShape;
- }
- function draw(myGraphics, nWidth, nHeight, nColor) {
- myGraphics.beginFill(nColor);
- myGraphics.drawRect(0, 0, nWidth, nHeight);
- }
- function defineFixture(myShape) {
- var b2FixtureDef = Box2D.Dynamics.b2FixtureDef;
- var fixtureDef = new b2FixtureDef();
- fixtureDef.shape = myShape;
- return fixtureDef;
- }
- function createBoxShape(nX, nY) {
- var b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape;
- var myPolygonShape = new b2PolygonShape();
- myPolygonShape.SetAsBox(nX * SCALE, nY * SCALE);
- return myPolygonShape;
- }
- function setFixtureDef(fixtureDef, density, friction, restitution) {
- fixtureDef.density = density;
- fixtureDef.friction = friction;
- fixtureDef.restitution = restitution;
- }
- function createBody(world, bodyDef, fixtureDef) {
- var body = world.CreateBody(bodyDef);
- body.CreateFixture(fixtureDef);
- }
- function update() {
- world.Step(time, velocityIterations, positionIterations);
- var body = world.GetBodyList();
- while (body) {
- var myObject = body.GetUserData();
- if (myObject) {
- var position = body.GetPosition();
- myObject.x = position.x / SCALE;
- myObject.y = position.y / SCALE;
- myObject.rotation = body.GetAngle() / Matrix2D.DEG_TO_RAD;
- }
- body = body.GetNext();
- }
- stage.update();
- }
|
Appendix おまけ
3つほどリンクを加えておく。
作成者: 野中文雄
更新日: 2013年5月22日 ラジアン角を度数値に換算する処理が漏れていたので追加。jsdo.itのコードを掲載。
作成日: 2013年5月13日
Copyright ©
2001-2013 Fumio Nonaka. All rights reserved.
|