Tutorial:PhysicsCollisionCallbacks (日本語)

序文

love.physics に関して十分な理解をしていないのであれば、最初に物理演算のチュートリアルを確認してください。

このチュートリアルでは World:setCallbacks により設定された特定のコールバックを呼び出して二つのオブジェクト間の衝突を作成します。


チュートリアル

main.lua の設定

三種類の LOVE 関数を main.lua へ設定するところから開始しましょう: love.load, love.update, および love.draw です。

function love.load()
 
end
function love.update(dt)
 
end
function love.draw()
 
end

World の設定

フレームワークの設定を行う必要があるので、 newWorld により love.load物理世界の設定を行います。 さらに World:update も使用します。

function love.load()
	-- "world" といった変数へ新世界を格納します
	world = love.physics.newWorld(0, 200, true)  -- 重力を y 方向へ 0 および x 方向へ 200 に設定します。
end
 
function love.update(dt)
	-- 世界における各フレームの更新を忘れずに。
	world:update(dt)
end

玉と地面を作成したいとします。これを行うためには newBody, newCircleShape, および newRectangleShape を必要とします。 newFixture を使用して形状に物体を接続します。

function love.load()
    ... -- これは関数に対して既存のコードが何処に存在する必要があるのかを単に示しているだけなので挿入しないでください
 
    ball = {}
        ball.b = love.physics.newBody(world, 400,200, "dynamic")  -- x,y の位置を (400,200) 設定してから移動を行い別のオブジェクトと当たります ("dynamic")。
        ball.b:setMass(10)                                        -- 非常に軽くします
        ball.s = love.physics.newCircleShape(50)                  -- 半径 50 を指定します
        ball.f = love.physics.newFixture(ball.b, ball.s)          -- 形状へ物体を接続します
        ball.f:setRestitution(0.4)                                -- 弾力性を持たせます
        ball.f:setUserData("Ball")                                -- 後でアクセスするために、名前を付与します
    static = {}
        static.b = love.physics.newBody(world, 400,400, "static") -- "static" は不動体です
        static.s = love.physics.newRectangleShape(200,50)         -- (x,y) へ 200,50 の大きさを設定します
        static.f = love.physics.newFixture(static.b, static.s)
        static.f:setUserData("Block")
end

これでオブジェクトは存在するようになりましたが、まだ表示されていませんので描画を行いましょう。

function love.draw()
    love.graphics.circle("line", ball.b:getX(),ball.b:getY(), ball.s:getRadius(), 20)
    love.graphics.polygon("line", static.b:getWorldPoints(static.s:getPoints()))
end

これで玉は落下してから固体の長方形に当たっているのが表示されます。

World コールバック

しかし、二つのオブジェクトの衝突に関して、より詳細な情報を希望する場合はどうしますか? その場合は World:setCallbacks を使用して衝突をさらに細かく調べます。

最初にすることは World:setCallbacks にて World コールバックを設定します。 衝突に対して四種類のコールバックがあります: beginContact, endContact, preSolve, および postSolve です。 beginContact 二つの取付具が並行し始める時に呼ばれます (二つのオブジェクトの衝突)。 endContact 二つの取付具が並行を終える時に呼ばれます (二つのオブジェクトの切断)。 preSolve はフレームが現在の衝突に対して解決される前に呼ばれます。 postSolve はフレームが現在の衝突に対して解決された後に呼ばれます。

function love.load()
    ...  -- love.load の該当部分を置き換えたものです
 
    world = love.physics.newWorld(0, 200, true)
        -- これらのコールバック関数の名前は、ほぼ希望する任意のものにできます:
        world:setCallbacks(beginContact, endContact, preSolve, postSolve)
 
    text       = ""   -- これは後で画面上にテキストを出力するために使用します
    persisting = 0    -- これは繰り返し呼び出されたコールバックの状態を格納するために使用します

さて名前を付けた各関数を定義します。

function beginContact(a, b, coll)
   
end
 
function endContact(a, b, coll)
   
end
 
function preSolve(a, b, coll)
   
end
 
function postSolve(a, b, coll, normalimpulse, tangentimpulse)
   
end

これら関数は毎回呼び出されて衝突動作のうち一つが発生します。それらに対しては二つの取付具および衝突オブジェクトを渡します。 postsolve コールバックは各衝突接点に対する法線および接線の力積を内包しています。これらの引数に関しても希望する任意の名前付けをすることができます。このチュートリアルでは a, b, および coll を選択します。

  • a は衝突する一番目の取付具オブジェクトです。
  • b は衝突する二番目の取付具オブジェクトです。
  • coll は作成された接点オブジェクトです。
  • normalimpulse は第一地点の衝突における法線に沿って適用される衝突量です。それは postsolve のみ適用を行うため、今のところそれを無視することができます。
  • tangentimpulse は第一地点の衝突における接線に沿って適用される衝突量です。それは postsolve のみ適用を行うため、今のところそれを無視することができます。

例えばコールバックが呼び出された場合は常に画面へ表示したいします。衝突が発生するたびに文字列を追記するために love.load() の最初にtext 変数を追加して変更する必要があります。また出力を清潔にしておくために追加のコードが少し必要です。

接点で使用可能な関数の一覧は接点のページにあります。

function love.update(dt)
    ... -- love.update の該当部分を置き換えたものです
 
    if string.len(text) > 768 then    -- 'テキスト'が長くなりすぎた場合に消去します
        text = ""
    end
end
 
function love.draw()
    ... -- love.draw の該当部分を置き換えたものです
 
    love.graphics.print(text, 10, 10)
end
 
function beginContact(a, b, coll)
    x,y = coll:getNormal()
    text = text.."\n"..a:getUserData().." colliding with "..b:getUserData().." with a vector normal of: "..x..", "..y
end
 
 
function endContact(a, b, coll)
    persisting = 0    -- 既に接触していないので再初期化設定を行います
    text = text.."\n"..a:getUserData().." uncolliding with "..b:getUserData()
end
 
function preSolve(a, b, coll)
    if persisting == 0 then    -- 最初に接触を開始したときのみ伝えます
        text = text.."\n"..a:getUserData().." touching "..b:getUserData()
    elseif persisting < 20 then    -- 次にカウントを開始します
        text = text.." "..persisting
    end
    persisting = persisting + 1    -- 接触に対する更新回数の追跡します
end
 
function postSolve(a, b, coll, normalimpulse, tangentimpulse)
-- この関数に対してはなにもしません
end

これで World コールバックの使用方法を理解できるようになりました!

この世界がどのような挙動でありいつ呼び出されるか確認する場合は玉の周辺で押し出すことができるようにするために制御を追加します:

function love.update(dt)
    world:update(dt)
 
    if love.keyboard.isDown("right") then
        ball.b:applyForce(1000, 0)
    elseif love.keyboard.isDown("left") then
        ball.b:applyForce(-1000, 0)
    end
    if love.keyboard.isDown("up") then
        ball.b:applyForce(0, -5000)
    elseif love.keyboard.isDown("down") then
        ball.b:applyForce(0, 1000)
    end
 
    if string.len(text) > 768 then    -- 'テキスト'が長くなりすぎた場合に消去します
        text = ""
    end
end


完成

画面撮影

完成品の画面撮影物。

main.lua

function love.load()
    world = love.physics.newWorld(0, 200, true)
        world:setCallbacks(beginContact, endContact, preSolve, postSolve)
    ball = {}
 
        ball.b = love.physics.newBody(world, 400,200, "dynamic")
        ball.b:setMass(10)
        ball.s = love.physics.newCircleShape(50)
        ball.f = love.physics.newFixture(ball.b, ball.s)
        ball.f:setRestitution(0.4)    -- 弾力性を持たせます
        ball.f:setUserData("Ball")
    static = {}
        static.b = love.physics.newBody(world, 400,400, "static")
        static.s = love.physics.newRectangleShape(200,50)
        static.f = love.physics.newFixture(static.b, static.s)
        static.f:setUserData("Block")
 
    text       = ""   -- これは後で画面上にテキストを出力するために使用します
    persisting = 0    -- これは繰り返し呼び出されたコールバックの状態を格納するために使用します
end
 
function love.update(dt)
    world:update(dt)
 
    if love.keyboard.isDown("right") then
        ball.b:applyForce(1000, 0)
    elseif love.keyboard.isDown("left") then
        ball.b:applyForce(-1000, 0)
    end
    if love.keyboard.isDown("up") then
        ball.b:applyForce(0, -5000)
    elseif love.keyboard.isDown("down") then
        ball.b:applyForce(0, 1000)
    end
 
    if string.len(text) > 768 then    -- 'テキスト'が長くなりすぎた場合に消去します
        text = ""
    end
end
 
function love.draw()
    love.graphics.circle("line", ball.b:getX(),ball.b:getY(), ball.s:getRadius(), 20)
    love.graphics.polygon("line", static.b:getWorldPoints(static.s:getPoints()))
 
    love.graphics.print(text, 10, 10)
end
 
function beginContact(a, b, coll)
    x,y = coll:getNormal()
    text = text.."\n"..a:getUserData().." colliding with "..b:getUserData().." with a vector normal of: "..x..", "..y
end
 
function endContact(a, b, coll)
    persisting = 0
    text = text.."\n"..a:getUserData().." uncolliding with "..b:getUserData()
end
 
function preSolve(a, b, coll)
    if persisting == 0 then    -- 最初に接触を開始したときのみ伝えます
        text = text.."\n"..a:getUserData().." touching "..b:getUserData()
    elseif persisting < 20 then    -- 次にカウントを開始します
        text = text.." "..persisting
    end
    persisting = persisting + 1    -- 接触に対する更新回数の追跡します
end
 
function postSolve(a, b, coll, normalimpulse, tangentimpulse)
end



そのほかの言語