Collision Detection in love2d(lua) - lua
I am doing a game development course by cs50 where Colton Ogden teaches love2d which is in lua. I am facing a problem in collision detection. The below code/logic works fine when the object is not rotating example love.graphics.draw(texture, x, y, r).
Here x and y is some value and r(rotation is 0). Value of x and y keeps on changing.
function Projectile:collides(target)
if self.x > target.x + target.width or target.x > self.x + self.width then
return false
end
if self.y > target.y + target.height or target.y > self.y + self.height then
return false
end
return true
end
But the above code doesn't work for target which is rotating i.e., r has some value which keeps on changing.
Below is how i am rotating an object.
function Meteor:init(speed)
self.r = math.random(-1, 1)
end
function Meteor:update(dt)
if self.r > 0 then
self.r = self.r + math.pi/9 * dt
elseif self.r < 0 then
self.r = self.r - math.pi/9 * dt
else
self.r = self.r
end
if self.y < VIRTUAL_HEIGHT + 110 then
self.y = self.y + self.speed * dt
else
self.remove = true
end
end
function Meteor:render()
love.graphics.drawLayer(self.meteor, self.type, self.x, self.y, self.r)
end
Sometimes it is easier and sufficient to just approximate an object's collision. In LÖVE you can specify the texture offset in love.graphics.draw(). Let the texture rotate at its center and see if an approximation works.
If you really need rectangles, which are not axis aligned, you may want to use a collision library. HardonCollider works perfect.
If you plan on adding physics too, then the LÖVE physics module makes more sense.
As #Luke100000 wrote LÖVE has already a collision detection in love.physics implemented.
Read and try: https://love2d.org/wiki/Tutorial:PhysicsCollisionCallbacks
For a zero gravity world i use for example...
function love.load()
local meter=4.5
love.physics.setMeter(meter)
ZeroGravity=love.physics.newWorld(0,0,true)
ZeroGravity:setCallbacks(Hit,Release,preSolve,postSolve) -- <Collision Callbacks>
end
And for example the "Hit" callback function...
-- <Collision Callback Functions>
Hit=function(a,b)
a:getBody():setUserData(os.date('%H:%M:%S'))
b:getBody():setUserData(os.date('%H:%M:%S'))
a:getUserData():emit(256)
b:getUserData():emit(256)
return true
end
The particlesystem that emitting 256 parts at "Hit" is assigned to the fixture in userdata of an physics body at creation...
function addAsteroid(x,y,r,kind)
local body=love.physics.newBody(ZeroGravity,x,y,kind)
local shape=love.physics.newCircleShape(r)
local fixture=love.physics.newFixture(body,shape,.01)
fixture:setUserData(psystem:clone()) -- Particlesystem for emitting
fixture:setDensity(1)
fixture:setFriction(1)
fixture:setRestitution(1)
--fixture:setFilterData(1,1,-1)
fixture:getUserData():start()
return body
end
A particle system can be very complex but it is worth for learning it.
Here i give you an example of mine that works with quads...
-- planets.lua
fields=1
quads={love.graphics.newImage("planets.png")
}
local assets={x=0,y=0,size=.1,time=-1,
[1]=love.graphics.newQuad(138,33,300,300,quads[1]:getDimensions()),
[2]=love.graphics.newQuad(505,37,300,300,quads[1]:getDimensions()),
[3]=love.graphics.newQuad(871,21,300,300,quads[1]:getDimensions()),
[4]=love.graphics.newQuad(1238,22,300,300,quads[1]:getDimensions()),
[5]=love.graphics.newQuad(1606,15,300,300,quads[1]:getDimensions()),
[6]=love.graphics.newQuad(1974,11,300,300,quads[1]:getDimensions()),
[7]=love.graphics.newQuad(1976,349,300,300,quads[1]:getDimensions()),
[8]=love.graphics.newQuad(136,382,300,300,quads[1]:getDimensions()),
[9]=love.graphics.newQuad(504,379,300,300,quads[1]:getDimensions()),
[10]=love.graphics.newQuad(871,366,300,300,quads[1]:getDimensions()),
[11]=love.graphics.newQuad(1236,362,300,300,quads[1]:getDimensions()),
[12]=love.graphics.newQuad(19,724,300,300,quads[1]:getDimensions()),
[13]=love.graphics.newQuad(1047,698,300,300,quads[1]:getDimensions()),
[14]=love.graphics.newQuad(1376,687,300,300,quads[1]:getDimensions()),
[15]=love.graphics.newQuad(1721,686,300,300,quads[1]:getDimensions()),
[16]=love.graphics.newQuad(2042,684,300,300,quads[1]:getDimensions()),
[17]=love.graphics.newQuad(347,704,660,300,quads[1]:getDimensions())
}
pquads={x=0,y=0,size=1,time=-1}
for i=12,12 do table.insert(pquads,assets[i]) end -- recall
assets=empty
psystem=love.graphics.newParticleSystem(quads[1],72)
psystem:setBufferSize(4096)
psystem:setLinearAcceleration(-150,-150,150,150) -- Random movement in all directions.
psystem:setEmissionArea('ellipse',145,145,math.rad(math.random(360)),false)
psystem:setQuads(pquads)
psystem:setSpread(75)
--[[psystem:setColors({love.math.random(),
love.math.random(),
love.math.random(),
0.3},{love.math.random(),
love.math.random(),
love.math.random(),
0.6},{love.math.random(),
love.math.random(),
love.math.random(),
0.3})]]--
--psystem:setColors({1,0,0,1},{0,0,1,1},{1,0,0,1})
--psystem:setColors({0,0,1,1},{1,1,1,1},{1,1,0,1},{1,0,0,1})
psystem:setSizes(math.random()*.05,0)
--psystem:setSizes(.1,.2,.3,.4,.3,.2,.1)
psystem:setSizeVariation(0)
psystem:setEmitterLifetime(pquads.time)
psystem:setParticleLifetime(pquads.size*11)
psystem:setEmissionRate(pquads.size*.11)
psystem:setInsertMode('top')
-- psystem:setPosition(-150,-150)
The assets i am using i get from...
https://opengameart.org/art-search?keys=planets
Related
Variable is nii?
I'm using LOVE2D and Lua for making games recently. I'm updating Breakout and I have an error in Paddle.lua. Code: Paddle = Class{} --[[ Our Paddle will initialize at the same spot every time, in the middle of the world horizontally, toward the bottom. ]] size = math.random(4) function Paddle:init(skin, size) -- x is placed in the middle self.x = VIRTUAL_WIDTH / 2 - 32 -- y is placed a little above the bottom edge of the screen self.y = VIRTUAL_HEIGHT - 32 -- start us off with no velocity self.dx = 0 self.size = size self.height = 16 if self.size == 1 then self.width = 32 elseif self.size == 3 then self.width = 96 elseif self.size == 4 then self.width = 128 else self.width = 64 end -- the skin only has the effect of changing our color, used to offset us -- into the gPaddleSkins table later self.skin = skin end function Paddle:update(dt) -- keyboard input if love.keyboard.isDown('left') then self.dx = -PADDLE_SPEED elseif love.keyboard.isDown('right') then self.dx = PADDLE_SPEED else self.dx = 0 end -- math.max here ensures that we're the greater of 0 or the player's -- current calculated Y position when pressing up so that we don't -- go into the negatives; the movement calculation is simply our -- previously-defined paddle speed scaled by dt if self.dx < 0 then self.x = math.max(0, self.x + self.dx * dt) -- similar to before, this time we use math.min to ensure we don't -- go any farther than the bottom of the screen minus the paddle's -- height (or else it will go partially below, since position is -- based on its top left corner) else self.x = math.min(VIRTUAL_WIDTH - self.width, self.x + self.dx * dt) end end --[[ Render the paddle by drawing the main texture, passing in the quad that corresponds to the proper skin and size. ]] function Paddle:render() love.graphics.draw(gTextures['main'], gFrames['paddles'][self.size + 4 * (self.skin - 1)], self.x, self.y) end Error: Error src/Paddle.lua:83: attempt to perform arithmetic on field 'size' (a nil value) Traceback src/Paddle.lua:83: in function 'render' src/states/ServeState.lua:68: in function 'render' src/StateMachine.lua:26: in function 'render' main.lua:210: in function 'draw' [C]: in function 'xpcall' Saying that the value is nii even though I assigned it. How do I fix this?
Function parameters are local to the function's body. So inside Paddle.Init size is a local variable that shadows the global variable size. As you call Paddle.Init without providing the size parameter, size is nil within the function leading to the observed error. See Wikipedia: Variable Shadowing Many people use a prefix to denote global variables. g_size for example. Then you could do something like: g_size = 4 function Paddle:Init(skin, size) self.size = size or g_size end Which would make self.size default to g_size if the size parameter is not provided. Another option is to call the paddle constructor with your global size as input.
I am trying to create an object AllBalls in my Playstate but when ever i try to initialize the object i get an error
In my playstatue I create an instance if my object as such function PlayState:enter(params) self.paddle = params.paddle self.bricks = params.bricks self.health = params.health self.score = params.score self.highScores = params.highScores self.level = params.level self.recoverPoints = 5000 self.extendPoints = 7000 -- give ball random starting velocity self.inPlayBalls= AllBalls(params.ball) self.inPlayBalls[1].dx = math.random(-200, 200) self.inPlayBalls[1].dy = math.random(-50, -60) end the AllBalls class is a simple class which goes as such AllBalls = Class{} function AllBalls:init(p) self.ball=p self.inPlayBalls={self.ball} end function AllBalls:update(dt,target) for k, ball in pairs(self.inPlayBalls) do ball:update(dt) if ball:collides(target) then -- raise ball above paddle in case it goes below it, then reverse dy ball.y = target.y - 8 ball.dy = -target.dy -- -- tweak angle of bounce based on where it hits the paddle -- -- if we hit the paddle on its left side while moving left... if ball.x < target.x + (target.width / 2) and target.dx < 0 then ball.dx = -50 + -(8 * (target.x + target.width / 2 - ball.x)) -- else if we hit the paddle on its right side while moving right... elseif ball.x > target.x + (target.width / 2) and target.dx > 0 then ball.dx = 50 + (8 * math.abs(target.x + target.width / 2 - ball.x)) end gSounds['paddle-hit']:play() end if math.abs(ball.dy) < 150 then ball.dy = ball.dy * 1.02 end if ball.remove then table.remove(self.AllBalls, k) end end end function AllBalls:collides(target) for k, ball in pairs(self.inPlayBalls) do if ball:collides(target) then if ball.x + 2 < brick.x and ball.dx > 0 then -- flip x velocity and reset position outside of brick ball.dx = -ball.dx ball.x = brick.x - 8 -- right edge; only check if we're moving left, , and offset the check by a couple of pixels -- so that flush corner hits register as Y flips, not X flips elseif ball.x + 6 > brick.x + brick.width and ball.dx < 0 then -- flip x velocity and reset position outside of brick ball.dx = -ball.dx ball.x = brick.x + 32 -- top edge if no X collisions, always check elseif ball.y < brick.y then -- flip y velocity and reset position outside of brick ball.dy = -ball.dy ball.y = brick.y - 8 -- bottom edge if no X collisions or top collision, last possibility else -- flip y velocity and reset position outside of brick ball.dy = -ball.dy ball.y = brick.y + 16 end end end end function AllBalls:add(ball) local newBall=Ball(ball.skin) newBall.x=ball.x newBall.dx=math.random(-200,200) newBall.y=ball.y newBall.dy=ball.dy table.insert(self.inPlayBalls, newBall) end function AllBalls:out() for k, ball in pairs(self.inPlayBalls) do if ball.y >= VIRTUAL_HEIGHT then ball.remove=true end end end when ever i enter the playstate I get the following error src/states/PlayStatue.lua:37 attempt to index nil value I am new to Lua as a whole and i probably made a silly mistake that i couldn't find so any help would be appricated
The problem likely comes from this assignment self.inPlayBalls= AllBalls(params.ball) From looking at your GitHub, inPlayBalls is an attribute of an AllBalls instance. That means you would have to create your AllBalls instance first, then get the attribute to assign it to a variable, such as in the following: self.allBalls = AllBalls(params.ball) self.inPlayBalls = self.allBalls.inPlayBalls
How to integrate computing time in AI reaction?
My program is an implementation of Pong where one of the paddles is to be moved by the computer and the other by the user. The program works fine with errors being made by the AI for realism. However, the movement of my paddle on screen is stuttering and it seems to be skipping one or two frames. The program is in Lua(+Love2D). function Paddle:comp_move(dt) error = math.random(3) == 2 and true or false start_time = os.time() diff = 0 if ball:collides(self) == false then if ball.y > self.y + self.height then -- Ball is below paddle -- Ball is moving up and difference is more than 20 pixels if ball.dy < 0 and (ball.y - self.y - self.height) > 20 then -- move down if error == false then diff = os.difftime(os.time() - start_time) self.y = math.min(VIRTUAL_HEIGHT - 20, self.y + PADDLE_SPEED*(dt)) end end -- Ball is moving down if ball.dy > 0 then -- move down if error == false then diff = os.difftime(os.time() - start_time) self.y = math.min(VIRTUAL_HEIGHT - 20, self.y + PADDLE_SPEED*(dt)) end end elseif ball.y + ball.height < self.y then -- Ball is above paddle -- Ball is moving down if ball.dy > 0 and (self.y - ball.y - ball.height) > 20 then -- move up if error == false then diff = os.difftime(os.time() - start_time) self.y = math.max(0,self.y - PADDLE_SPEED*(dt)) end elseif ball.dy < 0 then -- Ball is moving up -- move up if error == false then diff = os.difftime(os.time() - start_time) self.y = math.max(0,self.y - PADDLE_SPEED*(dt)) end end end end I am calculating the time taken to compute by the PC but what operation should i do with it normalize my paddle's movement.
From your code it seems the paddle speed is constant; it might be an idea to replace this by a function, so that initially the paddle moves more slowly, then speeds up, and then slows down again as it arrives at the desired location. This would make it more realistic in terms of movement behaviour, and it might also solve your problem of stuttering. Your constant PADDLE_SPEED would need to be replaced by some function that takes a step and returns a value (probably between 0.0 and PADDLE_SPEED) at the appropriate time.
How to Check Table of Circles don't collide as they grow
I have a table that is created by adding random x,y and r (radius), which I use to draw circles. First they are tested to ensure the new circles don't overlap existing ones. These circles then grow slowly over time. I'm trying to work out how to test my rings table for when they grow so much that they intersect. I cant find a way to test the first one against all others in the table, then the 2nd one against all remaining ones etc. Removing any that overlap. Started with this but realized it wont work at best it will compare itself to the next circle only but crash when at the end of the table. local function newRing() while true do -- infinite loop to create new rings for i, v in ipairs(rings) do --[[ collision calculations on all rings in table until a collision is detected using Pythagoras to calculate distance]] if not collides then rX= v.x rY = v.y rR = v.r local dx = rX - rings[i+1].x local dy = rY - rings[i+1].y local distCalc = dx * dx + dy * dy if distCalc <= ((rings[i+1].r + ringWidth) + (rR + ringWidth))^2 then collides = true break -- restarts while loop once one collision is found end -- end if distCalc block end -- i,v block break end -- end if not collides block end -- end while loop end
-- remove all collided rings for k = #rings, 1, -1 do local rX = rings[k].x local rY = rings[k].y local rR = rings[k].r local collides for j = k + 1, #rings do local dx = rX - rings[j].x local dy = rY - rings[j].y local distCalc = dx * dx + dy * dy if distCalc <= ((rings[j].r + ringWidth) + (rR + ringWidth))^2 then collides = true break end end if collides then -- do something here (erase ring[k] from the screen, etc.) table.remove(rings, k) end end
Corona Lua display object y property out by three ten-millionths of a pixel (!)
When I run this code (which is based on the "DragMe" sample app) I get very unexpected results in very particular circumstances. My "snapToGrid" function sets x and y to the nearest round 100 value after a drag. The "examine" function outputs the x and y values after an object is created, moved or rotated (by clicking on it). If you place an object in row 5 (so y = 500) and rotate it you will see that the y value changes to 499.99996948242 but this does not happen in any other row. How can this be explained? I notice this does not happen if the physics bodies are not added to the display object. I know I could call snapToGrid after rotation or round the value before I use it for anything else but I think there is an important opportunity here for me to learn something useful about Corona. local function snapToGrid(t) modHalf = t.x % t.width if modHalf > t.width/2 then t.x = t.x + (t.width-modHalf) end if modHalf < t.width/2 then t.x = t.x - modHalf end modHalfY = t.y % t.height if modHalfY > t.height/2 then t.y = t.y + (t.height-modHalfY) end if modHalfY < t.height/2 then t.y = t.y - modHalfY end display.getCurrentStage():setFocus( nil ) t.isFocus = false return true end function rotatePiece(target) if target.rotation == 270 then target.rotation = 0 else target.rotation = target.rotation + 90 end end local function dragBody(event) local target = event.target local phase = event.phase local halfWidth = target.width/2 local halfHeight = target.height/2 --get tileX and tileY relative to tile centre local tileX = event.x-target.x local tileY = event.y-target.y local modHalf = "" local snap = 15 if phase == "began" then -- Make target the top-most object display.getCurrentStage():setFocus( target ) -- Spurious events can be sent to the target, e.g. the user presses -- elsewhere on the screen and then moves the finger over the target. -- To prevent this, we add this flag. Only when it's true will "move" -- events be sent to the target. target.isFocus = true -- Store initial position target.x0 = event.x - target.x target.y0 = event.y - target.y elseif target.isFocus then if phase == "moved" then -- Make object move (we subtract target.x0,target.y0 so that moves are -- relative to initial grab point, rather than object "snapping"). target.x = event.x - target.x0 target.y = event.y - target.y0 if target.x > display.contentWidth - (target.width/2) then target.x = display.contentWidth - (target.width/2) end if target.y > display.contentHeight - (target.height/2) then target.y = display.contentHeight - (target.height/2) end if target.x < 0 + (target.width/2) then target.x = 0 + (target.width/2) end if target.y < 0 + (target.height/2) then target.y = 0 + (target.height/2) end modHalf = target.x % target.width if modHalf < snap then target.x = target.x - modHalf end if modHalf > ((target.width) - snap) then target.x = target.x + ((target.width)-modHalf) end modHalfY = target.y % target.height if modHalfY < snap then target.y = target.y - modHalfY end if modHalfY > ((target.height) - snap) then target.y = target.y + ((target.height)-modHalfY) end hasMoved = true return true elseif phase == "ended" then if hasMoved then hasMoved = false snapToGrid(target) --tile has moved examine(target) return true else --rotate piece rotatePiece(target) display.getCurrentStage():setFocus( nil ) target.isFocus = false --tile has rotated examine(target) return true end end end -- Important to return true. This tells the system that the event -- should not be propagated to listeners of any objects underneath. return true end local onTouch = function(event) if event.phase == "began" then local tile = {} img = display.newRect(event.x,event.y,100,100) img:addEventListener( "touch", dragBody ) snapToGrid(img) --top right corner and top middle solid topRight = {16,-16,16,50,50,50,50,-16} --top left and left middle solid topLeft = {-16,-16,-16,-50,50,-50,50,-16} --bottom right and right middle solid bottomRight = {16,16,16,50,-50,50,-50,16} --bottom left and bottom middle solid bottomLeft = {-16,16,-16,-50,-50,-50,-50,16} physics.addBody( img, "static", {shape=topRight}, {shape=topLeft}, {shape=bottomLeft}, {shape=bottomRight} ) --new tile created examine(img) return true end end function examine(img) print("--------------------------------------------------") if img ~= nil then print("X: "..img.x..", Y: "..img.y) end print("--------------------------------------------------") end local img local physics = require( "physics" ) physics.setDrawMode( "hybrid" ) --draw gridlines for i = 49, display.contentHeight, 100 do local line = display.newLine(0,i,display.contentWidth,i) local line2 = display.newLine(0,i+2,display.contentWidth,i+2) end for i = 49, display.contentWidth, 100 do local line = display.newLine(i,0,i,display.contentHeight ) local line2 = display.newLine(i+2,0,i+2,display.contentHeight ) end --init physics.start() Runtime:addEventListener("touch", onTouch)
There several possibilities. Firstly, since there are no integers in Lua, all numbers are double floating point values. According to FloatingPoint on Lua wiki, Some vendors' printf implementations may not be able to handle printing floating point numbers accurately. Believe it or not, some may incorrectly print integers (that are floating point numbers). This can manifest itself as incorrectly printing some numbers in Lua. Indeed, try the following: for i=1,50,0.01 do print(i) end You will see that a lot of numbers print exactly as you would expect, by many print with an error of 10^-12 or even 2 x 10^-12. However, your x prints fine when you don't give it to physics module. So surely the physics module does some computation on object position and changes it. I would certainly expect that for dynamic objects (due to collision detection), but here your object seems to be "static". So it must be that physics adjusts x even for static objects. The adjustment is so small that it would not be visible on the screen (you can't perceive any motion less than a pixel), but you're right that it is interesting to ask why. The SO post Lua: subtracting decimal numbers doesn't return correct precision is worth reading; it has some links you might find interesting and gives the neat example that decimal 0.01 cannot be represented exactly in base 2; just like 1/3 can't be represented exactly in base 10 (but it can in base 3: it would be 0.1 base 3!). The question shows that, although Lua (or possibly, the underlying C) is smart enough to print 0.01 as 0.01, it fails to print 10.08-10.07 as 0.01. If you really want to shake your understanding of floating point values, try this: > a=0.3 > b=0.3 > print(a==b) true > -- so far so good; now this: > a=0.15 + 0.15 > b=0.1 + 0.2 > print(a,b) 0.3 0.3 > print(c==d) false -- woa! This is explained by the fact that 0.15 in binary has a small error which is different from that of 0.1 or 0.2, so in terms of bits they are not identical; although when printed, the difference is too small to show. You may want to read the Floating Point Guide.
Actually this problem only occurs if you add the physics, so I think it is fair to say that the issue is caused by transferring control to box2d, not by Corona handling of the number alone. I asked on Corona forums and got this answer. http://forums.coronalabs.com/topic/46245-display-object-that-has-static-physics-body-moves-very-slightly-when-rotated/