Hi there!
Lacotemale wrote:I also posted in my own thread about this.
I didn't see it, sorry. Can you give me a link? EDIT:
Found it. For the record, I never browse "kitchen sink" threads that ask about everything and anything. Next time, consider creating a new thread per question/subject, with a descriptive title, like crystalheartstudios did; you will get more attention.
------
Let me try to explain how bump works in steps. I recommend you to follow each step and test it in an empty main.lua. I am not trying all the tests myself so I might make typos.
STEP 1:
The first thing you have to do is downloading the bump.lua file and putting it somewhere in your game folder (people usually put it inside a lib/ folder).
Then, require it wherever you use it. For example, if you put bump.lua next to main.lua, you can require it like this from main.lua:
STEP 2:
The next thing you must do is creating a
world with bump.newWorld. The simplest think I can think of is making it local in main.lua:
Code: Select all
local bump = require 'bump'
local world
function love.load()
world = bump.newWorld()
end
STEP 3:
In bump, a world is just a place where you put
items. Items are everything which participates in collisions in your game: the player, the blocks, bullets, powerups, etc. Every item will have an
associated rectangle. Generally, items are tables.
In order to add items to a world, you use
world:add. Let's start with the player. Imagine that our player character is a 32x32 image that starts in {100, 100}. You need to create a table for it, and then tell the world about it.
- Note that for simplicity I am going to use x and y on this example, but in bump those variables are called l (for left) and t (for top). This is because sometimes x,y is the center of the rectangle, not its left-top corner -
Code: Select all
local bump = require 'bump'
local world
local player
function love.load()
world = bump.newWorld()
player = {x=100, y=100, w=32, h=32}
world:add(player, player.x, player.y, player.w, player.h)
end
function love.draw()
love.graphics.rectangle(player.x, player.y, player.w, player.h)
end
STEP 4:
Now, when you move the player with the arrow keys, you must do two things: You must change the values of x and y in the
player table, and also update the bounding box associated with the player in the world. You do this with
world:move. This is one possible way to do it:
Code: Select all
...
function love.update(dt)
local player_speed = 60
-- update the player if the keys are pressed
if love.keyboard.isDown('up') then
player.y = player.y - player_speed * dt
elseif love.keyboard.isDown('down') then
... (repeat with down, left and right)
end
-- update the player associated bounding box in the world
world:move(player, player.x, player.y) -- player.w and player.h remain the same if not passed
end
STEP 5:
We need to add some blocks now. Blocks are easy because they don't move. I am going to store them in a variable called
blocks. To make things a bit easier, I am going to use a function called addBlock. I am going to create only two blocks; you can create more if you need.
Code: Select all
local bump = require 'bump'
local world
local player
local blocks
local function addBlock(x,y,w,h)
local block = {x=x,y=y,w=w,h=h}
blocks[#blocks + 1] = block
world:add(block, x,y,w,h)
end
function love.load()
world = bump.newWorld()
player = {x=100, y=100, w=32, h=32}
world:add(player, player.x, player.y, player.w, player.h)
addBlock(50, 100, 200, 32)
addBlock(150, 100, 100, 32)
end
function love.draw()
love.graphics.rectangle(player.x, player.y, player.w, player.h)
for i=1, #blocks do
local b = blocks[i]
love.graphics.rectangle(b.x, b.y, b.w, b.h)
end
end
STEP 6:
But now the player does not interact with the blocks at all; it moves through them. We must check for collisions. The way we check for collisions in bump is by calling
world:check. It works as follows: we pass it an item (in this case, the player) and
the coordinates where it wants to go. world:check returns a list of collisions and its length. If the way is free, then the list of collisions is empty, and its length will be 0.
So we can do a rough collision detection phase like this:
Code: Select all
...
function love.update(dt)
local player_speed = 60
local future_x, future_y = player.x, player.y
-- update future_x and future_y variables if the keys are pressed
if love.keyboard.isDown('up') then
future_y = future_y - player_speed * dt
elseif love.keyboard.isDown('down') then
... (repeat with down, left and right)
end
-- get the collisions
local collisions, len = world:check(player, future_x, future_y)
-- If there where no collisions, we can move the player safely
if len == 0 then
player.x, player.y = future_x, future_y
world:move(player, player.x, player.y)
end
end
STEP 7:
The thing is, this is not enough. There are two problems:
- It is not possible to do "partial steps". If the player goes fast enough, it will seem that he stops before he touches the blocks - there will be "phantom borders" around blocks sometimes. Plus, this depends on lots of things (like the speed of the computer)
- The player doesn't "slide over blocks" when going diagonally; the moment a collision is detected, it stops.
These problems happen because we are
detecting the collisions, but not
resolving then correctly. bump has several ways to help calculating the resolution, because different items in games behave differently - for example, Super Mario "slides" over tiles and walls, the bubbles in Supper Pang "bounce", while bullets tend to "get stuck" and not bounce at all.
In our case, we're going to use the "slide" resolution, so our player can "move diagonally and still go up" when facing a wall. collision:getSlide returns "the coordinates of the player who wanted to go to future_x and future_y after he collided with a block, sliding over it" (plus more things, like the coordinates where the player started touching the block, and the contact normal).
Here is a better (but still not perfect) approach to resolution, using the first collision:
Code: Select all
function love.update(dt)
... (same as before)
-- get the collisions
local collisions, len = world:check(player, future_x, future_y)
-- If there where no collisions, we can move the player safely
if len == 0 then
player.x, player.y = future_x, future_y
world:move(player, player.x, player.y)
elseif len == 1 then
local _,_,_,_,slide_x, slide_y = collisions[i]:getSlide()
player.x, player.y = slide_x, slide_y
world:move(player, player.x, player.y)
end
end
STEP 8:
This solves the first problem (there is no invisible barrier any more). But it makes some things worse. There are some occasions when the player collides simultaneously with more than 1 block (for example, when moving diagonally up-right to the up-right corner of a room). This code handles collisions with 1 block only, so when the player hits more than 1, it only collides with the nearest one, ignoring the rest.
Since the number of colliding blocks is unknown, we can't solve it with if-elses; we must use a loop, so we collide with the first block, then slide over it, then collide with the second, etc. There is a problem though: after sliding over the first block, the other collisions are not valid and must be recalculated. Also, sliding over more than 1 block will give bad results - we must slide only on the "last block colliding". For the rest, the player must act as a bullet, "getting stuck".
This all gives us the following code:
Code: Select all
function love.update(dt)
... (same as before)
-- get the collisions
local collisions, len = world:check(player, future_x, future_y)
-- If there where no collisions, we can move the player safely
if len == 0 then
player.x, player.y = future_x, future_y
world:move(player, player.x, player.y)
else
local col, tx, ty, sx, sy
while len > 0 do
col = cols[1]
tx,ty,_,_,sx,sy = col:getSlide()
-- Move the player so that it "touches" the block
player.l, player.t = tx, ty
world:move(player, tx, ty)
-- See if it collisions on anything else while "sliding" over the block
cols, len = world:check(player, sx, sy)
if len == 0 then -- If not colliding with anything else, slide. Otherwise, touch the next block and try sliding over it
player.l, player.t = sx, sy
world:move(player, sx, sy)
end
end
end
end
That's it. Now it should work more or less like Zelda.
Incidentally, this code is almost a copy from
bump's simple demo. Launch it up if you want to see it in action (the only thing that demo adds is debugging info).
There are more advanced stuff to do from here: filtering collisions, colliding with different kinds of items, and spatial queries. But I hope this explanation gets you started.