[Tutorial] Physics

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
Post Reply
User avatar
iPoisonxL
Party member
Posts: 227
Joined: Wed Feb 06, 2013 3:53 am
Location: Australia
Contact:

[Tutorial] Physics

Post by iPoisonxL »

This is my attempt at a tutorial for the basics in love.physics module in LÖVE.
This is not a complete tutorial on physics. Once you learn what I will teach you, it will be very easy to go and experiment on your own.

Here's what we're going to be making. By the end of this tutorial, all of this code will (probably) make sense to you.
main.love
(752 Bytes) Downloaded 397 times
We'll be going over:

Explanation
- Some insight on love.physics
- Why you would use it
Writing the code
- love.load
- love.update
- love.draw

Little reminder: I wouldn't consider love.physics to be a "ten ton hammer", as the wiki said, but it's much better to make a platformer without this module, as it will give you a much richer learning experience. If you've already made one, then be ready to learn love.physics!

Let's get started!

Explanation

First of all, you should know that love.physics is an implementation of Box2D, an open source C++ engine for simulating rigid bodies in 2 dimensions. LÖVE's version is basically translated to Lua, but if you need help with this, you are not limited to the LÖVE wiki, as there is a whole different community using Box2D, that is willing to help. This module has a lot of functions, and only a few will be used in this tutorial. All of the functions can be found on the love.physics wiki. They're all pretty much self explanatory.

The love.physics module is somewhat complicated when you look at it for the first time. There's a lot of steps to take, but the functions do almost all the work for you (that's why I don't like using this much, as it feels like I'm cheating! :crazy: ). Normally, you'd use this if you're looking to get an accurate representation of real life physics through 2D. If you're just looking to make a square jump around, then move along to this tutorial. It will teach you many basics in Lua, such as basic objects, gravity, and other stuff.

Throughout this tutorial, I strongly recommend you comment what you write. My final "here's what the code should look like" will not contain any comments, but your code can contain as many comments as you want.

Finally, done explaining, now we can get to the fun part! Programming!

main.lua

First of all, you'll need to make your basic main.lua, with the default LÖVE skeleton (love.load, love.update, love.draw).

Code: Select all

p = love.physics --these are to facilitate the usage of love in the code.
g = love.graphics --so instead of typing love.graphics.blahblahblah every time,
k = love.keyboard --you can simply write g.blahblahblah! Yay!

function love.load()

end

function love.update(dt)

end

function love.draw()

end
Yeah, yeah. You've done this all before (if not, then I suggest you leave, since this will probably be very confusing for you!).

love.load

Alright, now let's get working on love.load.
The FIRST thing you want to do, is to set how many pixels long one meter will be in this game. For this tutorial, let's set it to 100. This means that one meter in our game will equal 100 pixels on the screen. Simple!

Remember, this is going in love.load!

Code: Select all

p.setMeter(100) --as you can see, i simply had to write p.setMeter(100) instead of love.physics.setMeter(100)
Second, we need to create a world. This world will contain objects. You can't have objects without a world for it to live in!
love.physics.newWorld(xg, yg, bool)
xg is the horizontal gravity in this world.
yg is the vertical gravity in this world.
bool is whether or not objects can sleep in this world. Sleeping basically means that the object is not moving, and it's much more efficient to update, rather than having a body constantly awake even if it's not moving.

Code: Select all

  w = p.newWorld(0, 9.8*p.getMeter(), true) 

--[[this world will have no gravity horizontally, but will have a gravity of 9.8 meters vertically. Objects can sleep in this world.]]
Alright, now comes the more complicated part. Since we now have a world for our objects to exist in, and we already have our meter, we can now create (can you guess it?)... OUR OBJECTS!
This is still going to happen in love.load, since we need to set all the values for our objects.
First of all, we need to have an idea of what objects we're going to make. Let's have one ball, which we will control with the arrow keys, and a couple blocks that are besides each other. Oh, and this will all be on top of a rectangle that will be acting as the ground.

So, let's create the ground. This is very simple. An object needs 3 things: a body, a shape, and a fixture.

body: this is what gets affected by velocity, and it holds the X and Y. It's invisible.

shape: this is the shape that you see. It's used for mass control, and collision.

fixture: this is what attaches the shape to the body. It's like in those cartoons when someone's invisible, and they throw paint on him so you can see him!

Alright, so now you know this. To finally create the ground, I used a handy program that I made called Physmapper. It facilitates the creation (and drawing, but we'll get to that later) of objects. It is not needed for this tutorial, though.

So let's create our body for the ground. Remember, this is all going in love.load!

Code: Select all

ground = {} --this creates a ground table, which will hold the ground object and all its data.
ground.b = p.newBody(w, 407, 421, "static") --b for body!
--I'm creating a new body, in the world "w", at the x 407 and y 421, with the type "static". Static means that it will not move.
Now that we're done the body, let's make our shape.

Code: Select all

ground.s = p.newRectangleShape(429, 40) --s for shape!
--I'm creating a new rectangle shape, that will be 429 pixels wide and 40 pixels tall. We don't need any coordinates, since this shape will be attached to the body!
Whew, done the shape. Now let's work on our fixture!

Code: Select all

ground.f = p.newFixture(ground.b, ground.s) --f for fixture!
--I'm creating a new fixture, by attaching the shape ground.s to the body ground.b!
There we go. We're done the ground. It now exists!
Now let's create 2 rectangles that are standing besides each other!

Again, I used Physmapper for this, but it is not needed.
Let's make a table that holds all the moving blocks!

Code: Select all

blocks = {
  {},
  {}
}
--we're making a blocks table that can hold 2 different blocks!
Okay, now let's create our first block. Like the ground, it needs a body, a shape, and a fixture.

Code: Select all

blocks[1].b = p.newBody(w, 264, 254, "dynamic") 
--creating a block in world w, at x 264 and y 254. Dynamic means that it moves.

blocks[1].s = p.newRectangleShape(35, 75)
 --creating a rectangle shape with the width of 35, and the height of 75

blocks[1].f = p.newFixture(blocks[1].b, blocks[1].s) 
--creating a new fixture that will stick the body block[1].b and the shape block[1].s together.
We're done our first block. Now, let's make the second block.

Code: Select all

blocks[2].b = p.newBody(w, 264, 254, "dynamic") 
--creating a block in world w, at x 264 and y 254. Dynamic, meaning that it moves.

blocks[2].s = p.newRectangleShape(35, 75)
 --creating a rectangle shape with the width of 35, and the height of 75, just like the first block.

blocks[2].f = p.newFixture(blocks[2].b, blocks[2].s) 
--creating a new fixture that will stick the body block[1].b and the shape block[1].s together.
There we go, we're done all of our rectangles (ground, and blocks). Let's create our player. He's going to be a circle!

First, let's create a table that will hold our player object. Then we can create our player's body, shape, and fixture.

Code: Select all

player = {}

player.b = p.newBody(w, 476, 270, "dynamic") 
--creating a new body in the world "w" at x 476 and y 270. dynamic because it will be moving.

player.s = p.newCircleShape(30)
--creating a new circle shape, with the radius of 30!

player.f = p.newFixture(player.b, player.s)
--like any other fixture, we're attaching the player.b and player.s together!

player.f:setRestitution(0.7)
--we're setting the player's restitution. This is how bouncy the player is.
There we go, we're all done with love.load. We made all of our rectangles, and made our player! Here's what main.lua should look like so far:

Code: Select all

p = love.physics
g = love.graphics
k = love.keyboard

function love.load()
  p.setMeter(100)
  w = p.newWorld(0, 9.8*p.getMeter(), true) 

  ground = {}

  ground.b = p.newBody(w, 407, 421, "static")
  ground.s = p.newRectangleShape(429, 40)
  ground.f = p.newFixture(ground.b, ground.s)

  blocks = {
    {},
    {}
  }
  
  blocks[1].b = p.newBody(w, 264, 254, "dynamic") 

  blocks[1].s = p.newRectangleShape(35, 75)

  blocks[1].f = p.newFixture(blocks[1].b, blocks[1].s) 


  blocks[2].b = p.newBody(w, 264, 254, "dynamic") 

  blocks[2].s = p.newRectangleShape(35, 75)

  blocks[2].f = p.newFixture(blocks[2].b, blocks[2].s) 

  player = {}

  player.b = p.newBody(w, 476, 270, "dynamic") 

  player.s = p.newCircleShape(30)

  player.f = p.newFixture(player.b, player.s)

  player.f:setRestitution(0.7)
end

function love.update(dt)

end

function love.draw()

end
If you ran the code as it currently is, it would show a black screen of nothing. That's normal, because we didn't draw all of our stuff yet! But we'll get to that in a moment, right now we need to work with love.update!

love.update

Basically what we're going to do here is put our world in motion. We're going to make the player move if we press arrow keys/WASD, and we're going to make the whole world update with a simple function called w:update(dt).

Remember, this here is going in love.update.

Code: Select all

w:update(dt) --update the world. If you don't have this, your world will not move.

if k.isDown("up") or k.isDown("w") then --let's start with up. This will be a flying player! :)
  player.b:applyForce(0, -600) 
  --we're applying a force of 0 on the X axis, and -600 on the Y axis! -600 will make it go upwards, since love2d counts Y from up to down.
  --note: we're using -600 instead of -300 because gravity is also pulling down, so we need a stronger force to even it out!
end

if k.isDown("down") or k.isDown("s") then --let's move on to down! This will make the player go faster towards the ground.
  player.b:applyForce(0, 300)
  --again, applying force to the player's body. 0 on the X axis, and 300 on the Y axis. This will make it go downwards!
end

if k.isDown("left") or k.isDown("a") then --let's make him go left now.
  player.b:applyForce(-300, 0)
  --making the player go to the left by applying -300 force to the X axis, and 0 to the Y axis.
end

if k.isDown("right") or k.isDown("d") then --now, finally, to the right!
  player.b:applyForce(300, 0)
  --I think you get it... :)
end
There we go! Our player will now be able to move! Don't forget to pass dt as an argument to love.update, though. If not, you'll get an error!
Here's what the code should look like so far:

Code: Select all

p = love.physics
g = love.graphics
k = love.keyboard

function love.load()
  p.setMeter(100)
  w = p.newWorld(0, 9.8*p.getMeter(), true) 

  ground = {}

  ground.b = p.newBody(w, 407, 421, "static")
  ground.s = p.newRectangleShape(429, 40)
  ground.f = p.newFixture(ground.b, ground.s)

  blocks = {
    {},
    {}
  }
  
  blocks[1].b = p.newBody(w, 264, 254, "dynamic") 

  blocks[1].s = p.newRectangleShape(35, 75)

  blocks[1].f = p.newFixture(blocks[1].b, blocks[1].s) 


  blocks[2].b = p.newBody(w, 264, 254, "dynamic") 

  blocks[2].s = p.newRectangleShape(35, 75)

  blocks[2].f = p.newFixture(blocks[2].b, blocks[2].s) 

  player = {}

  player.b = p.newBody(w, 476, 270, "dynamic") 

  player.s = p.newCircleShape(30)

  player.f = p.newFixture(player.b, player.s)

  player.f:setRestitution(0.7)
end

function love.update(dt)
  w:update(dt)

  if k.isDown("up") or k.isDown("w") then
    player.b:applyForce(0, -300) 
  end

  if k.isDown("down") or k.isDown("s") then
    player.b:applyForce(0, 300)
  end

  if k.isDown("left") or k.isDown("a") then
    player.b:applyForce(-300, 0)
  end

  if k.isDown("right") or k.isDown("d") then
    player.b:applyForce(300, 0)
  end
end

function love.draw()

end
Well, the love.update was pretty simple. Now, we get to the love.draw!

love.draw

Now, we just need to draw our stuff.

First, let's start by drawing our player. Remember, this is going in love.draw!

Code: Select all

g.setColor(255, 0, 0) --let's set the color of the player to red.
g.circle("fill", player.b:getX(), player.b:getY(), player.s:getRadius()) 

--[[simply using the love.graphics.circle() method to draw our player.
we're setting the draw mode to fill, which means it will fill the shape with color.
for the second and third parameter, we're simply getting the player body's X and Y.
the third parameter is getting the radius of our player shape!
]]
We drew our player! Yay! Now let's get to the more complicated drawing. The ground and blocks!

Code: Select all

--creating the ground:
g.setColor(255, 255, 255) --setting the color to white for the ground. Because why not?
g.polygon("fill", ground.b:getWorldPoints(ground.s:getPoints()))

--[[here, I created a polygon with the draw mode fill. 
The world points are which points we'll be drawing our rectangle around. It's a bit complicated.
]]

g.setColor(0, 255, 0) --setting the color for our blocks to green.
g.polygon("fill", blocks[1].b:getWorldPoints(blocks[1].s:getPoints()))
g.polygon("fill", blocks[2].b:getWorldPoints(blocks[2].s:getPoints()))
--[[what we're doing here is the same as the ground, 
creating a polygon with drawmode fill and the world points... well, they're world points.
]]

g.setColor(255, 255, 255) --we should always set back the color to white at the end of love.draw!
And boom! We're done the love.draw! Simple. Now, the code should look like this:

Code: Select all

p = love.physics
g = love.graphics
k = love.keyboard

function love.load()
  p.setMeter(100)
  w = p.newWorld(0, 9.8*p.getMeter(), true) 

  ground = {}

  ground.b = p.newBody(w, 407, 421, "static")
  ground.s = p.newRectangleShape(429, 40)
  ground.f = p.newFixture(ground.b, ground.s)

  blocks = {
    {},
    {}
  }
  
  blocks[1].b = p.newBody(w, 264, 254, "dynamic") 

  blocks[1].s = p.newRectangleShape(35, 75)

  blocks[1].f = p.newFixture(blocks[1].b, blocks[1].s) 


  blocks[2].b = p.newBody(w, 264, 254, "dynamic") 

  blocks[2].s = p.newRectangleShape(35, 75)

  blocks[2].f = p.newFixture(blocks[2].b, blocks[2].s) 

  player = {}

  player.b = p.newBody(w, 476, 270, "dynamic") 

  player.s = p.newCircleShape(30)

  player.f = p.newFixture(player.b, player.s)

  player.f:setRestitution(0.7)
end

function love.update(dt)
  w:update(dt)

  if k.isDown("up") or k.isDown("w") then
    player.b:applyForce(0, -300) 
  end

  if k.isDown("down") or k.isDown("s") then
    player.b:applyForce(0, 300)
  end

  if k.isDown("left") or k.isDown("a") then
    player.b:applyForce(-300, 0)
  end

  if k.isDown("right") or k.isDown("d") then
    player.b:applyForce(300, 0)
  end
end

function love.draw()
  g.setColor(255, 0, 0)
  g.circle("fill", player.b:getX(), player.b:getY(), player.s:getRadius()) 

  g.setColor(255, 255, 255) --setting the color to white for the ground. Because why not?
  g.polygon("fill", ground.b:getWorldPoints(ground.s:getPoints()))

  g.setColor(0, 255, 0) --setting the color for our blocks to green.
  g.polygon("fill", blocks[1].b:getWorldPoints(blocks[1].s:getPoints()))
  g.polygon("fill", blocks[2].b:getWorldPoints(blocks[2].s:getPoints()))

  g.setColor(255, 255, 255)
end
Now, if you run this code, you should get a cool circle that can push blocks and fly!
Image

I thought I'd make this tutorial, because the tutorial on the wiki felt so... incomplete. It wasn't very explaining. I decided to make my own, that explains as much as it can.

Code: Select all

      L
    L Ö
    Ö V
L Ö V E
Ö B E
V E
E Y
Website
pielago
Party member
Posts: 142
Joined: Fri Jun 14, 2013 10:41 am

Re: [Tutorial] Physics

Post by pielago »

Nice but now I have a question?
in my case i want the circle to look like IDk a basketball so how do i add a picture to the circle?
can you show it ,i want to get in physics but i dont know how to add the pic to the physics object!
is it the same as a regular rectangle and circle? or the pic.x its different from the physic object.x???
User avatar
iPoisonxL
Party member
Posts: 227
Joined: Wed Feb 06, 2013 3:53 am
Location: Australia
Contact:

Re: [Tutorial] Physics

Post by iPoisonxL »

pielago wrote:Nice but now I have a question?
in my case i want the circle to look like IDk a basketball so how do i add a picture to the circle?
can you show it ,i want to get in physics but i dont know how to add the pic to the physics object!
is it the same as a regular rectangle and circle? or the pic.x its different from the physic object.x???
It's very different, but I've never done it. There's plenty of questions around the forum about it, or you could start your own.

Sorry, I really have no idea how to import a picture with physics. :brows:

But yes, the pic x would be different. It wouldn't measure in the middle, it would measure in the top right of the picture.

EDIT: I found this

Code: Select all

      L
    L Ö
    Ö V
L Ö V E
Ö B E
V E
E Y
Website
User avatar
Ranguna259
Party member
Posts: 911
Joined: Tue Jun 18, 2013 10:58 pm
Location: I'm right next to you

Re: [Tutorial] Physics

Post by Ranguna259 »

You could just do love.graphics.draw() with the x and y coords as the circle's center coords minus the circle's radius.
LoveDebug- A library that will help you debug your game with an on-screen fully interactive lua console, you can even do code hotswapping :D

Check out my twitter.
girng
Prole
Posts: 40
Joined: Fri Sep 07, 2018 5:42 am

Re: [Tutorial] Physics

Post by girng »

Thank you for this. Helped a lot =]
austingae
Prole
Posts: 2
Joined: Thu Jun 06, 2019 7:25 am

Re: [Tutorial] Physics

Post by austingae »

Cool tutorial!
User avatar
Karai17
Party member
Posts: 930
Joined: Sun Sep 02, 2012 10:46 pm

Re: [Tutorial] Physics

Post by Karai17 »

Ranguna259 wrote: Mon Nov 25, 2013 6:08 pm You could just do love.graphics.draw() with the x and y coords as the circle's center coords minus the circle's radius.
This is correct but there is a minor detail missing. You want to place your texture at the circle's center and then use half the width and height of the texture as the offset values in the draw function. this will shift the texture's centre point from the top-left to the centre. this allows you to rotate the texture with the angle of the circle so your basketball texture rotates correctly instead of just sliding around.
STI - An awesome Tiled library
LÖVE3D - A 3D library for LÖVE 0.10+

Dev Blog | GitHub | excessive ❤ moé
Post Reply

Who is online

Users browsing this forum: No registered users and 77 guests