Tutorial:PhysicsDrawing (日本語)

これはチュートリアルではなく物理演算プログラムのデバッグで便利な説明付きのコードスニペットです。

物理演算による衝突の描画

Box2D (aka love.physics) で開発中に、ちょっとした間違いで想定していた衝突地点と全く当たらない (一致しない) ことがあると思います。

このガイドの最後に、正しい位置で衝突処理を全て描画する完成版のコードスニペットを掲載してあります。コードだけが必要でした遠慮せずにガイドの最後へ移動してコピーと貼り付けをしてください。

はい、注目。 love.physics には動作の違うオブジェクトが数種類あります ー "World" (世界、ワールド), "Body" (物体), "Shape" (形状) および"Fixture" (取付具)。

  • "World" は物理演算 (剛体力学) が作用する世界、あるいは舞台です。オブジェクトを多数配置できますが、同じ World にあるオブジェクト同士に限り衝突できます。
  • "Body" (物体) は物理演算 (剛体力学) が作用する世界に配置されたオブジェクトです。オブジェクトは移動可能であり質量がある場合は (デフォルトでは Shape の寸法から計算します)、このオブジェクトの所属先となる世界を定義します。
  • "Shape" は自己衝突します。二個の Shape が衝突する場合は、物理演算システムで処理します。また Fixture の参照を維持します。
  • "Fixture" は Body へ Shape を接続する方法を指定します。衝突の処理方法に関するプロパティを定義します (摩擦、衝突チャンネルなど)。また Body と Shape の参照を維持します。

Shape をすべて描画には、ワールドを創造する必要があります。これで衝突をすべて処理します。オブジェクトは必ずリストに収容してください。具体的には、この方法により Body をリストへ格納できます。

listOfBodies = World:getBodyList()

けれども Body のリストは不要です。何らかの処理を Body に施したいだけです。処理をするにはfor ループで取り出します。

for _, body in pairs(World:getBodyList()) do
    -- 執筆予定
end

Body には複数の Fixture を取り付けた複数 Shape を構成できます。そのため Fixture リストもイテレートを行う必要があります。

for _, body in pairs(World:getBodyList()) do
    for _, fixture in pairs(body:getFixtureList()) do
        -- 執筆予定
    end
end

しかしながら Fixture に取り付け可能な Shape は一つだけです。さて、このループには全ての Fixture の衝突に関する基準地点が既にあります!

このようにすることで Shape への参照を格納できます。

for _, body in pairs(World:getBodyList()) do
    for _, fixture in pairs(body:getFixtureList()) do
        local shape = fixture:getShape()

        -- 衝突処理の描画 (執筆予定)
    end
end

さて、描画してみましょう。様々な Shape があります。詳しく説明すると、このようになります。

  • CircleShape
  • PolygonShape
  • EdgeShape
  • ChainShape

CircleShape の描画

まず、 CircleShape から説明しましょう。このコードで描画します。

local cx, cy = body:getWorldPoints(shape:getPoint())
love.graphics.circle("fill", cx, cy, shape:getRadius())

cx & cy は "CircleX" と "CircleY" に対応します。つまり描画したい位置を指定します。

body:getWorldPoints() は点の個数とワールド空間の変換処理をします (x と y の二種類の数値を指定します)。換言すれば、 "x + body:getX()", "y + body:getY()" はすべて指定された点となります。

そのあとに、 Shape の半径を算出して出た位置へ円を描画します。第一引数には輪を描画する "fill" を指定します。また、円を描画する "line" も使えます。

PolygonShape の描画

お次は PolygonShape です。PolygonShape は最大 8 頂点から成るリストです (x と y を16 の個数で表したものとして知られています。つまり、(x1, y1, x2, y1, ..., xn, yn) です)。

このコードを超小型スペットにするために、ちょっと Lua の機能を悪用します。つまり、 "shape:getPoints()" を呼び出すと全地点を取得できます (PolygonShape であることを確認後に)。

これは最大16までの数値を返します。しかしながら、これを引数の後ではなく別の関数へ直接指定しますと、この引数は下記の数値を出力します。

add = function(a, b)
    print( a + b )
end

f = function()
    return 1, 2
end

add(2, 2) --> 4 の表示。

add(f()) --> 3 の表示。 引数の指定後に複数の引数が返されるものとします。つまり、 a -> 1, b -> 2 です。

add(f(), 0) --> 1 の表示。 複数の引数が返されますが、その後にも値があります。最初の返値のみ使用され、他の値は無視されます。つまり a -> 1, b -> 0 です。

これを理解すると、このようなことができます。

love.graphics.polygon("fill", body:getWorldPoints(shape:getPoints()))

この引数がないため、 Shape の複数地点を関数で簡単に扱えます。それ以外の処理は不要です!

EdgeShape と ChainShape の描画

最後は EdgeShape と ChainShape です。これは本質的には同じものですが、内部処理では少し違うものとして扱われます。けれども、今回は EdgeShape が 2 点から成るChainShape として考えることにしましょう。 ChainShapeは 2 点以上から構成できます (これは唯一制限のない Shape です)。

しかしながら、互いにループする、またはループしない線で構成されています。そして互いに同じ関数である "shape:getPoints()" が使えます! よって、個別に扱わなくても問題ありません。

けれども、多角形の描画ではなく、線の描画となります。

love.graphics.line(body:getWorldPoints(shape:getPoints()))

完成版のコード

画面上の正しい位置で衝突処理を全て描画する完成版のコードスニペットを掲載しておきます!

Happy debugging! (ハッピー・デバッギング!)

for _, body in pairs(world:getBodyList()) do
    for _, fixture in pairs(body:getFixtureList()) do
        local shape = fixture:getShape()

        if shape:typeOf("CircleShape") then
            local cx, cy = body:getWorldPoints(shape:getPoint())
            love.graphics.circle("fill", cx, cy, shape:getRadius())
        elseif shape:typeOf("PolygonShape") then
            love.graphics.polygon("fill", body:getWorldPoints(shape:getPoints()))
        else
            love.graphics.line(body:getWorldPoints(shape:getPoints()))
        end
    end
end

そのほかの言語