「ニュートンのゆりかご」のシミュレートです。 ボールは上部の空間に紐で固定されていて、固定位置を中心にドラッグで移動することができます。
ダウンロードした zip ファイルを解凍して以下のコマンドを実行してください。
npm install
akashic-sandbox .
"use strict";
var box2d = require("@akashic-extension/akashic-box2d");
/** 2次元ベクトル */
var b2Vec2 = box2d.Box2DWeb.Common.Math.b2Vec2;
/** 2×2 の行列 */
var b2Mat22 = box2d.Box2DWeb.Common.Math.b2Mat22;
/** 物理世界のプロパティ */
var worldProperty = {
gravity: [0, 9.8], // 重力の方向(m/s^2)
scale: 50, // スケール(pixel/m)
sleep: true // 停止した物体を物理演算対象とするかどうか
};
/** 物理エンジンの世界 */
var physics = new box2d.Box2D(worldProperty);
/** 鉄球のパラメータ */
var ballParameter = {
appear: {
width: 1.0 * worldProperty.scale,
height: 1.0 * worldProperty.scale
},
/** 物理定義 */
physics: {
/** 物理挙動 */
body: physics.createBodyDef({
type: box2d.BodyType.Dynamic // 自由に動ける物体
}),
/** 物理性質 */
fixture: physics.createFixtureDef({
density: 5.0, // 密度
friction: 1.0, // 摩擦係数
restitution: 0.999, // 反発係数
shape: physics.createCircleShape(1.0 * worldProperty.scale) // 衝突判定の形(直径 1m の円形)
})
}
};
function main() {
var scene = new g.Scene({
game: g.game,
assetIds: ["circle", "circle_touch"]
});
scene.loaded.add(function() {
// 鉄球を5個横に並べて生成する
var ballCount = 5;
for (var i = 0; i < ballCount; ++i) {
var ball = createCircle(scene, ballParameter);
var position = calcCenter(g.game);
position.Add(new b2Vec2(i - Math.floor(ballCount / 2), 2.0));
ball.b2body.SetPosition(position);
ball.b2body.SetAngularDamping(0.8); // 回転しづらくする
ball.b2body.SetSleepingAllowed(false);
addMouseJoint(ball);
// 支点の位置は鉄球の 10m 上に設定
// ※ 支点からの距離が長いほど安定します
var fulcrum = position.Copy();
fulcrum.y -= 10.0;
// ボールとの接点の位置はボールの中心から少しずらす
// ※ 中心に設定すると、ボールが自由回転するため精度が落ちます
var contact = ball.b2body.GetPosition().Copy();
contact.Add(new b2Vec2(0.0, -0.5));
addDistanceJoint(ball, fulcrum, contact);
}
scene.update.add(function() {
// 物理エンジンの世界をすすめる
// ※ step関数の引数は秒数なので、1フレーム分の時間(1.0 / g.game.fps)を指定する
// ※ 今回はさらに精度を増すために、処理をさらに細分化する(10分割)
var accuracy = 10;
for (var i = 0; i < accuracy; ++i) {
physics.step(1.0 / g.game.fps / accuracy);
}
});
});
g.game.pushScene(scene);
}
/**
* オブジェクトの中心座標を計算する
* @param {Object} obj 中心座標を計算するオブジェクト
*/
function calcCenter(obj) {
return physics.vec2(obj.width / 2, obj.height / 2);
}
/**
* 衝突判定を持つ円を生成する
* @param {g.Scene} scene 描画を行うシーン
* @param {Object} parameter 円の生成パラメータ
*/
function createCircle(scene, parameter) {
// 画像をまとめる空のエンティティを生成
var entity = new g.E({
scene: scene,
width: parameter.appear.width,
height: parameter.appear.height
});
scene.append(entity);
// 表示用の円形を生成
// ※ AkashicEngineでは円を描画することができないので、画像で描画する
var circle = new g.Sprite({
scene: scene,
src: scene.assets["circle"],
srcWidth: 100,
srcHeight: 100,
width: parameter.appear.width,
height: parameter.appear.height
});
entity.append(circle);
// タッチされたとき用の円形スプライトを生成
var circle_touch = new g.Sprite({
scene: scene,
src: scene.assets["circle_touch"],
srcWidth: 100,
srcHeight: 100,
width: parameter.appear.width,
height: parameter.appear.height,
hidden: true
});
entity.append(circle_touch);
// タッチの有無で画像を切り替える
// タッチされたときにタッチ用の画像を表示
entity.pointDown.add(function() {
circle.hide();
circle_touch.show();
});
// タッチが解除されたときに通常時の画像を表示
entity.pointUp.add(function() {
circle.show();
circle_touch.hide();
});
// 表示用の円形と衝突判定を結び付けて返す
return physics.createBody(entity, parameter.physics.body, parameter.physics.fixture);
}
/**
* 指定されたオブジェクトにマウスジョイントを追加する
* @param {EBody} ebody 描画と衝突判定を持ったオブジェクト
*/
function addMouseJoint(ebody) {
// オブジェクトを触れるようにする
ebody.entity.touchable = true;
// タッチされている座標(Box2D上の絶対座標)
var anchor;
/** 鉄球とマウスを結びつけるジョイント */
var mouseJoint = null;
// オブジェクトがタッチされたときの処理
ebody.entity.pointDown.add(function(event) {
// 既にマウスジョイントが生成されている場合は削除しておく
if (mouseJoint !== null) {
physics.world.DestroyJoint(mouseJoint);
mouseJoint = null;
}
// タッチした位置の絶対座標を計算する
// ※ Box2Dが必要とするタッチ座標は、画面左上を原点とした絶対座標。
// ※ g.PointDownEventから取得できる座標は、ポインティング対象からの
// 相対座標なので、タッチ座標に対象となるb2bodyの回転を与えた後、
// b2bodyの座標を足すことで絶対座標へと変換する。
// ※ 回転の軸をg.Entityの中心にするため、回転の前にタッチ座標から
// 中心座標を引いておく。
anchor = physics.vec2(event.point.x, event.point.y);
anchor.Subtract(calcCenter(ebody.entity));
anchor.MulM(b2Mat22.FromAngle(ebody.b2body.GetAngle()));
anchor.Add(ebody.b2body.GetPosition());
// マウスとオブジェクトの紐づけを作成
var mouseJointDef = new box2d.Box2DWeb.Dynamics.Joints.b2MouseJointDef();
// 紐づける2つの物体
// ※ 今回はマウスと結び付けるので、bodyAにはworld.GetGroundBodyを指定する。
mouseJointDef.bodyA = physics.world.GetGroundBody();
mouseJointDef.bodyB = ebody.b2body;
// bodyAとbodyBの衝突判定を行うかどうか
mouseJointDef.collideConnected = true;
// マウスの引き寄せる力
// ※ この値が大きいと、マウスにぴったりと追従するように動き、
// 小さいと、ヨーヨーのように少し遅れてマウスに追従する。
mouseJointDef.maxForce = 1000.0 * ebody.b2body.GetMass();
// ジョイントの初期位置
// ※ この座標を基準にジョイントが生成されるので、以降マウスの位置を変更しても
// この座標と現在のb2bodyの座標との距離が保たれるような挙動を行う。
mouseJointDef.target = anchor;
// マウスジョイントを生成
mouseJoint = physics.world.CreateJoint(mouseJointDef);
});
// タッチ中の座標が移動したときの処理
ebody.entity.pointMove.add(function(event) {
// タッチ座標を更新する
anchor.Add(physics.vec2(event.prevDelta.x, event.prevDelta.y));
mouseJoint.SetTarget(anchor);
});
// オブジェクトが離されたときの処理
ebody.entity.pointUp.add(function() {
// オブジェクトとマウスの紐づけを解除
physics.world.DestroyJoint(mouseJoint);
mouseJoint = null;
});
}
/**
* 指定されたオブジェクトにディスタンスジョイントを追加する
* @param {Ebody} ebody 衝突判定を持ったオブジェクト
* @param {b2Vec2} fulcrum オブジェクトを固定する座標
* @param {b2Vec2} contact オブジェクト内で固定する座標
*/
function addDistanceJoint(ebody, fulcrum, contact) {
var distanceJointdef = new box2d.Box2DWeb.Dynamics.Joints.b2DistanceJointDef();
distanceJointdef.Initialize(
physics.world.GetGroundBody(), // オブジェクトではなく空間に固定する場合は、このように書く
ebody.b2body,
fulcrum,
contact
);
physics.world.CreateJoint(distanceJointdef);
}
module.exports = main;
© DWANGO Co., Ltd.