Tutorial:PhysicsDrawing (日本語)
これはチュートリアルではなく物理演算プログラムのデバッグで便利な説明付きのコードスニペットです。
Contents
物理演算による衝突の描画
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 の描画
Next up is the PolygonShape. A polygon is a list of points with up to 8 vertices (aka 16 numbers representing x and y. e.g. (x1, y1, x2, y1, ..., xn, yn) )
We can abuse a little bit of functionality of lua to make this a super small snippet of code. We can get all the points by calling "shape:getPoints()" (once we verified it's a polygon shape).
This will return up to 16 individual numbers. However. If we put this directly into another function without following parameter. It will simply put the next number as next parameter.
add = function(a, b)
print( a + b )
end
f = function()
return 1, 2
end
add(2, 2) --> 4 の表示。
add(f()) --> 3 の表示。 We returned multiple parameters which are simply considered as following parameters. So a -> 1, b -> 2.
add(f(), 0) --> 1 の表示。 We returned multiple parameters. However those are directly followed by another value. So only the first return value is used and the others will be discarded. a -> 1, b -> 0
Knowing this we can do the following:
love.graphics.polygon("fill", body:getWorldPoints(shape:getPoints()))
Since there is no following parameter it will simply put as many points in the function as the shape has. We don't have to handle anything else!
EdgeShape と ChainShape の描画
Lastly we have edge and chain shapes. They are essentially the same. Internally they are handled a bit differently. But for our case we can assume an EdgeShape to be the same as a ChainShape with two points. A ChainShape can have two or more points (this is the only shape without a limit).
However. Both are made up of lines that may or may not loop around. And both have the function "shape:getPoints()" as well! So we do not have to treat them separately.
We pretty much do the same as we did for the polygon. But instead of drawing a polygon we draw a line.
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