Difference between revisions of "Tutorial:PhysicsCollisionCallbacks"

m (Other languages (fix))
(cleanup, fix code errors, expand tutorial some)
Line 1: Line 1:
 
__FORCETOC__
 
__FORCETOC__
 
__TOC__
 
__TOC__
 +
 
== Preface ==
 
== Preface ==
 
If you do not have a good grasp on [[love.physics]], you should first check out this [[Tutorial:Physics|physics tutorial]].
 
If you do not have a good grasp on [[love.physics]], you should first check out this [[Tutorial:Physics|physics tutorial]].
  
 
In this tutorial we will create a collision between two objects that calls certain callbacks set by [[World:setCallbacks]].
 
In this tutorial we will create a collision between two objects that calls certain callbacks set by [[World:setCallbacks]].
 +
 +
 
== Tutorial ==
 
== Tutorial ==
 
=== main.lua setup ===
 
=== main.lua setup ===
 
I usually start every main.lua with 3 love functions: [[love.load]], [[love.update]], and [[love.draw]]
 
I usually start every main.lua with 3 love functions: [[love.load]], [[love.update]], and [[love.draw]]
 +
 
<source lang="lua">
 
<source lang="lua">
 
function love.load()
 
function love.load()
Line 22: Line 26:
 
=== World setup ===
 
=== World setup ===
 
Now that we have a framework setup, let's setup a [[World|physics world]] in [[love.load]] with [[love.physics.newWorld|newWorld]].
 
Now that we have a framework setup, let's setup a [[World|physics world]] in [[love.load]] with [[love.physics.newWorld|newWorld]].
 +
Also we will use [[World:setGravity]] and [[World:update]].
  
Also we will use [[World:setGravity]] and [[World:update]].
 
 
<source lang="lua">
 
<source lang="lua">
 
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(-800,-600, 800,600)
+
world = love.physics.newWorld(0,0, 800,600)
--Gravity is being set to 0 in the x direction and 20 in the y direction.
+
--Gravity is being set to 0 in the x direction and 40 in the y direction.
world:setGravity(0,20)
+
world:setGravity(0,40)
 
end
 
end
  
Line 39: Line 43:
  
 
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]].
 +
 
<source lang="lua">
 
<source lang="lua">
 
function love.load()
 
function love.load()
--Store the new world in a variable such as "world"
+
    ... -- don't include this, it's just indicating where the existing code for the function should be
world = love.physics.newWorld(-800,-600, 800,600)
+
 
--Gravity is being set to 0 in the x direction and 20 in the y direction.
+
    ball = {}
world:setGravity(0,20)
+
        ball.b = love.physics.newBody(world, 400,200, 10,0)
ball = {}
+
        ball.s = love.physics.newCircleShape(ball.b, 0,0, 50)
ball.b = love.physics.newBody(world, 400,200, 10,0)
+
        ball.s:setRestitution(0.4)    -- make it bouncy
ball.s = love.physics.newCircleShape(ball.b, 0,0, 50)
+
        ball.s:setData("Ball")
ball.s:setData("Ball")
+
    static = {}
static = {}
+
        static.b = love.physics.newBody(world, 400,400, 0,0)
static.b = love.physics.newBody(world, 400,400, 0,0)
+
        static.s = love.physics.newRectangleShape(static.b, 0,0, 200,50, 0)
static.s = love.physics.newRectangleShape(static.b, -100,-25, 200,50, 0)
+
        static.s:setData("Block")
static.s:setData("Block")
 
 
end
 
end
 
</source>
 
</source>
  
The balls are there now, but you can't yet see them. Let's draw them. This can be a bit tricky at times to match up the origins of objects.
+
The objects are there now, but you can't yet see them. Let's draw them.
 +
 
 
<source lang="lua">
 
<source lang="lua">
 
function love.draw()
 
function love.draw()
love.graphics.circle("line", ball.b:getX(),ball.b:getY(), ball.s:getRadius())
+
    love.graphics.circle("line", ball.b:getX(),ball.b:getY(), ball.s:getRadius(), 20)
local x1,y1, _,_, x3,y3, _,_ = static.s:getBoundingBox()
+
    love.graphics.polygon("line", static.s:getPoints())
local w = x3-x1
 
local h = y3-y1
 
love.graphics.rectangle("line",
 
static.b:getX()-w/2,static.b:getY(),
 
w,h, 0)
 
 
end
 
end
 
</source>
 
</source>
 +
 
Now we should see a ball fall down and hit a solid rectangle.
 
Now we should see a ball fall down and hit a solid rectangle.
  
Line 77: Line 78:
 
: Add is the callback for every new collision.
 
: Add is the callback for every new collision.
 
: Persist is the callback for every collision that is continuing from last frame.
 
: Persist is the callback for every collision that is continuing from last frame.
: Remove is the callback for any collision that stopped happening since last frame.
+
: Rem(ove) is the callback for any collision that stopped happening since last frame.
: Result [do not know what is].
+
: Result (not yet implemented in Love).
 
<source lang="lua">
 
<source lang="lua">
 
function love.load()
 
function love.load()
--Store the new world in a variable such as "world"
+
    ...  -- substitute for the rest of love.load
world = love.physics.newWorld(-800,-600, 800,600)
+
 
--Gravity is being set to 0 in the x direction and 20 in the y direction.
+
    world = love.physics.newWorld(0,0, 800,600)
world:setGravity(0,20)
+
        world:setGravity(0, 40)
--Can be almost any function names you want.
+
        --These callback function names can be almost any you want:
world:setCallbacks(add, persist, rem, result)
+
        world:setCallbacks(add, persist, rem, result)
 +
 
 +
    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
 
</source>
 
</source>
  
Line 107: Line 111:
 
end
 
end
 
</source>
 
</source>
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, I chose a, b, and coll.
+
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.
 
* a is the first object in the collision.
 
* b is the second object in the collision.
 
* b is the second object in the collision.
 
* coll is the [[Contact|contact object]] created.
 
* coll is the [[Contact|contact object]] created.
  
Say we want to print to screen whenever a callback is called. All we have to do is create a variable to store the strings and then append to the string every time a collision action happens.
+
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.
 
A list of functions you can use on contacts can be found at the [[Contact]] page.
  
 
<source lang="lua">
 
<source lang="lua">
text = ""
+
function love.update(dt)
 +
    ... -- substitute for the rest of love.update
 +
 
 +
    if string.len(text) > 512 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 add(a, b, coll)
 
function add(a, b, coll)
text = text..a.." collding with "..b.." at an angle of "..coll:getNormal().."\n"
+
    persisting = 0    -- reset since every event other than persist means they're not continuing to touch
 +
    x,y = coll:getNormal()
 +
    text = text.."\n"..a.." colliding with "..b.." with a vector normal of: "..x..", "..y
 
end
 
end
  
 
function persist(a, b, coll)
 
function persist(a, b, coll)
text = text..a.." touching "..b.."\n"
+
    if persisting == 0 then    -- only say when they first start touching
 +
        text = text.."\n"..a.." touching "..b
 +
    else    -- then just start counting
 +
        text = text.." "..persisting
 +
    end
 +
    persisting = persisting + 1    -- keep track of how many updates they've been touching for
 
end
 
end
  
 
function rem(a, b, coll)
 
function rem(a, b, coll)
text = text..a.." uncolliding "..b.."\n"
+
    persisting = 0
 +
    text = text.."\n"..a.." uncolliding with "..b
 
end
 
end
  
 
function result(a, b, coll)
 
function result(a, b, coll)
text = text..a.." hit "..b.."resulting with "..coll:getNormal().."\n"
+
    persisting = 0
 +
    text = text.."\n"..a.." hit "..b
 
end
 
end
 
</source>
 
</source>
  
 
And now you know how to use world callbacks!
 
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:
 +
 +
<source lang="lua">
 +
function love.update(dt)
 +
    world:update(dt)
 +
 +
    if love.keyboard.isDown("right") then
 +
        ball.b:applyForce(100, 0)
 +
    elseif love.keyboard.isDown("left") then
 +
        ball.b:applyForce(-100, 0)
 +
    end
 +
    if love.keyboard.isDown("up") then
 +
        ball.b:applyForce(0, -100)
 +
    elseif love.keyboard.isDown("down") then
 +
        ball.b:applyForce(0, 100)
 +
    end
 +
 +
    if string.len(text) > 512 then    -- cleanup when 'text' gets too long
 +
        text = ""
 +
    end
 +
end
 +
</source>
 +
  
 
== Finished ==
 
== Finished ==
Line 143: Line 193:
 
<source lang="lua">
 
<source lang="lua">
 
function love.load()
 
function love.load()
world = love.physics.newWorld(-800,-600, 800,600)
+
    world = love.physics.newWorld(0, 0, 800, 600)
world:setGravity(0,20)
+
        world:setGravity(0, 40)
world:setCallbacks(add, persist, rem, result)
+
        world:setCallbacks(add, persist, rem, result)
ball = {}
+
 
ball.b = love.physics.newBody(world, 400,200, 10,0)
+
    ball = {}  
ball.s = love.physics.newCircleShape(ball.b, 0,0, 50)
+
        ball.b = love.physics.newBody(world, 400,200, 10, 0)  
ball.s:setData("Ball")
+
        ball.s = love.physics.newCircleShape(ball.b, 0,0, 50)
static = {}
+
        ball.s:setRestitution(0.4)    -- make it bouncy
static.b = love.physics.newBody(world, 400,400, 0,0)
+
        ball.s:setData("Ball")
static.s = love.physics.newRectangleShape(static.b, -100,-25, 200,50, 0)
+
    static = {}  
static.s:setData("Block")
+
        static.b = love.physics.newBody(world, 400,400, 0,0)
 +
        static.s = love.physics.newRectangleShape(static.b, 0,0, 200,50, 0)  
 +
        static.s:setData("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
 
end
  
 
function love.update(dt)
 
function love.update(dt)
world:update(dt)
+
    world:update(dt)
 +
 
 +
    if love.keyboard.isDown("right") then
 +
        ball.b:applyForce(100, 0)
 +
    elseif love.keyboard.isDown("left") then
 +
        ball.b:applyForce(-100, 0)
 +
    end
 +
    if love.keyboard.isDown("up") then
 +
        ball.b:applyForce(0, -100)
 +
    elseif love.keyboard.isDown("down") then
 +
        ball.b:applyForce(0, 100)
 +
    end
 +
 
 +
    if string.len(text) > 512 then    -- cleanup when 'text' gets too long
 +
        text = ""
 +
    end
 
end
 
end
  
 
function love.draw()
 
function love.draw()
love.graphics.circle("line", ball.b:getX(),ball.b:getY(), ball.s:getRadius())
+
    love.graphics.circle("line", ball.b:getX(),ball.b:getY(), ball.s:getRadius(), 20)
local x1,y1, _,_, x3,y3, _,_ = static.s:getBoundingBox()
+
    love.graphics.polygon("line", static.s:getPoints())
local w = x3-x1
+
 
local h = y3-y1
+
    love.graphics.print(text, 10, 10)
love.graphics.rectangle("line",
 
static.b:getX()-w/2,static.b:getY(),
 
w,h, 0)
 
love.graphics.print(text,0,12)
 
 
end
 
end
 
--Refer to http://love2d.org/wiki/Contact for more information on collision objects
 
--'coll' is an object created by the collision
 
--'a' is the first object in the collision and 'b' is the second
 
 
text = ""
 
  
 
function add(a, b, coll)
 
function add(a, b, coll)
text = text..a.." collding with "..b.." at an angle of "..coll:getNormal().."\n"
+
    persisting = 0    -- reset since every event other than persist means they're not continuing to touch
 +
    x,y = coll:getNormal()
 +
    text = text.."\n"..a.." colliding with "..b.." with a vector normal of: "..x..", "..y
 
end
 
end
  
 
function persist(a, b, coll)
 
function persist(a, b, coll)
text = text..a.." touching "..b.."\n"
+
    if persisting == 0 then    -- only say when they first start touching
 +
        text = text.."\n"..a.." touching "..b
 +
    else    -- then just start counting
 +
        text = text.." "..persisting
 +
    end
 +
    persisting = persisting + 1    -- keep track of how many updates they've been touching for
 
end
 
end
  
 
function rem(a, b, coll)
 
function rem(a, b, coll)
text = text..a.." uncolliding "..b.."\n"
+
    persisting = 0
 +
    text = text.."\n"..a.." uncolliding with "..b
 
end
 
end
  
 
function result(a, b, coll)
 
function result(a, b, coll)
text = text..a.." hit "..b.."resulting with "..coll:getNormal().."\n"
+
    persisting = 0
 +
    text = text.."\n"..a.." hit "..b
 
end
 
end
 
</source>
 
</source>

Revision as of 09:22, 27 March 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,0, 800,600)
		--Gravity is being set to 0 in the x direction and 40 in the y direction.
		world:setGravity(0,40)
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.

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, 10,0)
        ball.s = love.physics.newCircleShape(ball.b, 0,0, 50)
        ball.s:setRestitution(0.4)    -- make it bouncy
        ball.s:setData("Ball")
    static = {}
        static.b = love.physics.newBody(world, 400,400, 0,0)
        static.s = love.physics.newRectangleShape(static.b, 0,0, 200,50, 0)
        static.s:setData("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.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: add, persist, remove, and result.

Add is the callback for every new collision.
Persist is the callback for every collision that is continuing from last frame.
Rem(ove) is the callback for any collision that stopped happening since last frame.
Result (not yet implemented in Love).
function love.load()
    ...  -- substitute for the rest of love.load

    world = love.physics.newWorld(0,0, 800,600)
        world:setGravity(0, 40)
        --These callback function names can be almost any you want:
        world:setCallbacks(add, persist, rem, result)

    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 add(a, b, coll)
	
end

function persist(a, b, coll)
	
end

function rem(a, b, coll)
	
end

function result(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) > 512 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 add(a, b, coll)
    persisting = 0    -- reset since every event other than persist means they're not continuing to touch
    x,y = coll:getNormal()
    text = text.."\n"..a.." colliding with "..b.." with a vector normal of: "..x..", "..y
end

function persist(a, b, coll)
    if persisting == 0 then    -- only say when they first start touching
        text = text.."\n"..a.." touching "..b
    else    -- then just start counting
        text = text.." "..persisting
    end
    persisting = persisting + 1    -- keep track of how many updates they've been touching for
end

function rem(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

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(100, 0) 
    elseif love.keyboard.isDown("left") then
        ball.b:applyForce(-100, 0) 
    end
    if love.keyboard.isDown("up") then
        ball.b:applyForce(0, -100)
    elseif love.keyboard.isDown("down") then
        ball.b:applyForce(0, 100)
    end

    if string.len(text) > 512 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, 0, 800, 600)
        world:setGravity(0, 40)
        world:setCallbacks(add, persist, rem, result)

    ball = {} 
        ball.b = love.physics.newBody(world, 400,200, 10, 0) 
        ball.s = love.physics.newCircleShape(ball.b, 0,0, 50)
        ball.s:setRestitution(0.4)    -- make it bouncy
        ball.s:setData("Ball")
    static = {} 
        static.b = love.physics.newBody(world, 400,400, 0,0)
        static.s = love.physics.newRectangleShape(static.b, 0,0, 200,50, 0) 
        static.s:setData("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(100, 0) 
    elseif love.keyboard.isDown("left") then
        ball.b:applyForce(-100, 0) 
    end
    if love.keyboard.isDown("up") then
        ball.b:applyForce(0, -100)
    elseif love.keyboard.isDown("down") then
        ball.b:applyForce(0, 100)
    end

    if string.len(text) > 512 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.s:getPoints())

    love.graphics.print(text, 10, 10)
end

function add(a, b, coll)
    persisting = 0    -- reset since every event other than persist means they're not continuing to touch
    x,y = coll:getNormal()
    text = text.."\n"..a.." colliding with "..b.." with a vector normal of: "..x..", "..y
end

function persist(a, b, coll)
    if persisting == 0 then    -- only say when they first start touching
        text = text.."\n"..a.." touching "..b
    else    -- then just start counting
        text = text.." "..persisting
    end
    persisting = persisting + 1    -- keep track of how many updates they've been touching for
end

function rem(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



Other languages