Difference between revisions of "Tutorial:PhysicsCollisionCallbacks"

(update to show 0.7.2 compatibility)
(update for 0.8.0 compatibility)
Line 31: Line 31:
 
function love.load()
 
function love.load()
 
--Store the new world in a variable such as "world"
 
--Store the new world in a variable such as "world"
world = love.physics.newWorld(0,0, 800,600)
+
world = love.physics.newWorld(0, 200, true) --Gravity is being set to 0 in the x direction and 200 in the y direction.
--Gravity is being set to 0 in the x direction and 40 in the y direction.
 
world:setGravity(0,40)
 
 
end
 
end
  
Line 42: Line 40:
 
</source>
 
</source>
  
Now we want to create a ball and a ground. To do this we will need  [[love.physics.newBody|newBody]], [[love.physics.newCircleShape|newCircleShape]], and [[love.physics.newRectangleShape|newRectangleShape]].
+
Now we want to create a ball and a ground. To do this we will need  [[love.physics.newBody|newBody]], [[love.physics.newCircleShape|newCircleShape]], and [[love.physics.newRectangleShape|newRectangleShape]].  We'll connect the bodies to the shapes using [[love.physics.newFixture|newFixture]].
  
 
<source lang="lua">
 
<source lang="lua">
Line 49: Line 47:
  
 
     ball = {}
 
     ball = {}
         ball.b = love.physics.newBody(world, 400,200, 10,0)
+
         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.s = love.physics.newCircleShape(ball.b, 0,0, 50)
+
        ball.b:setMass(10)                                       -- make it pretty light
         ball.s:setRestitution(0.4)   -- make it bouncy
+
         ball.s = love.physics.newCircleShape(50)                  -- give it a radius of 50
         ball.s:setData("Ball")
+
        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 = {}
         static.b = love.physics.newBody(world, 400,400, 0,0)
+
         static.b = love.physics.newBody(world, 400,400, "static") -- "static" makes it not move
         static.s = love.physics.newRectangleShape(static.b, 0,0, 200,50, 0)
+
         static.s = love.physics.newRectangleShape(200,50)        -- set size to 200,50 (x,y)
         static.s:setData("Block")
+
         static.f = love.physics.newFixture(static.b, static.s)
 +
        static.f:setUserData("Block")
 
end
 
end
 
</source>
 
</source>
Line 65: Line 66:
 
function love.draw()
 
function love.draw()
 
     love.graphics.circle("line", ball.b:getX(),ball.b:getY(), ball.s:getRadius(), 20)
 
     love.graphics.circle("line", ball.b:getX(),ball.b:getY(), ball.s:getRadius(), 20)
     love.graphics.polygon("line", static.s:getPoints())
+
     love.graphics.polygon("line", static.b:getWorldPoints(static.s:getPoints()))
 
end
 
end
 
</source>
 
</source>
Line 75: Line 76:
  
 
First thing we do is set the world callbacks with [[World:setCallbacks]].
 
First thing we do is set the world callbacks with [[World:setCallbacks]].
There are four callbacks for a collision: add, persist, remove, and result.
+
There are four callbacks for a collision: beginContact, endContact, preSolve, and postSolve.
: Add is the callback for every new collision.
+
: beginContact gets called when two fixtures start overlapping (two objects collide).
: Persist is the callback for every collision that is continuing from last frame.
+
: endContact gets called when two fixtures stop overlapping (two objects disconnect).
: Rem(ove) is the callback for any collision that stopped happening since last frame.
+
: preSolve is called just before a frame is resolved for a current collision
: Result (not yet implemented in Love).
+
: postSolve is called just after a frame is resolved for a current collision
 
<source lang="lua">
 
<source lang="lua">
 
function love.load()
 
function love.load()
 
     ...  -- substitute for the rest of love.load
 
     ...  -- substitute for the rest of love.load
  
     world = love.physics.newWorld(0,0, 800,600)
+
     world = love.physics.newWorld(0, 200, true)
        world:setGravity(0, 40)
 
 
         --These callback function names can be almost any you want:
 
         --These callback function names can be almost any you want:
         world:setCallbacks(add, persist, rem, result)
+
         world:setCallbacks(beginContact, endContact, preSolve, postSolve)
  
 
     text      = ""  -- we'll use this to put info text on the screen later
 
     text      = ""  -- we'll use this to put info text on the screen later
Line 95: Line 95:
 
Now define each function you just named.
 
Now define each function you just named.
 
<source lang="lua">
 
<source lang="lua">
function add(a, b, coll)
+
function beginContact(a, b, coll)
 
 
 
end
 
end
  
function persist(a, b, coll)
+
function endContact(a, b, coll)
 
 
 
end
 
end
  
function rem(a, b, coll)
+
function preSolve(a, b, coll)
 
 
 
end
 
end
  
function result(a, b, coll)
+
function postSolve(a, b, coll)
 
 
 
end
 
end
Line 124: Line 124:
 
     ... -- substitute for the rest of love.update
 
     ... -- substitute for the rest of love.update
  
     if string.len(text) > 512 then    -- cleanup when 'text' gets too long
+
     if string.len(text) > 768 then    -- cleanup when 'text' gets too long
 
         text = ""  
 
         text = ""  
 
     end
 
     end
Line 135: Line 135:
 
end
 
end
  
function add(a, b, coll)
+
function beginContact(a, b, coll)
    persisting = 0    -- reset since every event other than persist means they're not continuing to touch
 
 
     x,y = coll:getNormal()
 
     x,y = coll:getNormal()
     text = text.."\n"..a.." colliding with "..b.." with a vector normal of: "..x..", "..y
+
     text = text.."\n"..a:getUserData().." colliding with "..b:getUserData().." with a vector normal of: "..x..", "..y
 
end
 
end
  
function persist(a, b, coll)
+
 
 +
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
 
     if persisting == 0 then    -- only say when they first start touching
         text = text.."\n"..a.." touching "..b
+
         text.."\n"..a:getUserData().." touching "..b:getUserData()
 
     elseif persisting < 20 then    -- then just start counting
 
     elseif persisting < 20 then    -- then just start counting
 
         text = text.." "..persisting
 
         text = text.." "..persisting
Line 150: Line 155:
 
end
 
end
  
function rem(a, b, coll)
+
function postSolve(a, b, coll)
    persisting = 0
+
-- we won't do anything with this function
    text = text.."\n"..a.." uncolliding with "..b
 
end
 
 
 
function result(a, b, coll)
 
    persisting = 0
 
    text = text.."\n"..a.." hit "..b
 
 
end
 
end
 
</source>
 
</source>
Line 170: Line 169:
  
 
     if love.keyboard.isDown("right") then
 
     if love.keyboard.isDown("right") then
         ball.b:applyForce(100, 0)  
+
         ball.b:applyForce(1000, 0)  
 
     elseif love.keyboard.isDown("left") then
 
     elseif love.keyboard.isDown("left") then
         ball.b:applyForce(-100, 0)  
+
         ball.b:applyForce(-1000, 0)  
 
     end
 
     end
 
     if love.keyboard.isDown("up") then
 
     if love.keyboard.isDown("up") then
         ball.b:applyForce(0, -100)
+
         ball.b:applyForce(0, -5000)
 
     elseif love.keyboard.isDown("down") then
 
     elseif love.keyboard.isDown("down") then
         ball.b:applyForce(0, 100)
+
         ball.b:applyForce(0, 1000)
 
     end
 
     end
  
     if string.len(text) > 512 then    -- cleanup when 'text' gets too long
+
     if string.len(text) > 768 then    -- cleanup when 'text' gets too long
 
         text = ""  
 
         text = ""  
 
     end
 
     end
Line 193: Line 192:
 
<source lang="lua">
 
<source lang="lua">
 
function love.load()
 
function love.load()
     world = love.physics.newWorld(0, 0, 800, 600)
+
     world = love.physics.newWorld(0, 200, true)
        world:setGravity(0, 40)
+
         world:setCallbacks(beginContact, endContact, preSolve, postSolve)
         world:setCallbacks(add, persist, rem, result)
 
  
     ball = {}  
+
     ball = {}
         ball.b = love.physics.newBody(world, 400,200, 10, 0)  
+
         ball.b = love.physics.newBody(world, 400,200, "dynamic")
         ball.s = love.physics.newCircleShape(ball.b, 0,0, 50)
+
        ball.b:setMass(10)
         ball.s:setRestitution(0.4)    -- make it bouncy
+
         ball.s = love.physics.newCircleShape(50)
         ball.s:setData("Ball")
+
        ball.f = love.physics.newFixture(ball.b, ball.s)
     static = {}  
+
         ball.f:setRestitution(0.4)    -- make it bouncy
         static.b = love.physics.newBody(world, 400,400, 0,0)
+
         ball.f:setUserData("Ball")
         static.s = love.physics.newRectangleShape(static.b, 0,0, 200,50, 0)  
+
     static = {}
         static.s:setData("Block")
+
         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
 
     text      = ""  -- we'll use this to put info text on the screen later
Line 215: Line 216:
  
 
     if love.keyboard.isDown("right") then
 
     if love.keyboard.isDown("right") then
         ball.b:applyForce(100, 0)  
+
         ball.b:applyForce(1000, 0)
 
     elseif love.keyboard.isDown("left") then
 
     elseif love.keyboard.isDown("left") then
         ball.b:applyForce(-100, 0)  
+
         ball.b:applyForce(-1000, 0)
 
     end
 
     end
 
     if love.keyboard.isDown("up") then
 
     if love.keyboard.isDown("up") then
         ball.b:applyForce(0, -100)
+
         ball.b:applyForce(0, -5000)
 
     elseif love.keyboard.isDown("down") then
 
     elseif love.keyboard.isDown("down") then
         ball.b:applyForce(0, 100)
+
         ball.b:applyForce(0, 1000)
 
     end
 
     end
  
     if string.len(text) > 512 then    -- cleanup when 'text' gets too long
+
     if string.len(text) > 768 then    -- cleanup when 'text' gets too long
         text = ""  
+
         text = ""
 
     end
 
     end
 
end
 
end
Line 232: Line 233:
 
function love.draw()
 
function love.draw()
 
     love.graphics.circle("line", ball.b:getX(),ball.b:getY(), ball.s:getRadius(), 20)
 
     love.graphics.circle("line", ball.b:getX(),ball.b:getY(), ball.s:getRadius(), 20)
     love.graphics.polygon("line", static.s:getPoints())
+
     love.graphics.polygon("line", static.b:getWorldPoints(static.s:getPoints()))
  
 
     love.graphics.print(text, 10, 10)
 
     love.graphics.print(text, 10, 10)
 
end
 
end
  
function add(a, b, coll)
+
function beginContact(a, b, coll)
    persisting = 0    -- reset since every event other than persist means they're not continuing to touch
 
 
     x,y = coll:getNormal()
 
     x,y = coll:getNormal()
     text = text.."\n"..a.." colliding with "..b.." with a vector normal of: "..x..", "..y
+
     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
 
end
  
function persist(a, b, coll)
+
function preSolve(a, b, coll)
 
     if persisting == 0 then    -- only say when they first start touching
 
     if persisting == 0 then    -- only say when they first start touching
         text = text.."\n"..a.." touching "..b
+
         text = text.."\n"..a:getUserData().." touching "..b:getUserData()
 
     elseif persisting < 20 then    -- then just start counting
 
     elseif persisting < 20 then    -- then just start counting
 
         text = text.." "..persisting
 
         text = text.." "..persisting
Line 252: Line 257:
 
end
 
end
  
function rem(a, b, coll)
+
function postSolve(a, b, coll)
    persisting = 0
 
    text = text.."\n"..a.." uncolliding with "..b
 
end
 
 
 
function result(a, b, coll)
 
    persisting = 0
 
    text = text.."\n"..a.." hit "..b
 
 
end
 
end
 
</source>
 
</source>
Line 265: Line 263:
 
[[Category:Tutorials]]
 
[[Category:Tutorials]]
 
{{#set:Name=World Collision Callbacks Tutorial}}
 
{{#set:Name=World Collision Callbacks Tutorial}}
{{#set:LOVE Version=0.7.2}}
+
{{#set:LOVE Version=0.8.0}}
 
{{#set:Description=A tutorial for learning how to use [[World:setCallbacks]].}}
 
{{#set:Description=A tutorial for learning how to use [[World:setCallbacks]].}}
 
{{#set:Screenshot=File:WorldCallbacksTutorial.png}}
 
{{#set:Screenshot=File:WorldCallbacksTutorial.png}}

Revision as of 23:33, 19 April 2012

Preface

If you do not have a good grasp on love.physics, you should first check out this physics tutorial.

In this tutorial we will create a collision between two objects that calls certain callbacks set by World:setCallbacks.


Tutorial

main.lua setup

I usually start every main.lua with 3 love functions: love.load, love.update, and love.draw

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

World setup

Now that we have a framework setup, let's setup a physics world in love.load with newWorld. Also we will use World:setGravity and World:update.

function love.load()
	--Store the new world in a variable such as "world"
	world = love.physics.newWorld(0, 200, true)  --Gravity is being set to 0 in the x direction and 200 in the y direction.
end

function love.update(dt)
	--Never forget to update your world every frame.
	world:update(dt)
end

Now we want to create a ball and a ground. To do this we will need newBody, newCircleShape, and newRectangleShape. We'll connect the bodies to the shapes using 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)
	
end

These functions are called every time one of the collision actions happen. They pass in two shapes and a collision object. These parameters can also be named to whatever you want. In this tutorial, we choose a, b, and coll.

  • a is the first object in the collision.
  • b is the second object in the collision.
  • coll is the contact object created.

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.."\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)
-- 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

Screenshots

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)
end



Other languages