サイトトップ

Director Flash 書籍 業務内容 プロフィール

HTML5テクニカルノート

Box2dWebを使うときの名前空間の扱い方

ID: FN1301002 Technique: HTML5 and JavaScript

Box2Dは数多くのクラスから成立ちます。Box2dWeb(以下「Box2D」とします)は、それらのクラスをファイルでは分けず、代わりに名前空間により分類・識別します。そのため、クラスを参照するときには、頭にそれぞれの名前空間を添えなければなりません。同じクラスを何度も用いる場合には、煩わしいことになります。Box2Dを使うときの名前空間の扱い方について考えます。


01 クラスをローカル変数に入れる

Box2Dの名前空間が煩わしいのは、パッケージにもとづく階層的な組立てになっているからです。たとえば、b2Vec2クラスはBox2D.Common.Math.b2Vec2、b2WorldクラスがBox2D.Dynamics.b2Worldです。すると、b2Worldインスタンスをつくる初期化の関数(initializeBox2D())は、つぎのように定められることになります。ステートメントが長くなるだけでなく、中身が見づらくなっています。

var world;
var gravityVertical = 10;

function initializeBox2D() {
  world = new Box2D.Dynamics.b2World(new Box2D.Common.Math.b2Vec2(0, gravityVertical), true);
}

そこで「EaselJSのオブジェクトを物理演算エンジンのBox2Dで落とす」では、名前空間のクラスをローカル変数に入れてから処理を書きました。ステートメント数は増えても見やすくなります。また、関数の中で同じクラスを何度も参照するときは、手間も省けます。

var world;
var gravityVertical = 10;

function initializeBox2D() {
  var b2Vec2 = Box2D.Common.Math.b2Vec2;
  var b2World = Box2D.Dynamics.b2World;
  world = new b2World(new b2Vec2(0, gravityVertical), true);
}

もっとも、新たな関数で同じクラスを使うときは、また改めてローカル変数を宣言しなければなりません。


02 関数の外で宣言した変数にクラスを入れる

関数の外で宣言した変数にクラスを納めれば、すべての関数から参照できます。比較的簡単なスクリプトであれば、これが手っ取り早いです。

var world;
var gravityVertical = 10;
var b2Vec2 = Box2D.Common.Math.b2Vec2;
var b2World = Box2D.Dynamics.b2World;

function initializeBox2D() {
  world = new b2World(new b2Vec2(0, gravityVertical), true);
}

ただ、初めに述べたとおり、Box2Dは多くのクラスから成立っています。そのため、Box2Dを使うだけで、宣言する変数の数はかなり増えます。グローバルな領域に変数を多くつくりたくない、という人もいます。

そこで、Box2dWebサイトでは、名前のない関数でクラスを定義し、そのローカル変数にBox2Dのクラスを与えることがお勧めとされています。この書き方でしたら、クラスのすべてのメソッドから変数が参照できます。他方、ローカル変数として扱われるため、グローバル領域には残らず、クラスの外からは見えなくなります。

(function () {
  var b2Vec2 = Box2D.Common.Math.b2Vec2;
  var b2BodyDef = Box2D.Dynamics.b2BodyDef;
  var b2Body = Box2D.Dynamics.b2Body;
  var b2FixtureDef = Box2D.Dynamics.b2FixtureDef;
  var b2Fixture = Box2D.Dynamics.b2Fixture;
  var b2World = Box2D.Dynamics.b2World;
  var b2MassData = Box2D.Collision.Shapes.b2MassData;
  var b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape;
  var b2CircleShape = Box2D.Collision.Shapes.b2CircleShape;
  var b2DebugDraw = Box2D.Dynamics.b2DebugDraw;
  // クラス定義
})();

ただし、スクリプトはクラスとして定めなければなりません。その分、ハードルは上がってしまいます。


03 名前空間を短くする

CreateJS Suiteのクラスには、名前空間としてcreatejsが加えられています(「CreateJS Suiteのクラスに名前空間が設定される」参照)。同じように、たとえばbox2dという短い名前空間にできないでしょうか。それには、Objectインスタンスをつくって、そのプロパティにBox2Dのクラスを定めればよいでしょう。

var world;
var gravityVertical = 10;
var box2d = {
  b2Vec2:Box2D.Common.Math.b2Vec2,
  b2World:Box2D.Dynamics.b2World
};

function initializeBox2D() {
  world = new box2d.b2World(new box2d.b2Vec2(0, gravityVertical), true);
}

グローバルには、名前空間のオブジェクト(box2d)がひとつ変数に加わっただけです。また、Box2Dのクラスを参照するときは、すべてに同じ短い名前空間を添えれば済みます。

前出「EaselJSのオブジェクトを物理演算エンジンのBox2Dで落とす」のコード003は、ローカル変数にBox2Dのクラスを入れて参照しました。このスクリプトを、短い名前空間(box2d)で書替えてみましょう。script要素全体は、後にコード001として掲げます。その中から、新たな名前空間に変える部分を、行番号とともに抜書きします。

名前空間のオブジェクト(box2d)には、Box2Dの6つのクラスをプロパティとして定めました(第23行目)。すると、後の関数の中でBox2Dのクラスをローカル変数で宣言するステートメントは要らなくなります。代わりに、Box2Dのクラスは必ず短い名前空間(box2d)を添えて参照しなければなりません。

6つの関数(initializeBox2D()、createStaticFloor()、createDynamicBox()、createBodyDef()、createFixtureDefWithShape()、setShapeToFixtureDef())でBox2Dクラスのローカル変数宣言が要らなくなり、代わりに名前空間(box2d)がクラスの参照に添えられています。

  1. 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
    };
  1. function initializeBox2D(stageWidth, stageHeight) {
      // var b2Vec2 = Box2D.Common.Math.b2Vec2;
      // var b2World = Box2D.Dynamics.b2World;
      // world = new b2World(new b2Vec2(0, gravityVertical), true);
  2.   world = new box2d.b2World(new box2d.b2Vec2(0, gravityVertical), true);
  1. }
  2. function createStaticFloor(nX, nY, nWidth, nHeight, nColor) {
      // var b2Body = Box2D.Dynamics.b2Body;
      // var bodyDef = createBodyDef(nX, nY, b2Body.b2_staticBody);
  3.   var bodyDef = createBodyDef(nX, nY, box2d.b2Body.b2_staticBody);
  1. }
  2. function createDynamicBox(nX, nY, nWidth, nHeight, nColor) {
      // var b2Body = Box2D.Dynamics.b2Body;
      // var bodyDef = createBodyDef(nX, nY, b2Body.b2_dynamicBody);
  3.   var bodyDef = createBodyDef(nX, nY, box2d.b2Body.b2_dynamicBody);
  1. }
  2. function createBodyDef(nX , nY, nType) {
      // var b2BodyDef = Box2D.Dynamics.b2BodyDef;
      // var bodyDef = new b2BodyDef();
  3.   var bodyDef = new box2d.b2BodyDef();
  1. }
  1. function createFixtureDefWithShape(nWidth, nHeight) {
      // var b2FixtureDef = Box2D.Dynamics.b2FixtureDef;
      // var fixtureDef = new b2FixtureDef();
  2.   var fixtureDef = new box2d.b2FixtureDef();
  1. }
  2. function setShapeToFixtureDef(fixtureDef, nX, nY) {
      // var b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape;
      // var myPolygonShape = new b2PolygonShape();
  3.   var myPolygonShape = new box2d.b2PolygonShape();
  1. }

前出「EaselJSのオブジェクトを物理演算エンジンのBox2Dで落とす」のコード003では、CreateJSの名前空間(createjs)は用いていません。script要素を加えて、名前空間をwindowオブジェクトに定めることにより、参照は省いたからです(前出「CreateJS Suiteのクラスに名前空間が設定される」参照)。

つぎのようにこのscript要素は外して、CreateJSについても名前空間(createjs)を用いるように揃えましょう。

<!--
<script>
var createjs = window;
</script>
-->

CreateJSの名前空間(createjs)を加えるステートメントは、さほど多くありません。Stageオブジェクトと剛体として描くShapeオブジェクトの作成(第26および第63行目)、およびアニメーションに用いるTickerクラスの参照(第28〜29行目)に添えました。

  1. function initialize() {
  2.   var canvasElement = document.getElementById("myCanvas");
      // stage = new Stage(canvasElement);
  3.   stage = new createjs.Stage(canvasElement);
      // Ticker.setInterval(time * 1000);
  1.   createjs.Ticker.setInterval(time * 1000);
      // Ticker.addListener(this);
  2.   createjs.Ticker.addListener(this);
  3. }
  1. function createShapeForBodyDef(bodyDef, nWidth, nHeight, nColor) {
      // var myShape = new Shape();
  2.   var myShape = new createjs.Shape();
  1. }

ふたつの名前空間(box2dおよびcreatejs)を用いるように書替えたscript要素全体は、以下のコード001のとおりです。Box2Dのクラスを参照するためにvar宣言した名前空間(box2d)が書き加えられたものの、関数の中のローカル変数宣言は減りました。CreateJSの名前空間(createjs)も用いましたので、どのクラスがどちらのライブラリのものかもわかりやすくなったのではないでしょうか。

コード001■動的な剛体が落下して静的な剛体のうえで弾むアニメーション
  1. <script src="lib/Box2dWeb-2.1.a.3.min.js"></script>
  2. <script src="easeljs/utils/UID.js"></script>
  3. <script src="easeljs/geom/Matrix2D.js"></script>
  4. <script src="easeljs/events/MouseEvent.js"></script>
  5. <script src="easeljs/display/DisplayObject.js"></script>
  6. <script src="easeljs/display/Container.js"></script>
  7. <script src="easeljs/display/Stage.js"></script>
  8. <script src="easeljs/display/Graphics.js"></script>
  9. <script src="easeljs/display/Shape.js"></script>
  10. <script src="easeljs/utils/Ticker.js"></script>
  11. <script>
  12. var stage;
  13. var SCALE = 1 / 30;
  14. var world;
  15. var gravityVertical = 10;
  16. var velocityIterations = 10;
  17. var positionIterations = 10;
  18. var time = 1 / 24;
  19. var boxEdge = 20;
  20. var density = 1;
  21. var friction = 0.5;
  22. var restitution = 0.5;
  23. 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
    };
  24. function initialize() {
  25.   var canvasElement = document.getElementById("myCanvas");
  26.   stage = new createjs.Stage(canvasElement);
  27.   initializeBox2D(canvasElement.width, canvasElement.height);
  28.   createjs.Ticker.setInterval(time * 1000);
  29.   createjs.Ticker.addListener(this);
  30. }
  31. function initializeBox2D(stageWidth, stageHeight) {
  32.   world = new box2d.b2World(new box2d.b2Vec2(0, gravityVertical), true);
  33.   var centerX = stageWidth / 2;
  34.   var floorWidth = stageWidth * 0.8;
  35.   var floorHeight = boxEdge;
  36.   var floorShape = createStaticFloor(centerX, stageHeight - floorHeight, floorWidth, floorHeight, "#CCCCCC");
  37.   stage.addChild(floorShape);
  38.   var myShape = createDynamicBox(centerX, boxEdge, boxEdge, boxEdge, "#0000FF");
  39.   stage.addChild(myShape);
  40. }
  41. function createStaticFloor(nX, nY, nWidth, nHeight, nColor) {
  42.   var bodyDef = createBodyDef(nX, nY, box2d.b2Body.b2_staticBody);
  43.   var myShape = createShapeForBodyDef(bodyDef, nWidth, nHeight, nColor);
  44.   var fixtureDef = createFixtureDefWithShape(nWidth, nHeight);
  45.   var body = createBodyWithFixture(world, bodyDef, fixtureDef);
  46.   return myShape;
  47. }
  48. function createDynamicBox(nX, nY, nWidth, nHeight, nColor) {
  49.   var bodyDef = createBodyDef(nX, nY, box2d.b2Body.b2_dynamicBody);
  50.   var myShape = createShapeForBodyDef(bodyDef, nWidth, nHeight, nColor);
  51.   var fixtureDef = createFixtureDefWithShape(nWidth, nHeight);
  52.   setFixtureDef(fixtureDef, density, friction, restitution);
  53.   var body = createBodyWithFixture(world, bodyDef, fixtureDef);
  54.   return myShape;
  55. }
  56. function createBodyDef(nX , nY, nType) {
  57.   var bodyDef = new box2d.b2BodyDef();
  58.   bodyDef.position.Set(nX * SCALE, nY * SCALE);
  59.   bodyDef.type = nType;
  60.   return bodyDef;
  61. }
  62. function createShapeForBodyDef(bodyDef, nWidth, nHeight, nColor) {
  63.   var myShape = new createjs.Shape();
  64.   draw(myShape, nWidth, nHeight, nColor);
  65.   myShape.regX = nWidth / 2;
  66.   myShape.regY = nHeight / 2;
  67.   bodyDef.userData = myShape;
  68.   return myShape;
  69. }
  70. function draw(myShape, nWidth, nHeight, nColor) {
  71.   var myGraphics = myShape.graphics;
  72.   myGraphics.beginFill(nColor);
  73.   myGraphics.drawRect(0, 0, nWidth, nHeight);
  74. }
  75. function createFixtureDefWithShape(nWidth, nHeight) {
  76.   var fixtureDef = new box2d.b2FixtureDef();
  77.   setShapeToFixtureDef(fixtureDef, nWidth / 2, nHeight / 2);
  78.   return fixtureDef;
  79. }
  80. function setShapeToFixtureDef(fixtureDef, nX, nY) {
  81.   var myPolygonShape = new box2d.b2PolygonShape();
  82.   myPolygonShape.SetAsBox(nX * SCALE, nY * SCALE)
  83.   fixtureDef.shape = myPolygonShape;
  84. }
  85. function setFixtureDef(fixtureDef, density, friction, restitution) {
  86.   fixtureDef.density = density;
  87.   fixtureDef.friction = friction;
  88.   fixtureDef.restitution = restitution;
  89. }
  90. function createBodyWithFixture(world, bodyDef, fixtureDef) {
  91.   var body = world.CreateBody(bodyDef);
  92.   body.CreateFixture(fixtureDef);
  93.   return body;
  94. }
  95. function tick() {
  96.   world.Step(time, velocityIterations, positionIterations);
  97.   var body = world.GetBodyList();
  98.   while (body) {
  99.     var myObject = body.GetUserData();
  100.     if (myObject) {
  101.       var position = body.GetPosition();
  102.       myObject.x = position.x / SCALE;
  103.       myObject.y = position.y / SCALE;
  104.       myObject.rotation = body.GetAngle();
  105.     }
  106.     body = body.GetNext();
  107.   }
  108.   stage.update();
  109. }
  110. </script>

以上ご紹介したBox2Dの名前空間の扱い方は、それぞれ長短がありました。プロジェクトの大きさや用いるライブラリの種類、みなさんの知識や技術などに応じて、適切と思われる手法をお選びください。


作成者: 野中文雄
作成日: 2013年1月12日


Copyright © 2001-2013 Fumio Nonaka.  All rights reserved.