Fixing Collision

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
lilfinn
Prole
Posts: 10
Joined: Thu Jul 18, 2013 5:58 am

Fixing Collision

Post by lilfinn »

Im working on a little concept here and wanted to get the collision working. I feel like I have hacked and slashed my way through to get a very rudimentary collision system working and I was wondering if anyone can provide some guidance on how to improve it. Or if its just completely awful, maybe provide a better way to do it? Currently I have a map being loaded in from Tiled using STI. Im using the map:getCollisionMap() to check my players movement against a grid, but I want my player to be able to move anywhere and not confined to a grid. I thought if I could get a "ghost" player that is on the grid to follow my player close enough, I could just use the grid players collision and apply it to the free form player. This kind of worked but Im not happy with the fact that I can sometimes move through blocks, or get stuck on others.

Thanks in advance
Blake

EDIT
If you don't want to download it to try and find something here is the main.lua

Code: Select all

local sti = require "STI"

function love.load()
    map = sti.new("Map")
    collision = map:getCollisionMap("Collision")

    player = {
    	x = 32,
    	y = 32,
    	gridX = 3,
    	gridY = 3,
    	width = 16,
    	height = 16
}

    canMoveRight = true
    canMoveLeft = true
    canMoveup = true
    canMoveDown = true

end

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

    if love.keyboard.isDown("right") then
        if canMoveRight then
            player.x = player.x + 2
        end
        if collision.data[player.gridY][player.gridX + 1] ~= 1 then
            if player.x % 8 == 0 and player.x > player.gridX * 16 - 16 then
                player.gridX = player.gridX + 1
            end
        end

        if collision.data[player.gridY][player.gridX + 1] == 1 and player.x % 16 == 0  then
                canMoveRight = false
        else
            canMoveRight = true
        end

    end


    if love.keyboard.isDown("left") then
        if canMoveLeft then
            player.x = player.x - 2
        end

        if collision.data[player.gridY][player.gridX - 1] ~= 1 then
            if player.x % 8 == 0 and player.x < player.gridX * 16 - 16 then
                player.gridX = player.gridX - 1
            end
        end

        if collision.data[player.gridY][player.gridX - 1] == 1 and player.x % 16 == 0 then
            canMoveLeft = false
        else
            canMoveLeft = true
        end
    end


    if love.keyboard.isDown("up") then
        if canMoveUp then
            player.y = player.y - 2
        end

        if collision.data[player.gridY - 1][player.gridX] ~= 1 then
            if player.y % 8 == 0 and player.y < player.gridY * 16 - 16 then
                player.gridY = player.gridY - 1
            end
        end

        if collision.data[player.gridY - 1][player.gridX] == 1 and player.y % 16 == 0 then
            canMoveUp = false
        else
            canMoveUp = true
        end
    end

    if love.keyboard.isDown("down") then
        if canMoveDown then
            player.y = player.y + 2
        end

        if collision.data[player.gridY + 1][player.gridX] ~= 1 then
            if player.y % 8 == 0 and player.y > player.gridY * 16 - 16 then
                player.gridY = player.gridY + 1
            end
        end

        if collision.data[player.gridY + 1][player.gridX] == 1 and player.y % 16 == 0 then
                canMoveDown = false
        else
            canMoveDown = true
        end
    end

end

function love.draw()
    map:draw()
    love.graphics.rectangle( "fill", (player.gridX * 16 - 16), player.gridY * 16 - 16, player.width, player.height )
    love.graphics.setColor(0,0,0)
    love.graphics.rectangle( "fill", player.x, player.y, player.width, player.height)
    
    love.graphics.setColor(255,255,255)
    love.graphics.print(player.x % 16, 300, 300, 0, 2)
    love.graphics.print(player.y % 16, 300, 350, 0, 2)
end

function love.resize(w, h)
    map:resize(w, h)
end
Attachments
Collision.love
(26.94 KiB) Downloaded 169 times
User avatar
bdjnk
Citizen
Posts: 81
Joined: Wed Jul 03, 2013 11:44 pm

Re: Fixing Collision

Post by bdjnk »

Less complicated is usually better. My code checks the potential collisions (two possible collision per direction) and only move when I won't collide.

Code: Select all

local sti = require "STI"

function love.load()
	map = sti.new("Map")
	collision = map:getCollisionMap("Collision")

	player = {
		x = 32,
		y = 32,
		w = 16,
		h = 16
	}
end

function love.update(dt)
	map:update(dt)
	local px,py = player.x,player.y

	if love.keyboard.isDown("right") then
		px = player.x + 1
		if collision.data[math.ceil(py/16)+1][math.ceil(px/16)+1] == 1 or
			collision.data[math.floor(py/16)+1][math.ceil(px/16)+1] == 1 then
			px = px - 1
		end
	end
	if love.keyboard.isDown("left") then
		px = player.x - 1
		if collision.data[math.ceil(py/16)+1][math.floor(px/16)+1] == 1 or
			collision.data[math.floor(py/16)+1][math.floor(px/16)+1] == 1 then
			px = px + 1
		end
	end
	if love.keyboard.isDown("up") then
		py = player.y - 1
		if collision.data[math.floor(py/16)+1][math.ceil(px/16)+1] == 1 or
			collision.data[math.floor(py/16)+1][math.floor(px/16)+1] == 1 then
			py = py + 1
		end
	end
	if love.keyboard.isDown("down") then
		py = player.y + 1
		if collision.data[math.ceil(py/16)+1][math.ceil(px/16)+1] == 1 or
			collision.data[math.ceil(py/16)+1][math.floor(px/16)+1] == 1 then
			py = py - 1
		end
	end
	player.x = px
	player.y = py
end

function love.draw()
	map:draw()
	--map:drawCollisionMap(collision)
	love.graphics.setColor(0,0,0)
	love.graphics.rectangle( "fill", player.x, player.y, player.w, player.h)

	love.graphics.setColor(255,255,255)
	love.graphics.print(player.x/16, 300, 300, 0, 2)
	love.graphics.print(player.y/16, 300, 350, 0, 2)
end
d Bus TicketsRoundtrip
function love.resize(w, h)
	map:resize(w, h)
end
Truthfully, any speed should be multiplied by dt to remain computing power independent, which creates a new set of concerns.
User avatar
CaptainMaelstrom
Party member
Posts: 163
Joined: Sat Jan 05, 2013 10:38 pm

Re: Fixing Collision

Post by CaptainMaelstrom »

Honestly, collision detection is one of those things that's best left to those who really know it. Kind of like encryption and security, don't re-invent the wheel. I'm using a great lua module written by kikito called "bump" : https://github.com/kikito/bump.lua. It's easy to use and quite powerful/fast. Try it out!
User avatar
bdjnk
Citizen
Posts: 81
Joined: Wed Jul 03, 2013 11:44 pm

Re: Fixing Collision

Post by bdjnk »

CaptainMaelstrom wrote:Honestly, collision detection is one of those things that's best left to those who really know it. Kind of like encryption and security, don't re-invent the wheel.
Aw, but reinventing the wheel is the best way to learn about wheels (and really make you appreciate good wheels). Speaking of which, I've fixed all the problems. :awesome: Yes, all of them. ;)

Code: Select all

local sti = require "STI"

function love.load()
	map = sti.new("Map")
	collision = map:getCollisionMap("Collision")

	player = {
		x = 32,
		y = 32,
		w = 16,
		h = 16
	}
	speed = 128
end

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

	local px,py = player.x,player.y

	local dR = love.keyboard.isDown("right")
	local dL = love.keyboard.isDown("left")
	local dU = love.keyboard.isDown("up")
	local dD = love.keyboard.isDown("down")

	local speed = speed
	if (dR or dL) and (dU or dD) then
		speed = speed / math.sqrt(2)
	end

	if dR then
		px = player.x + speed * dt
		if collision.data[math.ceil(py/16)+1][math.ceil(px/16)+1] == 1 or
			collision.data[math.floor(py/16)+1][math.ceil(px/16)+1] == 1 then
			px = math.ceil(px - px % 16)
		end
	end
	if dL then
		px = player.x - speed * dt
		if collision.data[math.ceil(py/16)+1][math.floor(px/16)+1] == 1 or
			collision.data[math.floor(py/16)+1][math.floor(px/16)+1] == 1 then
			px = math.ceil(px + 16 - px % 16)
		end
	end
	if dU then
		py = player.y - speed * dt
		if collision.data[math.floor(py/16)+1][math.ceil(px/16)+1] == 1 or
			collision.data[math.floor(py/16)+1][math.floor(px/16)+1] == 1 then
			py = math.ceil(py + 16 - py % 16)
		end
	end
	if dD then
		py = player.y + speed * dt
		if collision.data[math.ceil(py/16)+1][math.ceil(px/16)+1] == 1 or
			collision.data[math.ceil(py/16)+1][math.floor(px/16)+1] == 1 then
			py = math.ceil(py - py % 16)
		end
	end
	player.x = px
	player.y = py
end

function love.draw()
	map:draw()
	--map:drawCollisionMap(collision)
	love.graphics.setColor(0,0,0)
	love.graphics.rectangle( "fill", player.x, player.y, player.w, player.h)

	love.graphics.setColor(255,255,255)
	love.graphics.print(player.x/16, 300, 300, 0, 2)
	love.graphics.print(player.y/16, 300, 350, 0, 2)
end

function love.resize(w, h)
	map:resize(w, h)
end
This offers configurable speed with pixel perfect collision, takes into account dt to ensure similar speed across machines, and corrects diagonal speed (normally it would be too fast). All with less lines and more clarity. I think I need to pat myself on the back. :ultrahappy:
User avatar
Zilarrezko
Party member
Posts: 345
Joined: Mon Dec 10, 2012 5:50 am
Location: Oregon

Re: Fixing Collision

Post by Zilarrezko »

bdjnk wrote:
CaptainMaelstrom wrote:Honestly, collision detection is one of those things that's best left to those who really know it. Kind of like encryption and security, don't re-invent the wheel.
Aw, but reinventing the wheel is the best way to learn about wheels
THANK YOU! Couldn't have said it better me self.
User avatar
bdjnk
Citizen
Posts: 81
Joined: Wed Jul 03, 2013 11:44 pm

Re: Fixing Collision

Post by bdjnk »

I should really explain my code. Sorry for just dumping it. I should also actually address the question in the first post. Sorry for not doing that originally. Oh, and while I'm being sorry for things, sorry for bumping this thread again.
lilfinn wrote:...provide some guidance on how to improve it. Or if its just completely awful, maybe provide a better way to do it?
It's not awful. In fact, it's very close to correct. The problem is too much complexity, which is most likely the result of not enough time spent with a pencil and paper figuring out the math.

The first unecessary component is the canMoveDirection booleans. These obscure the fact that for each direction all I need to do is a series of tests to determine if or how much I can move, then do what I can. Just one test set, with no need for a boolean variable.

The second unecessary component is the player's grid tracking. This has led you down a wrong path, and introduced gratuitous complexity and clutter. Let's do the math directly instead.

Let's take a look at a better way to handle, for example, the up condition

Code: Select all

if love.keyboard.isDown("up") then
   py = player.y - speed * dt
   if collision.data[math.floor(py/16)+1][math.ceil(px/16)+1] == 1 or
      collision.data[math.floor(py/16)+1][math.floor(px/16)+1] == 1 then
      py = math.ceil(py + 16 - py % 16)
   end
end
Let's say py is 31.9 (this is a good test case because we're moving upwards, which is in the negative y direction (smaller is higher)) and px is 47.2 (randomly selected):

Code: Select all

 ceil(47.2 / 16) + 1 = 4
floor(47.2 / 16) + 1 = 3
floor(31.9 / 16) + 1 = 2
 ceil(31.9 + 16 - 31.9 % 16) = 32
Essentially, we are asking if tile[2][4] or tile[2][3] is blocked, because if either is blocked, we should only move so far as we're able, and because each block is 16 pixels, we can move to 32 with confidence.

Regarding diagonal movement speed, see this post.

The rest of what I did seems more self explanatory to me, but if it isn't clear to you, feel free to ask.
lilfinn
Prole
Posts: 10
Joined: Thu Jul 18, 2013 5:58 am

Re: Fixing Collision

Post by lilfinn »

I can't thank you enough. You helped make it work and explained it to a point that I understand what you are doing. Thanks you again so much.

Blake
Post Reply

Who is online

Users browsing this forum: Ahrefs [Bot], Bing [Bot], Semrush [Bot] and 8 guests