ぶつかると散る

以下の枠線内でタッチされた場所の下部より、赤い箱が上に打ち出されます。 箱同士が衝突すると、斜め 4 方向へ箱が分裂して飛び散ります。 左右に少しずらして 4 個前後、箱を打ち上げると綺麗に散らかります。

動かし方

ダウンロードした zip ファイルを解凍して以下のコマンドを実行してください。

npm install
akashic-sandbox .

ソースコード

"use strict";

var box2d = require("@akashic-extension/akashic-box2d");

// 2次元ベクトル
var b2Vec2 = box2d.Box2DWeb.Common.Math.b2Vec2;

// 斜め方向のベクトル
var directions = [
  new b2Vec2(-1, -1), // 左上
  new b2Vec2(1, -1), // 右上
  new b2Vec2(-1, 1), // 左下
  new b2Vec2(1, 1) // 右下
];

/** 物理世界のプロパティ */
var worldProperty = {
  gravity: [0.0, 9.8], // 重力の方向(m/s^2)
  scale: 50, // スケール(pixel/m)
  sleep: true // 停止した物体を演算対象としないかどうか
};
/** 物理エンジンの世界 */
var physics = new box2d.Box2D(worldProperty);

/** 衝突判定を持つ箱のリスト */
var boxList = [];
/** 衝突した物体のIDリスト */
var contactIDList = [];

/** 衝突イベントのリスナ */
var contactListener = new box2d.Box2DWeb.Dynamics.b2ContactListener();
// 衝突開始時のイベントリスナを設定
contactListener.BeginContact = function(contact) {
  // physics.createBodyDefにuserDataを指定していない場合は、
  // userDataにg.E.idが設定されるので、IDの組を保存しておく
  var a = contact
    .GetFixtureA()
    .GetBody()
    .GetUserData();
  var b = contact
    .GetFixtureB()
    .GetBody()
    .GetUserData();
  contactIDList.push({ a: a, b: b });
};
// イベントリスナを設定
physics.world.SetContactListener(contactListener);

function main() {
  var scene = new g.Scene({ game: g.game });

  scene.loaded.add(function() {
    scene.pointDownCapture.add(function(event) {
      // タッチされた場所の画面下から箱を飛ばす
      var box = createBox(scene, createBoxParameter(1.0, 1.0, "crimson"));
      boxList.push(box);
      // ※ 表示はbox2dのbodyの座標に同期するので、box2dの座標だけ書き換える
      box.b2body.SetPosition(physics.vec2(event.point.x, g.game.height));
      // 箱に上向きの瞬間的な力を加える
      box.b2body.ApplyImpulse(
        new b2Vec2(0, -13), // 加える力ベクトル
        box.b2body.GetPosition() // 力点
      );
    });

    scene.update.add(function() {
      // 衝突した箱を4分割して斜めに飛ばす
      while (0 < contactIDList.length) {
        var contactID = contactIDList.pop();
        for (var i = 0; i < boxList.length; ++i) {
          var box = boxList[i];
          // 衝突した箱かどうかの判定
          if (box.entity.id === contactID.a || box.entity.id === contactID.b) {
            // 大きさは衝突した箱の4分の1
            var size = physics.vec2(box.entity.width, box.entity.height);
            size.Multiply(0.5);

            // 処理落ちしないように一定の大きさで分裂をやめる
            if (size.x < 0.1) {
              break;
            }

            for (var j = 0; j < 4; ++j) {
              var dir = directions[j];

              var miniBox = createBox(scene, createBoxParameter(size.x, size.y, "royalblue"));
              // 斜めに少しずらして配置する
              var pos = box.b2body.GetPosition().Copy();
              pos.Add(dir);
              miniBox.b2body.SetPosition(pos);
              // 分裂前の箱の速度を引き継ぐ
              miniBox.b2body.SetLinearVelocity(box.b2body.GetLinearVelocity());
              // 斜め方向に加速させる
              miniBox.b2body.ApplyImpulse(dir, miniBox.b2body.GetPosition());

              boxList.push(miniBox);
            }

            removeBox(box);
            --i; // boxListから要素を削除した分、indexを前に詰める
          }
        }
      }

      // 物理エンジンの世界をすすめる
      // ※ step関数の引数は秒数なので、1フレーム分の時間(1.0 / g.game.fps)を指定する
      physics.step(1.0 / g.game.fps);
    });
  });

  g.game.pushScene(scene);
}

/**
 * 箱の生成パラメータを生成する
 * @param {number} width 横幅(m)
 * @param {number} height 縦幅(m)
 * @param {string} color 描画色
 */
function createBoxParameter(width, height, color) {
  return {
    /** 表示情報のパラメータ */
    appear: {
      width: width * worldProperty.scale,
      height: height * worldProperty.scale,
      cssColor: color
    },
    /** 物理定義 */
    physics: {
      /** 物理挙動 */
      body: physics.createBodyDef({
        type: box2d.BodyType.Dynamic // 自由に動ける物体
      }),
      /** 物理性質 */
      fixture: physics.createFixtureDef({
        density: 1.0, // 密度
        friction: 0.5, // 摩擦係数
        restitution: 0.3, // 反発係数
        shape: physics.createRectShape(width * worldProperty.scale, height * worldProperty.scale) // 衝突判定の形
      })
    }
  };
}

/**
 * 衝突判定を持つ箱を生成する
 * @param {g.Scene} scene 描画を行うシーン
 * @param {Object} parameter 箱の生成パラメータ
 */
function createBox(scene, parameter) {
  // 表示用の矩形(1m × 1m)を生成
  var rect = new g.FilledRect({
    scene: scene,
    width: parameter.appear.width,
    height: parameter.appear.height,
    cssColor: parameter.appear.cssColor
  });
  scene.append(rect);

  // 表示用の矩形と衝突判定を結び付けて返す
  var box = physics.createBody(rect, parameter.physics.body, parameter.physics.fixture);

  box.entity.update.add(function() {
    // 画面外に出たら自分を削除
    if (g.game.height + 100 < box.entity.y) {
      removeBox(box);
    }
  });

  return box;
}

/**
 * 箱を削除する
 * @param {EBody} box 削除する箱
 */
function removeBox(box) {
  boxList.splice(boxList.indexOf(box), 1);
  physics.removeBody(box);
  box.entity.destroy();
}

module.exports = main;

© DWANGO Co., Ltd.