Tutorial:PhysicsCollisionCallbacks (简体中文)

引言

如果你还不是太了解 love.physics,那么首先看看这个 physics tutorial

在这个教程中我们会对两个物体进行碰撞,并用设置好的World:setCallbacks执行碰撞回调函数。

教程

main.lua 配置

首先配置 main.lua 中的3个 love 函数: love.load, love.update, and love.draw

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

World 配置

现在我们有个框架了,让我们用 newWorld 函数在 love.load 函数中配置 physics world 。 我们还会用到 World:update 函数

function love.load()
        --用变量 "world" 存储创建好的新的物理世界
	world = love.physics.newWorld(0, 200, true)  --设置 X 方向上为 0,Y 方向上为 200 的重力。
end
 
function love.update(dt)
        --永远别忘记了这玩意——更新!
	world:update(dt)
end

现在,我们想创建一个球和一个地面。我们需要使用 newBody, newCircleShape,和 newRectangleShape 函数来创建它们。我们使用 newFixture 函数连接形状到物体上。

function love.load()
    ... -- don't include this, it's just indicating where the existing code for the function should be
 
    ball = {}
        ball.b = love.physics.newBody(world, 400,200, "dynamic")  -- set x,y position (400,200) and let it move and hit other objects ("dynamic")
        ball.b:setMass(10)                                        -- make it pretty light
        ball.s = love.physics.newCircleShape(50)                  -- give it a radius of 50
        ball.f = love.physics.newFixture(ball.b, ball.s)          -- connect body to shape
        ball.f:setRestitution(0.4)                                -- make it bouncy
        ball.f:setUserData("Ball")                                -- give it a name, which we'll access later
    static = {}
        static.b = love.physics.newBody(world, 400,400, "static") -- "static" makes it not move
        static.s = love.physics.newRectangleShape(200,50)         -- set size to 200,50 (x,y)
        static.f = love.physics.newFixture(static.b, static.s)
        static.f:setUserData("Block")
end

The objects are there now, but you can't yet see them. Let's draw them.

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

Now we should see a ball fall down and hit a solid rectangle.

World Callbacks

But what if we want more information on the two objects colliding? Now we will use World:setCallbacks to further dissect their collision(s).

First thing we do is set the world callbacks with World:setCallbacks. There are four callbacks for a collision: beginContact, endContact, preSolve, and postSolve.

beginContact gets called when two fixtures start overlapping (two objects collide).
endContact gets called when two fixtures stop overlapping (two objects disconnect).
preSolve is called just before a frame is resolved for a current collision
postSolve is called just after a frame is resolved for a current collision.
function love.load()
    ...  -- substitute for the rest of love.load
 
    world = love.physics.newWorld(0, 200, true)
        --These callback function names can be almost any you want:
        world:setCallbacks(beginContact, endContact, preSolve, postSolve)
 
    text       = ""   -- we'll use this to put info text on the screen later
    persisting = 0    -- we'll use this to store the state of repeated callback calls

Now define each function you just named.

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

These functions are called every time one of the collision actions happen. They pass in two fixtures and a collision object. The postsolve callback also contains the normal and tangent impulse for each collision contact point. These parameters can also be named to whatever you want. In this tutorial, we choose a, b, and coll.

  • a is the first fixture object in the collision.
  • b is the second fixture object in the collision.
  • coll is the contact object created.
  • normalimpulse is the amount of impulse applied along the normal of the first point of collision. It only applies to the postsolve callback, and we can ignore it for now.
  • tangentimpulse is the amount of impulse applied along the tangent of the first point of collision. It only applies to the postsolve callback, and we can ignore it for now.

Say we want to print to screen whenever a callback is called. We just need to modify the text variable we added to love.load() earlier by appending a string every time a collision action happens. We need a bit of extra code to keep the output clean too.

A list of functions you can use on contacts can be found at the Contact page.

function love.update(dt)
    ... -- substitute for the rest of love.update
 
    if string.len(text) > 768 then    -- cleanup when 'text' gets too long
        text = "" 
    end
end
 
function love.draw()
    ... -- substitute for the rest of 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    -- reset since they're no longer touching
    text = text.."\n"..a:getUserData().." uncolliding with "..b:getUserData()
end
 
function preSolve(a, b, coll)
    if persisting == 0 then    -- only say when they first start touching
        text = text.."\n"..a:getUserData().." touching "..b:getUserData()
    elseif persisting < 20 then    -- then just start counting
        text = text.." "..persisting
    end
    persisting = persisting + 1    -- keep track of how many updates they've been touching for
end
 
function postSolve(a, b, coll, normalimpulse, tangentimpulse)
-- we won't do anything with this function
end

And now you know how to use world callbacks!

To better explore how this world behaves and see when the callbacks are invoked, add some controls to allow you to push around the ball:

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    -- cleanup when 'text' gets too long
        text = "" 
    end
end


Finished

截图

Screenshot of the finished product.

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)    -- make it bouncy
        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       = ""   -- we'll use this to put info text on the screen later
    persisting = 0    -- we'll use this to store the state of repeated callback calls
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    -- cleanup when 'text' gets too long
        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    -- only say when they first start touching
        text = text.."\n"..a:getUserData().." touching "..b:getUserData()
    elseif persisting < 20 then    -- then just start counting
        text = text.." "..persisting
    end
    persisting = persisting + 1    -- keep track of how many updates they've been touching for
end
 
function postSolve(a, b, coll, normalimpulse, tangentimpulse)
end



Other languages