kkana's blog

新米コーダーの忘れそうなことメモ

Box2dWebを使ってみる(2)

bodyを作ったので、アニメーションのためのループしながら、ワールドから座標を取得してcanvasに書いてみます。

前回

tuitui.hatenablog.com

作ったもの

See the Pen box2dを使ってみる(2) by kana (@kanaparty) on CodePen.

作成したbodyのbox2dワールドでの位置と、canvasに描くときの位置が食い違わないように注意する

前回も書きましたがBox2Dはメートル法なので、canvas上では1m何pxなのか決めること。
また、Box2Dではbodyの中心点の座標を使用したり、高さと幅は半分を使用したりします。 canvasに置き換えたときの計算を間違えるとbox2D上ではうまく処理できていても
見た目が変だったりすることがあるので、この辺は確認しながら作っていきます。

以下注意点。

  • ボックスとして定義するとき(b2PolygonShape.SetAsBox)は半分の横幅を指定する

リファレンスにも書いてあるけれど...

http://www.box2dflash.org/docs/2.1a/reference/

  • 座標を設定するとき(Box2D.Dynamics.position.Set)にはbodyの中心点の座標を渡す

  • Box2D内の1mを何pxで書くか決めて、それを変換しながら使うこと

変換用の関数を用意しておくと楽です。

//座標変換用 box2Dワールドからcanvasでの大きさに変えるとき
function convertToCanvasScale(box2dWorldscale) {
    return box2dWorldscale * worldScale;
}
//座標変換用 canvasからbox2Dワールドでの大きさに変えるとき
function convertToWorldScale(canvsScale) {
    return canvsScale / worldScale;
}
  • 止まってるかどうか調べる(必要であれば)

b2Body.IsAwake();でtureが返ってきたらまだ動いています。

  • 追記:回転するときの軸に注意する。 Box2Dでは物体の中心点からの回転した角度が得られるので、canvasで書くときも、物体の中心を座標の中心に置き換えて書く必要がある。

食い違いがおこらないようにデバッグモードで確認する

b2DebugDrawというのが用意されているので、それを使うと作成したbodyを表示して確認することができます。 表示設定もできます。 まとめておいたほうが便利そうです。

function debugDraw() {
    var debugDraw = new b2DebugDraw();
    //キャンバスの指定です。
    //今回は既にdocument.getElementById('canvas').getContext('2d')を変数ctxに入れているのでそれを引数にしてます。
    debugDraw.SetSprite(ctx);
    //スケールの設定。worldを宣言したときに決めたworldScaleを渡して大きく1m30pxで表示させます。
    debugDraw.SetDrawScale(worldScale);
    //見た目の設定
    debugDraw.SetFillAlpha(0.5);
    debugDraw.SetLineThickness(1.0);
    //シェイプとジョイントの両方を描きます。
    debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);
    //ワールドに設定
    world.SetDebugDraw(debugDraw);
}

デバックモードで描くときはアニメーションループの中で以下のように呼び出します。

function loop() {
    //ワールドの時間を進めます。1 / 60は固定値?
    world.Step(1 / 60, 10, 10);
    //デバッグ用のデータを呼び出す
    world.DrawDebugData();
    world.ClearForces();
    //さっきまとめておいた処理
    debugDraw();

    requestAnimationFrame(loop);
}

loop();

canvasに書いていく

アニメーションのループの中で、worldから位置のリストを取り出していきます。

function loop() {

    // キャンバスをクリアする
    ctx.clearRect(0, 0, windowWidth, windowHeight);
    //ワールドの時間をすすめる
    world.Step(1 / 60, 10, 10);

    //worldの中に物体の情報が入っている分だけリピート
    for (var b = world.GetBodyList(); b; b = b.GetNext()) {
        var userData = b.GetUserData();
        //意図的に生成したものだけ描く
        if(userData){
            var position = b.GetPosition();
            var angle = b.GetAngle();
            //ボックス
            if(userData.bodyName === 'box'){
                boxArray[userData.order].updateState(position.x, position.y , angle);
                boxArray[userData.order].draw();
            }
            //障害物
            else if(userData.bodyName === 'staticBox'){
                staticBox.updateState(position.x, position.y);
                staticBox.draw();
            }
            //床
            else if(userData.bodyName === 'floor'){
                floor.updateState(position.x, position.y);
                floor.draw();
            }
        }
        else{
        }
    }
    world.ClearForces();
    // world.DrawDebugData();
    // debugDraw();
    requestAnimationFrame(loop);
}

loop();

まずworld.Step(1 / 60, 10, 10);でワールドの時間を進めます。
Box2Dは「この物体は、何秒後に ここにいます」という情報を返してくれるので 1フレームごとにその時間を進めます。(引数はよくわかってないんですが固定・・・?)

for (var b = world.GetBodyList(); b; b = b.GetNext()) {
//ここにすべてのbodyに対しての処理を書く
}

次にworld内にあるbodyの数だけ処理をします。
world.GetBodyList();でbodyのリストを取得、.GetNext()で次の情報を取得。最後まで来るとnullが帰ってきます。
またbodyに作成した順と逆から処理されているみたいです。
(さらにアタッチした数より1つ多く処理が入るんですがこれはworld自身なのかな・・?)

今回は自分が意図的に作成したobjectだけをcanvasに書きたかったので、BodyDef.userDataに名前など保存しておいて、 canvasに書くときにどのオブジェクトなのか判断することにしています。

b.GetPosition();で座標が
b.GetAngle();で中心点の回転角度が得られます。

こうして得られた座標をcanvas用に変換して(前述)書きます。

以上でBox2Dを使って 単純な四角を 静止した単純な四角に ぶつけるアニメーションの完成!


余談 canvasでの回転

canvasで回転させるときは以下のように書く。
saveして状態を保存、座標を変えて回転させて、保存した状態にもどす。

//描く処理
draw() {
  //いままでのcanvasの設定を保存
  ctx.save();
 //座標の基準点を移動
  ctx.translate(this.leftTopX, this.leftTopY);
  //移動した後に基準点を中心に回転させる
  ctx.rotate(this.angle);
  //四角を書く
  ctx.fillStyle = this.fillColor;
  ctx.fillRect(0, 0, this.width, this.height);
  //先ほど保存した設定に戻す。
  ctx.restore();
}

次回

tuitui.hatenablog.com