物理エンジンライブラリ Box2DFlashAS3 ジョイント3(滑車ジョイント・マウスジョイント)

滑車ジョイントとマウスジョイントについてのメモ。
サンプルコードでは、距離ジョイントも使ってます。

ついでに任意で物体に力を加える方法も。

滑車ジョイント(PulleyJoint)

  • 滑車の上に紐で繋がった物体をかけたようなジョイント。
  • 二つの滑車を軸に、紐の長さ分だけ物体が動く。
  • 紐の長さ、伸びやすさ(ゴムっぽくしたり)を指定できる。

使い方

  • 滑車の場所(b2Vec2)を左右指定。
  • 滑車に繋げる物体を指定(b2Body)。
  • ジョイントの性質(b2PulleyJointDef)を設定(長さ等)
  • ジョイントの初期化→ジョイント登録

マウスジョイント(MouseJoint)

  • マウスと物体を繋ぐジョイント。
  • マウス(座標)と他の物体を繋ぐだけ。
  • マウスを動かしたら付いてくる、というような動作は自力で実装。

使い方

  • マウス押した時に、マウス座標の下にある物体を取得(これは自力で作る)
  • マウス座標、取得した物体、引っ張る力など設定(b2MouseJointDef)
  • ジョイント作成(b2MouseJoint)
  • 一定時間毎(EnterFrameイベントなど)マウスドラッグ時のマウス座標を反映させる
  • マウス放したらジョイントを削除

物体に任意で力を加える

bb.ApplyForce(new b2Vec2(0, 200), bb.GetPosition());
(bbはb2Bodyオブジェクト。力を加える方向と、力を加える位置を指定する)

サンプルコード

実行結果(きのこの島)
きのこの島

※Box2Dのまとめたクラス(Box2DBase)を作って、基本クラスで使ってます。ソースは物理エンジンライブラリ Box2DFlashAS3(基本)にあります。

マウスジョイントの辺りは、Box2Dの付属コード、TestBedのTest.asのサンプルからほぼコピペです。

[as]
public class StudyB2Joint3 extends B2Base
{
private var m_display_blocks:Sprite; //表示用

private var m_left_fook1:b2Body;
private var m_left_fook2:b2Body;
private var m_left_box1:b2Body;
private var m_left_box2:b2Body;

public function StudyB2Joint3() : void{

super(24, 10, 10);

//滑車ときのこ表示用
this.m_display_blocks = new Sprite();
this.addChild(this.m_display_blocks);

//水面
var water:MovieClip = new water_mc;
water.y = 350;
this.addChild(water);

//上下左右の壁作成
this.makeB2Walls();

//島作成
this.makeShima();

//滑車表示用のMC
var kassha_left_fook1:Sprite = new kinoko_fook_mc as Sprite;
var kassha_left_fook2:Sprite = new kinoko_fook_mc as Sprite;
var kassha_left_box1:Sprite = new board_100_mc as Sprite;
var kassha_left_box2:Sprite = new board_100_mc as Sprite;

var kassha_right_fook1:Sprite = new kinoko_fook_mc as Sprite;
var kassha_right_fook2:Sprite = new kinoko_fook_mc as Sprite;
var kassha_right_box1:Sprite = new board_50_mc as Sprite;
var kassha_right_box2:Sprite = new board_50_mc as Sprite;

//左の島の滑車
this.makeKassha(200, 175, 75, 175, 100, 10, 250,
kassha_left_fook1, kassha_left_fook2,
kassha_left_box1, kassha_left_box2);
//右の島の滑車
this.makeKassha(150, 450, 50, 150, 50, 10, 200,
kassha_right_fook1, kassha_right_fook2,
kassha_right_box1, kassha_right_box2);

//きのこ
this.makeKinokos();

//this.setDebugDraw();

this.addEventListener(Event.ENTER_FRAME, this.update, false, 0, true);

this.addEventListener(MouseEvent.MOUSE_DOWN, this.onMouseDown, false, 0, true);
this.addEventListener(MouseEvent.MOUSE_UP, this.onMouseUp, false, 0, true);
}

//上下左右の壁作成
private function makeB2Walls():void {

this.makeB2BodyBox(700, 10, 300, -50, 0, 0, 0.5, 0.5); //上
this.makeB2BodyBox(700, 10, 300, 550, 0, 0, 0.5, 0.5); //下
this.makeB2BodyBox(10, 600, -50, 250, 0, 0, 0.5, 0.5); //左
this.makeB2BodyBox(10, 600, 650, 250, 0, 0, 0.5, 0.5); //右
}

//島作成
private function makeShima():void {

this.makeB2BodyBox(100, 350, 175, 425, 0, 0, 0.5, 0.5);
this.makeB2BodyBox(100, 400, 450, 400, 0, 0, 0.5, 0.5);
}

//滑車作成
private function makeKassha(pulley_w:int, pulley_x:int, pulley_y:int,
fook_y:int,
box_w:int, box_h:int, box_y:int,
disp_fook1:Sprite = null, disp_fook2:Sprite = null,
disp_box1:Sprite = null, disp_box2:Sprite = null):void {

var x1:Number = pulley_x – (pulley_w / 2);
var x2:Number = pulley_x + (pulley_w / 2)

//フックに繋がる箱
var box1:b2Body = this.makeB2BodyBox(box_w, box_h, x1, box_y, 0, 1, 0.5, 0.5, disp_box1, this.m_display_blocks);
var box2:b2Body = this.makeB2BodyBox(box_w, box_h, x2, box_y, 0, 1, 0.5, 0.5, disp_box2, this.m_display_blocks);

//フック
var fook1:b2Body = this.makeB2BodyBox(10, 10, x1, fook_y, 0, 1, 0.5, 0.5, disp_fook1, this.m_display_blocks);
var fook2:b2Body = this.makeB2BodyBox(10, 10, x2, fook_y, 0, 1, 0.5, 0.5, disp_fook2, this.m_display_blocks);

//滑車
var ga1:b2Vec2 = this.createB2Vec(x1, pulley_y);
var ga2:b2Vec2 = this.createB2Vec(x2, pulley_y);

//滑車ジョイント作成
var pj_def:b2PulleyJointDef = new b2PulleyJointDef;
pj_def.Initialize(fook1, fook2, ga1, ga2, fook1.GetPosition(), fook2.GetPosition(), 1);

this.m_b2_world.CreateJoint(pj_def);

//フックは他のものと衝突しないようにする
fook1.GetShapeList().m_filter.maskBits = 0;
fook2.GetShapeList().m_filter.maskBits = 0;

//距離ジョイントで左の箱とフックつなぐ
var box1_anc_L:b2Vec2 = this.createB2Vec(x1 – (box_w / 2) + 5, box_y);
var box1_anc_R:b2Vec2 = this.createB2Vec(x1 + (box_w / 2) – 5, box_y);

var dj_def1_L:b2DistanceJointDef = new b2DistanceJointDef;
dj_def1_L.Initialize(fook1, box1, fook1.GetPosition(), box1_anc_L);

this.m_b2_world.CreateJoint(dj_def1_L);

var dj_def1_R:b2DistanceJointDef = new b2DistanceJointDef;
dj_def1_R.Initialize(fook1, box1, fook1.GetPosition(), box1_anc_R);

this.m_b2_world.CreateJoint(dj_def1_R);

//距離ジョイントで右の箱とフックつなぐ
var box2_anc_L:b2Vec2 = this.createB2Vec(x2 – (box_w / 2) + 5, box_y);
var box2_anc_R:b2Vec2 = this.createB2Vec(x2 + (box_w / 2) – 5, box_y);

var dj_def2_L:b2DistanceJointDef = new b2DistanceJointDef;
dj_def2_L.Initialize(fook2, box2, fook2.GetPosition(), box2_anc_L);

this.m_b2_world.CreateJoint(dj_def2_L);

var dj_def2_R:b2DistanceJointDef = new b2DistanceJointDef;
dj_def2_R.Initialize(fook2, box2, fook2.GetPosition(), box2_anc_R);

this.m_b2_world.CreateJoint(dj_def2_R);
}

//きのこを降らせる
private function makeKinokos():void {

//滑車の上に
this.makeKinoko(75, 225);
this.makeKinoko(257, 225);

this.makeKinoko(375, 175);
this.makeKinoko(525, 175);

//島の上に
this.makeKinoko(150, 0);
this.makeKinoko(200, 0);

this.makeKinoko(425, 0);
this.makeKinoko(475, 0);
}

//きのこ作成
private function makeKinoko(x:Number, y:Number):void {

var kinoko:Sprite;
var land = Math.random();

if (land >= 0.6) {
//普通のきのこ
kinoko = new kinoko_S1_MC as Sprite;
}
else if(land >= 0.3){
//怪しいきのこ
kinoko = new kinoko_S2_MC as Sprite;
}
else {
//しめじ
kinoko = new kinoko_S3_MC as Sprite;
}

this.makeB2BodyCircle(30, x, y, 1.0, 0.5, 0.5, kinoko, this.m_display_blocks);
}

//演算
private function update(e:Event):void{

//B2Boxの時間進める
this.m_b2_world.Step(this.m_b2_timeStep, this.m_b2_iterations);

//マウス座標設定
this.updateMouseWorld();

//マウスドラッグ時の設定
this.mouseDrag();

//滑車とフック・箱の間の線を引く
var self:StudyB2Joint3 = this;
//Body位置から描画位置移動する関数
var moveTo_Pos:Function = function(graphics:Graphics, anchor:b2Vec2):void {

var x:Number = anchor.x * self.m_b2_physcale;
var y:Number = anchor.y * self.m_b2_physcale;

graphics.moveTo(x, y);
}

//Body位置へ線を引く関数
var lineTo_Pos:Function = function(graphics:Graphics, anchor:b2Vec2):void {

var x:Number = anchor.x * self.m_b2_physcale;
var y:Number = anchor.y * self.m_b2_physcale;

graphics.lineTo(x, y);
}

//線表示用ディスプレイオブジェクト
var disp_gra:Graphics = this.m_display_blocks.graphics;
disp_gra.clear();
disp_gra.lineStyle(1, 0x9FD06F);

//worldに登録してあるBodyの動きを描画する
for (var bb:b2Body = this.m_b2_world.m_bodyList; bb; bb = bb.m_next){
if (bb.IsDynamic()) {

//Bodyの動きにあわせて中のSpriteを動かす
if (bb.m_userData is Sprite){
//メートルからピクセル単位に変換
bb.m_userData.x = bb.GetPosition().x * m_b2_physcale;
bb.m_userData.y = bb.GetPosition().y * m_b2_physcale;
bb.m_userData.rotation = bb.GetAngle() * (180/Math.PI);
}

//ジョイントを持ってるもののみ、線を描画
if (bb.m_jointList is b2JointEdge) {
for (var joint:b2Joint = bb.m_jointList.joint; joint; joint=joint.m_next){
//ジョイントのタイプで分岐)
if (joint.m_type == b2Joint.e_pulleyJoint) {
//滑車ジョイントの場合(滑車とフックを線で繋ぐ)
var joint_p:b2PulleyJoint = joint as b2PulleyJoint;
moveTo_Pos(disp_gra, joint_p.GetGroundAnchor1());
lineTo_Pos(disp_gra, joint_p.GetAnchor1());
moveTo_Pos(disp_gra, joint_p.GetGroundAnchor2());
lineTo_Pos(disp_gra, joint_p.GetAnchor2());
}
else {
//その他のジョイント(アンカー同士を線で繋ぐ)
//ここでは距離ジョイントとマウスジョイント
moveTo_Pos(disp_gra, joint.GetAnchor1());
lineTo_Pos(disp_gra, joint.GetAnchor2());
}
}
}

//水面に浮かぶような感じを出してみる(物体に任意で力を加える)
var bb_y:Number = bb.GetPosition().y * m_b2_physcale;
if (bb_y > 350) {
//水中は上方向に力を加える
var force:b2Vec2;
if (bb_y > 500) {
//深いところは多めに力を加える
force = new b2Vec2(0, -200);
}
else {
force = new b2Vec2(0, -100);
}
bb.ApplyForce(force, bb.GetPosition());
}
else if(bb_y <= 350 && bb_y > 250){
if (bb.GetLinearVelocity().y < 0) { //水面近くは下方向に力を加える(上方向に向かってる場合のみ) bb.ApplyForce(new b2Vec2(0, 200), bb.GetPosition()); } } } } } //マウスジョイント設定 --------------------------------------------------------------------- //Box2D付属のGeneralのInputをほぼこコピペ // world mouse position private var m_mouseJoint:b2MouseJoint; //マウスジョイント private var m_mouseXWorldPhys:Number; //メートル単位のマウス座標 private var m_mouseYWorldPhys:Number; private var m_mouse_dowm:Boolean = false; //マウスボタン押されてるか private function onMouseDown(e:Event):void { this.m_mouse_dowm = true; } private function onMouseUp(e:Event):void { this.m_mouse_dowm = false; } //マウス座標取得 public function updateMouseWorld():void{ //メートル単位のマウス座標に変換 m_mouseXWorldPhys = this.mouseX / this.m_b2_physcale; m_mouseYWorldPhys = this.mouseY / this.m_b2_physcale; } //マウスドラッグ時 public function mouseDrag():void{ //マウスボタン押した(ジョイントまだ) if (this.m_mouse_dowm && !this.m_mouseJoint){ var body:b2Body = GetBodyAtMouse(); //マウスの下の物体取得 if (body) { var md:b2MouseJointDef = new b2MouseJointDef(); md.body1 = this.m_b2_world.GetGroundBody(); md.body2 = body; md.target.Set(this.m_mouseXWorldPhys, this.m_mouseYWorldPhys); md.maxForce = 300000.0 * body.GetMass(); md.timeStep = this.m_b2_timeStep; m_mouseJoint = this.m_b2_world.CreateJoint(md) as b2MouseJoint; body.WakeUp(); } } //マウスボタン離した if (!this.m_mouse_dowm){ if (this.m_mouseJoint) { this.m_b2_world.DestroyJoint(m_mouseJoint); m_mouseJoint = null; } } //マウス押されてる状態(ジョイントされてる) if (this.m_mouse_dowm && this.m_mouseJoint) { var p2:b2Vec2 = new b2Vec2(this.m_mouseXWorldPhys, this.m_mouseYWorldPhys); m_mouseJoint.SetTarget(p2); } } //マウスの下の物体取得 private var mousePVec:b2Vec2 = new b2Vec2(); public function GetBodyAtMouse(includeStatic:Boolean=false):b2Body{ // Make a small box. mousePVec.Set(this.m_mouseXWorldPhys, this.m_mouseYWorldPhys); var aabb:b2AABB = new b2AABB(); aabb.lowerBound.Set(this.m_mouseXWorldPhys - 0.001, this.m_mouseYWorldPhys - 0.001); aabb.upperBound.Set(this.m_mouseXWorldPhys + 0.001, this.m_mouseYWorldPhys + 0.001); // Query the world for overlapping shapes. var k_maxCount:int = 10; var shapes:Array = new Array(); var count:int = this.m_b2_world.Query(aabb, shapes, k_maxCount); var body:b2Body = null; for (var i:int = 0; i < count; ++i) { if (shapes[i].GetBody().IsStatic() == false || includeStatic) { var tShape:b2Shape = shapes[i] as b2Shape; var inside:Boolean = tShape.TestPoint(tShape.GetBody().GetXForm(), mousePVec); if (inside) { body = tShape.GetBody(); break; } } } return body; } } [/as]