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

第30回Box2Dでたくさんのボールを床に落とし続ける

前回の第29回Box2Dで落としたボールを床に弾ませるは、剛体のボールひとつを床に自由落下させた。今回は、ボールの数を増やしてランダムな大きさと位置で落とすとともに、スクリプトの組立ても整えたい。前回書き上げた第29回コード2自由落下させたボールを静的な床の上で弾ませるに手を加えてゆこう。

ボールをつくり続ける

第29回コード2には、ボールをつくる関数(addBall())が定められている。そして、素材画像を読込み終えたリスナー関数(loadFinished())から呼び出した。だが、ボールをいくつもつくるため、このリスナーに替えて、Ticker.tickイベントのリスナー関数(tick())から呼び出すことにする。

もっとも、Ticker.tickイベントのたびにボールを増やしたのでは、間隔が短すぎる。そこで、イベント間の経過時間をイベントオブジェクトのdeltaプロパティで調べて変数(duration)に加算し、合計値が予め決めた変数(interval)の時間より大きくなったら剛体のボールをつくることにした。そのため、経過時間はボールを増やす関数(addBall())の引数(delta)に加えて渡している。なお、ボールの剛体のxy座標値(nXとnY)および半径(radius)はランダムに定めた。

var duration = 0;
var interval = 50;

function tick(eventObject) {
  var delta = eventObject.delta;
  addBall(delta);

}

// function addBall() {
function addBall(delta) {
  duration += delta;
  if (duration > interval) {
    var nX = floor.width * Math.random() + floor.x;
    var nY = -stageHeight * Math.random();
    var radius = 15 * (Math.random() - 0.5) + standardRadius;
    // var ball = createDynamicBall(stageWidth / 2, -imageRadius, imageRadius);
    var ball = createDynamicBall(nX, nY, radius);

    duration = 0;
  }
}

function loadFinished(eventObject) {

  // addBall();
}

これで、大小さまざまなボールが、ランダムな位置からつぎつぎに床に向けて自由落下し、互いにぶつかり合っては床下に落ちる図1⁠。これこそ、まさに物理シミュレーションの醍醐味だ。さて、これだけのコードで不安を覚えた読者はおられるだろうか。その危機感は正しい。

ボールのオブジェクトと剛体は、ステージと物理空間にひたすら加えられるばかりで、除かれることがない。ボールが床下に見えなくなったといっても、それはあくまで人形のボールから見た話だ。黒子のBox2Dは構うことなく、物理空間に加えられたすべての剛体のシミュレーションを続ける。演算の負荷は瞬く間に上がってしまう。

図1 さまざまな大きさのボールがランダムな位置からつぎつぎに床に向けて自由落下する
図1 さまざまな大きさのボールがランダムな位置からつぎつぎに床に向けて自由落下する

では、どうすればいいかというと、本連載でもすでに紹介してきたようにオブジェクトを使い回すことだ。ステージから見えなくなった人形のオブジェクトは表示リストから外すとともに、剛体も物理演算のシミュレーションから除く。けれど、その剛体とオブジェクトはとっておき、新たなボールを求められたときに初期化して使い回せばよい。そうしたところがまだ不完全とはいえ、参照の便を考えてここまでをコード1としてつぎにまとめた。

コード1 ランダムな位置と大きさのボールをひたすらつくって床に自由落下させる
var SCALE = 1 / 30;
var stage;
var world;
var gravityVertical = 15;
var velocityIterations = 8;
var positionIterations = 3;
var stageWidth;
var stageHeight;
var ballImage;
var imageRadius;
var standardRadius = 20;
var floor = new createjs.Rectangle();
var duration = 0;
var interval = 50;
function initialize() {
  var canvasElement = document.getElementById("myCanvas");
  var gravity = new Box2D.Common.Math.b2Vec2(0, gravityVertical);
  stage = new createjs.Stage(canvasElement);
  stageWidth = canvasElement.width;
  stageHeight = canvasElement.height;
  floor.width = stageWidth * 0.8;
  floor.x = (stageWidth - floor.width) / 2;
  initializeBox2D(gravity, stageWidth, stageHeight);
  createjs.Ticker.timingMode = createjs.Ticker.RAF;
  preloadImage("images/Pen.png");
}
function initializeBox2D(gravity, stageWidth, stageHeight) {
  world = new Box2D.Dynamics.b2World(gravity, true);
  var floorShape = createStaticFloor(stageWidth / 2, stageHeight - standardRadius, floor.width, standardRadius, "#CCCCCC");
  stage.addChild(floorShape);
}
function tick(eventObject) {
  var delta = eventObject.delta;
  addBall(delta);
  update(delta);
  stage.update();
}
function addBall(delta) {
  duration += delta;
  if (duration > interval) {
    var nX = floor.width * Math.random() + floor.x;
    var nY = -stageHeight * Math.random();
    var radius = 15 * (Math.random() - 0.5) + standardRadius;
    var ball = createDynamicBall(nX, nY, radius);
    stage.addChild(ball);
    duration = 0;
  }
}
function createDynamicBall(nX, nY, radius) {
  var dynamicBody = Box2D.Dynamics.b2Body.b2_dynamicBody;
  var bodyDef = defineBody(nX, nY, dynamicBody);
  var ball = createVisualBall(radius, bodyDef);
  var circleShape = new Box2D.Collision.Shapes.b2CircleShape(radius * SCALE);
  var fixtureDef = defineFixture(circleShape);
  setFixture(fixtureDef, 1, 0.1, 0.8);
  createBody(world, bodyDef, fixtureDef);
  return ball;
}
function createStaticFloor(nX, nY, nWidth, nHeight, color) {
  var staticBody = Box2D.Dynamics.b2Body.b2_staticBody
  var bodyDef = defineBody(nX, nY, staticBody);
  var floorShape = createVisualFloor(nWidth, nHeight, color, bodyDef);
  var boxShape = new Box2D.Collision.Shapes.b2PolygonShape();
  var fixtureDef = defineFixture(boxShape);
  boxShape.SetAsBox(nWidth / 2 * SCALE, nHeight / 2 * SCALE);
  createBody(world, bodyDef, fixtureDef);
  return floorShape;
}
function defineBody(nX , nY, bodyType) {
  var bodyDef = new Box2D.Dynamics.b2BodyDef();
  bodyDef.position.Set(nX * SCALE, nY * SCALE);
  bodyDef.type = bodyType;
  return bodyDef;
}
function defineFixture(myShape) {
  var fixtureDef = new Box2D.Dynamics.b2FixtureDef();
  fixtureDef.shape = myShape;
  return fixtureDef;
}
function setFixture(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(delta) {
  world.Step(delta / 1000, 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()/createjs.Matrix2D.DEG_TO_RAD;
    }
    body = body.GetNext();
  }
}
function createVisualBall(radius, bodyDef) {
  var ball = new createjs.Bitmap(ballImage);
  ball.regX = ballImage.width / 2;
  ball.regY = ballImage.height / 2;
  ball.scaleX = ball.scaleY = radius / imageRadius;
  bodyDef.userData = ball;
  return ball;
}
function createVisualFloor(nWidth, nHeight, color, bodyDef) {
  var floorShape = new createjs.Shape();
  floorShape.regX = nWidth / 2;
  floorShape.regY = nHeight / 2;
  floorShape.graphics
  .beginFill(color)
  .drawRect(0, 0, nWidth, nHeight);
  bodyDef.userData = floorShape;
  return floorShape;
}
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);
}

ボールのオブジェクトを使い回す

剛体のb2Bodyオブジェクトを物理演算シミュレーションから外したり、加えたりするには、b2Body.SetActive()メソッドを用いる。引数はブール(論理)値で、falseを与えると物理演算の対象から外れる。改めて、シミュレーションに加えるときはtrueを渡せばよい。

b2Bodyオブジェクト.SetActive(アクティブ化)

ただし、b2Body.SetActive()メソッドで剛体を物理演算シミュレーションから除いても、b2Worldオブジェクトの物理空間にはそのまま残る。つまり、b2World.GetBodyList()b2Body.GetNext()メソッドで拾えてしまうということだ。物理演算からそれらは外して、無駄を省くようにしたい。

剛体のb2Bodyオブジェクトを使い回すときには、その物理情報を初期化することも忘れてならない。移動と回転の速度は、それぞれb2Body.SetLinearVelocity()b2Body.SetAngularVelocity()メソッドで0にする。そして、使い回す剛体の新たな位置と角度を、b2Body.SetPositionAndAngle()メソッドで与えればよい表1⁠。

表1 剛体の物理情報を定めるメソッド
b2Bodyメソッド引数値
SetLinearVelocity(速度ベクトル)剛体が移動する速度ベクトルを示すb2Vec2オブジェクト
SetAngularVelocity(角速度)剛体の回転する角速度を示すラジアン値(ラジアン/秒)
SetPositionAndAngle(位置ベクトル, 角度)剛体の位置ベクトルを示すb2Vec2オブジェクトと、回転角を示すラジアン値

ボールの剛体を増やす関数(addBall)は、いきなり剛体をつくる関数(createDynamicBall())を呼び出すのではなく、以下のように新たに定めた関数(getDynamicBall())の呼出しにより剛体のオブジェクトを使い回す。剛体のオブジェクトは、変数(balls)に定めた配列に納めておく。剛体を使い回す関数は、配列にオブジェクトがあればそれを取り出し、ない場合のみ新たな剛体をつくる。

配列から取り出したエレメントの剛体は、まずb2Body.SetActive()メソッドで物理演算シミュレーションに戻す。そのうえで、前掲表1のメソッドにより、速度は0にし、位置を引数の値に、回転角は0とした。なお、移動速度の初期化に用いるため、0ベクトルのb2Vec2オブジェクトをあらかじめ変数(ZERO_VECTOR)に定めておいた。

var ZERO_VECTOR = new Box2D.Common.Math.b2Vec2();
var balls = [];

function addBall(delta) {

  // var ball = createDynamicBall(nX, nY, radius);
  var ball = getDynamicBall(nX, nY, radius);

}
function getDynamicBall(nX, nY, radius) {
  var ball;
  if (balls.length) {
    var body = balls.pop();
    body.SetActive(true);
    body.SetLinearVelocity(ZERO_VECTOR);
    body.SetAngularVelocity(0);
    body.SetPositionAndAngle(new Box2D.Common.Math.b2Vec2(nX * SCALE, nY * SCALE), 0);
    ball = body.GetUserData();
  } else {
    ball = createDynamicBall(nX, nY, radius);
  }
  return ball;
}

つぎは、ステージから見えなくなったボールを表示リストから外し、剛体を物理演算シミュレーションから除く処理だ。物理演算を進める関数(update())は、以下のように書き替えた。落ちた剛体(body)がステージ下端(bottomLimit)から消えるまではシミュレーションを続ける。下端からステージの外に出た剛体はシミュレーションから外し、ボールのオブジェクト(myObject)は表示リストから除いたうえで、使い回す剛体の配列(balls)に加えた。

なお、ステージ下端の座標(bottomLimit)は、素材をロードし終えたときのリスナー関数(loadFinished())が定めている。また、剛体を使い回しの配列(balls)に加える前に、同じ剛体がすでにエレメントとして納められていないかを念のため確かめた。indexOf()は、引数が配列エレメントならそのインデックスを、エレメントになければ-1を返すCreateJSの便利な関数だ。

var bottomLimit;

function update(delta) {

  var position = body.GetPosition();
  var positionY = position.y / SCALE;
  if (positionY < bottomLimit) {

    // myObject.y = position.y / SCALE;
    myObject.y = positionY;

  } else {
    body.SetActive(false);
    stage.removeChild(myObject);
    if (createjs.indexOf(balls, body) < 0) {
      balls.push(body);
    }
  }

}

function loadFinished(eventObject) {

  bottomLimit = stageHeight + ballImage.height;

}

これで自由落下する剛体のオブジェクトが使い回される。つまり、床から落ちてステージの下に消えたボールの剛体は、新たな設定が与えられてまたステージの上の方から落とされる。初めの見た目はコード1と同じだ。けれど、ずっとシミュレーションを続けても処理が重くなることはない。script要素全体はつぎのコード2にまとめた。

コード2 剛体のオブジェクトを使い回してつぎつぎと床に自由落下させる
var SCALE = 1 / 30;
var ZERO_VECTOR = new Box2D.Common.Math.b2Vec2();
var stage;
var world;
var gravityVertical = 15;
var velocityIterations = 8;
var positionIterations = 3;
var stageWidth;
var stageHeight;
var bottomLimit;
var ballImage;
var imageRadius;
var standardRadius = 20;
var floor = new createjs.Rectangle();
var duration = 0;
var interval = 50;
var balls = [];
function initialize() {
  var canvasElement = document.getElementById("myCanvas");
  var gravity = new Box2D.Common.Math.b2Vec2(0, gravityVertical);
  stage = new createjs.Stage(canvasElement);
  stageWidth = canvasElement.width;
  stageHeight = canvasElement.height;
  floor.width = stageWidth * 0.8;
  floor.x = (stageWidth - floor.width) / 2;
  initializeBox2D(gravity, stageWidth, stageHeight);
  createjs.Ticker.timingMode = createjs.Ticker.RAF;
  preloadImage("images/Pen.png");
}
function initializeBox2D(gravity, stageWidth, stageHeight) {
  world = new Box2D.Dynamics.b2World(gravity, true);
  var floorShape = createStaticFloor(stageWidth / 2, stageHeight - standardRadius, floor.width, standardRadius, "#CCCCCC");
  stage.addChild(floorShape);
}
function tick(eventObject) {
  var delta = eventObject.delta;
  addBall(delta);
  update(delta);
  stage.update();
}
function addBall(delta) {
  duration += delta;
  if (duration > interval) {
    var nX = floor.width * Math.random() + floor.x;
    var nY = -stageHeight * Math.random();
    var radius = 15 * (Math.random() - 0.5) + standardRadius;
    var ball = getDynamicBall(nX, nY, radius);
    stage.addChild(ball);
    duration = 0;
  }
}
function getDynamicBall(nX, nY, radius) {
  var ball;
  if (balls.length) {
    var body = balls.pop();
    body.SetActive(true);
    body.SetLinearVelocity(ZERO_VECTOR);
    body.SetAngularVelocity(0);
    body.SetPositionAndAngle(new Box2D.Common.Math.b2Vec2(nX * SCALE, nY * SCALE), 0);
    ball = body.GetUserData();
  } else {
    ball = createDynamicBall(nX, nY, radius);
  }
  return ball;
}
function createDynamicBall(nX, nY, radius) {
  var dynamicBody = Box2D.Dynamics.b2Body.b2_dynamicBody;
  var bodyDef = defineBody(nX, nY, dynamicBody);
  var ball = createVisualBall(radius, bodyDef);
  var circleShape = new Box2D.Collision.Shapes.b2CircleShape(radius * SCALE);
  var fixtureDef = defineFixture(circleShape);
  setFixture(fixtureDef, 1, 0.1, 0.8);
  createBody(world, bodyDef, fixtureDef);
  return ball;
}
function createStaticFloor(nX, nY, nWidth, nHeight, color) {
  var staticBody = Box2D.Dynamics.b2Body.b2_staticBody
  var bodyDef = defineBody(nX, nY, staticBody);
  var floorShape = createVisualFloor(nWidth, nHeight, color, bodyDef);
  var boxShape = new Box2D.Collision.Shapes.b2PolygonShape();
  var fixtureDef = defineFixture(boxShape);
  boxShape.SetAsBox(nWidth / 2 * SCALE, nHeight / 2 * SCALE);
  createBody(world, bodyDef, fixtureDef);
  return floorShape;
}
function defineBody(nX , nY, bodyType) {
  var bodyDef = new Box2D.Dynamics.b2BodyDef();
  bodyDef.position.Set(nX * SCALE, nY * SCALE);
  bodyDef.type = bodyType;
  return bodyDef;
}
function defineFixture(myShape) {
  var fixtureDef = new Box2D.Dynamics.b2FixtureDef();
  fixtureDef.shape = myShape;
  return fixtureDef;
}
function setFixture(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(delta) {
  world.Step(delta / 1000, velocityIterations, positionIterations);
  var body = world.GetBodyList();
  while (body) {
    if (body.IsActive()) {
      var myObject = body.GetUserData();
      if (myObject) {
        var position = body.GetPosition();
        var positionY = position.y / SCALE;
        if (positionY < bottomLimit) {
          myObject.x = position.x / SCALE;
          myObject.y = positionY;
          myObject.rotation = body.GetAngle()/createjs.Matrix2D.DEG_TO_RAD;
        } else {
          body.SetActive(false);
          stage.removeChild(myObject);
          if (createjs.indexOf(balls, body) < 0) {
            balls.push(body);
          }
        }
      }
    }
    body = body.GetNext();
  }
}
function createVisualBall(radius, bodyDef) {
  var ball = new createjs.Bitmap(ballImage);
  ball.regX = ballImage.width / 2;
  ball.regY = ballImage.height / 2;
  ball.scaleX = ball.scaleY = radius / imageRadius;
  bodyDef.userData = ball;
  return ball;
}
function createVisualFloor(nWidth, nHeight, color, bodyDef) {
  var floorShape = new createjs.Shape();
  floorShape.regX = nWidth / 2;
  floorShape.regY = nHeight / 2;
  floorShape.graphics
  .beginFill(color)
  .drawRect(0, 0, nWidth, nHeight);
  bodyDef.userData = floorShape;
  return floorShape;
}
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;
  bottomLimit = stageHeight + ballImage.height;
  createjs.Ticker.addEventListener("tick", tick);
}

Box2Dの名前空間の扱い

仕上げとして、Box2Dの名前空間の扱いについて考えたい。CreateJSのライブラリのクラスは、⁠createjs」という名前空間を添えて参照する。これは、さまざまなライブラリを合わせて用いたとき、たまたまクラス名が重複して使えなくなるのを避ける仕組みだ。とはいえ、読者の多くは、Box2Dの名前空間が長過ぎると思われたろう。落語の寿限無ではないのだからと。たとえば、b2PolygonShapeだ。

Box2D.Collision.Shapes.b2PolygonShape

ひとつの手は、Box2Dのクラスの参照を変数に入れてしまうことだ。だが、変数が多くなってしまう。とくに、グローバルな変数の数は、やたらと増やしたくない。そこで、変数にはオブジェクトをひとつ定め、Box2Dのクラスを以下のようにそのプロパティとして与える。これなら、変数(box2d)はひとつで済み、参照の仕方もCreateJSと変わらない[1]⁠。

box2d.b2PolygonShape
var box2d = {
  b2Vec2:Box2D.Common.Math.b2Vec2,
  b2World:Box2D.Dynamics.b2World,
  b2Body:Box2D.Dynamics.b2Body,
  b2BodyDef:Box2D.Dynamics.b2BodyDef,
  b2FixtureDef:Box2D.Dynamics.b2FixtureDef,
  b2PolygonShape:Box2D.Collision.Shapes.b2PolygonShape,
  b2CircleShape:Box2D.Collision.Shapes.b2CircleShape
};

するとたとえば、前掲コード2の剛体をつくる関数(createDynamicBall())は、つぎのように書き替えられる。これで、ステートメントが短くなって、コードも書きやすい。

function createDynamicBall(nX, nY, radius) {
  // var dynamicBody = Box2D.Dynamics.b2Body.b2_dynamicBody;
  var dynamicBody = box2d.b2Body.b2_dynamicBody;
  var bodyDef = defineBody(nX, nY, dynamicBody);
  var ball = createVisualBall(radius, bodyDef);
  // var circleShape = new Box2D.Collision.Shapes.b2CircleShape(radius * SCALE);
  var circleShape = new box2d.b2CircleShape(radius * SCALE);
  var fixtureDef = defineFixture(circleShape);
  setFixture(fixtureDef, 1, 0.1, 0.8);
  createBody(world, bodyDef, fixtureDef);
  return ball;
}

名前空間については、いちいち書直しの前と後のステートメントを示すのはかえって煩わしいだろう。書替えを済ませたscript要素をつぎのコード3にまとめた。今回のサンプルでは、Box2Dの同じクラスをあちこちで参照しているところがあまり多くない。したがって、書く手間でいえば、Box2Dのクラスをオブジェクトのプロパティに定めたからといって、さほど楽になった訳ではない。とはいえ、コードが見やすくなり、後あとの拡張や修正を考えれば改善されたといえよう。

コード3 Box2Dのクラスをひとつのオブジェクトのプロパティに定めた
var box2d = {
  b2Vec2:Box2D.Common.Math.b2Vec2,
  b2World:Box2D.Dynamics.b2World,
  b2Body:Box2D.Dynamics.b2Body,
  b2BodyDef:Box2D.Dynamics.b2BodyDef,
  b2FixtureDef:Box2D.Dynamics.b2FixtureDef,
  b2PolygonShape:Box2D.Collision.Shapes.b2PolygonShape,
  b2CircleShape:Box2D.Collision.Shapes.b2CircleShape
};
var SCALE = 1 / 30;
var ZERO_VECTOR = new box2d.b2Vec2();
var stage;
var world;
var gravityVertical = 15;
var velocityIterations = 8;
var positionIterations = 3;
var stageWidth;
var stageHeight;
var bottomLimit;
var ballImage;
var imageRadius;
var standardRadius = 20;
var floor = new createjs.Rectangle();
var duration = 0;
var interval = 50;
var balls = [];
function initialize() {
  var canvasElement = document.getElementById("myCanvas");
  var gravity = new box2d.b2Vec2(0, gravityVertical);
  stage = new createjs.Stage(canvasElement);
  stageWidth = canvasElement.width;
  stageHeight = canvasElement.height;
  floor.width = stageWidth * 0.8;
  floor.x = (stageWidth - floor.width) / 2;
  initializeBox2D(gravity, stageWidth, stageHeight);
  createjs.Ticker.timingMode = createjs.Ticker.RAF;
  preloadImage("images/Pen.png");
}
function initializeBox2D(gravity, stageWidth, stageHeight) {
  world = new box2d.b2World(gravity, true);
  var floorShape = createStaticFloor(stageWidth / 2, stageHeight - standardRadius, floor.width, standardRadius, "#CCCCCC");
  stage.addChild(floorShape);
}
function tick(eventObject) {
  var delta = eventObject.delta;
  addBall(delta);
  update(delta);
  stage.update();
}
function addBall(delta) {
  duration += delta;
  if (duration > interval) {
    var nX = floor.width * Math.random() + floor.x;
    var nY = -stageHeight * Math.random();
    var radius = 15 * (Math.random() - 0.5) + standardRadius;
    var ball = getDynamicBall(nX, nY, radius);
    stage.addChild(ball);
    duration = 0;
  }
}
function getDynamicBall(nX, nY, radius) {
  var ball;
  if (balls.length) {
    var body = balls.pop();
    body.SetActive(true);
    body.SetLinearVelocity(ZERO_VECTOR);
    body.SetAngularVelocity(0);
    body.SetPositionAndAngle(new box2d.b2Vec2(nX * SCALE, nY * SCALE), 0);
    ball = body.GetUserData();
  } else {
    ball = createDynamicBall(nX, nY, radius);
  }
  return ball;
}
function createDynamicBall(nX, nY, radius) {
  var dynamicBody = box2d.b2Body.b2_dynamicBody;
  var bodyDef = defineBody(nX, nY, dynamicBody);
  var ball = createVisualBall(radius, bodyDef);
  var circleShape = new box2d.b2CircleShape(radius * SCALE);
  var fixtureDef = defineFixture(circleShape);
  setFixture(fixtureDef, 1, 0.1, 0.8);
  createBody(world, bodyDef, fixtureDef);
  return ball;
}
function createStaticFloor(nX, nY, nWidth, nHeight, color) {
  var staticBody = box2d.b2Body.b2_staticBody;
  var bodyDef = defineBody(nX, nY, staticBody);
  var floorShape = createVisualFloor(nWidth, nHeight, color, bodyDef);
  var boxShape = new box2d.b2PolygonShape();
  var fixtureDef = defineFixture(boxShape);
  boxShape.SetAsBox(nWidth / 2 * SCALE, nHeight / 2 * SCALE);
  createBody(world, bodyDef, fixtureDef);
  return floorShape;
}
function defineBody(nX , nY, bodyType) {
  var bodyDef = new box2d.b2BodyDef();
  bodyDef.position.Set(nX * SCALE, nY * SCALE);
  bodyDef.type = bodyType;
  return bodyDef;
}
function defineFixture(myShape) {
  var fixtureDef = new box2d.b2FixtureDef();
  fixtureDef.shape = myShape;
  return fixtureDef;
}
function setFixture(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(delta) {
  world.Step(delta / 1000, velocityIterations, positionIterations);
  var body = world.GetBodyList();
  while (body) {
    if (body.IsActive()) {
      var myObject = body.GetUserData();
      if (myObject) {
        var position = body.GetPosition();
        var positionY = position.y / SCALE;
        if (positionY < bottomLimit) {
          myObject.x = position.x / SCALE;
          myObject.y = positionY;
          myObject.rotation = body.GetAngle()/createjs.Matrix2D.DEG_TO_RAD;
        } else {
          body.SetActive(false);
          stage.removeChild(myObject);
          if (createjs.indexOf(balls, body) < 0) {
            balls.push(body);
          }
        }
      }
    }
    body = body.GetNext();
  }
}
function createVisualBall(radius, bodyDef) {
  var ball = new createjs.Bitmap(ballImage);
  ball.regX = ballImage.width / 2;
  ball.regY = ballImage.height / 2;
  ball.scaleX = ball.scaleY = radius / imageRadius;
  bodyDef.userData = ball;
  return ball;
}
function createVisualFloor(nWidth, nHeight, color, bodyDef) {
  var floorShape = new createjs.Shape();
  floorShape.regX = nWidth / 2;
  floorShape.regY = nHeight / 2;
  floorShape.graphics
  .beginFill(color)
  .drawRect(0, 0, nWidth, nHeight);
  bodyDef.userData = floorShape;
  return floorShape;
}
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;
  bottomLimit = stageHeight + ballImage.height;
  createjs.Ticker.addEventListener("tick", tick);
}

このたびのBox2Dを使ったお題は、これででき上がりとする。jsdo.itにコード3を掲げた。次回は、また新たなお題を考えたい。

おすすめ記事

記事・ニュース一覧