Variable is nii? - lua

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.

Related

Collision Detection in love2d(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

Check collision of ball with brick and undo the brick area to blank space and reverse the x velocity [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 2 years ago.
Improve this question
For making my project I want the ball to reverse the velocity when hit with the brick and erase that particular brick but can't get to work with the knowledge i have.
EDIT - Found the solution https://github.com/itswaqas14/brickBreaker
block_pos = {} -- table to store block positions
rows, columns = 30, 20 -- you decide how many
chance_of_block = 75 -- % chance of placing a block
block_width = math .floor( VIRTUAL_WIDTH /columns )
block_height = math .floor( VIRTUAL_HEIGHT /rows )
col = columns -1 -- don't loop through columns, just use final column
for row = 0, rows -1 do
if love .math .random() *100 <= chance_of_block then
local xpos = col *block_width
local ypos = row *block_height
block_pos[ #block_pos +1 ] = { x = xpos, y = ypos }
end -- rand
end -- #columns
and for printing the generated block in love.draw()
for b = 1, #block_pos do
local block = block_pos[b]
love .graphics .rectangle( 'line', block.x + 5, block.y, 5, 10 )
end -- #block_pos
-- random 2nd line of blocks
for b = 1, #block_pos do
local block = block_pos[b]
love .graphics .rectangle( 'line', block.x - 5, block.y, 5, 10 )
end -- #block_pos
All this is in main.lua since I am not familiar with Class concept in java and I have written a basic collision function in ball.lua which is imported in main.lua and I also have written paddle.lua for controlling the paddle
if self.x > box.x + box.width or self.x + self.width < box.x then
return false
end
if self.y > box.y + box.height or self.y + self.height < box.y then
return false
end
return true
end
how big is that ball? you need to add the radius to your collision detection. you could use pythag a² +b² = c² IF you absolutely need diagonal precision, but it's faster without. and at that scale, it's only one or two pixels, so you won't even notice.
-- right side of ball > left side of box or left side of ball < right side of box
if ( self.x +self.radius > box.x -box.width or self.x -self.radius < self.width +box.x )
-- top of ball < bottom of box or bottom of ball > top of box
and ( self.y -self.radius < box.y +box.height or self.y +self.radius > self.height -box.y ) then
block = nil -- collision detected, get rid of block at that [index] location
-- block_pos[b] = nil however you have it worded in this region of code
ball.dirX = -ball.dirX -- not sure how you are keeping track of ball direction,
-- but make vector reflect here
end

Error Trying to Draw Platforms in Lua/Love2D

New to Lua/Game dev in general here. I'm trying to write a code to draw the background of my game. I have four different spritesheets for things related to setting:
Background - which contains the drawn "background image" of the game
Platforms - which contain the platform sprites of the game
objectsBig - which contain the relatively bigger drawn objects in the background (example rocks, seaweed, etc)
objectsSmall - which contain the relatively smaller drawn objects (smaller rocks, plants, etc)
I made a function, called generateQuads() which is in my Util.lua file, which, given a spritesheet, splices it up into the different sprites in the spritesheet and returns those sprites. It is as follows:
-- Function to generate quads/cut the spritesheet
function generateQuads(atlas, tileWidth, tileHeight)
local sheetWidth = atlas:getWidth() / tileWidth
local sheetHeight = atlas:getHeight() / tileHeight
local sheetCounter = 1
local quads = {}
-- for every line of the sprite sheet
for y = 0, sheetHeight - 1 do
-- for every piece in the width
for x = 0, sheetWidth - 1 do
-- quads generated will be the ones specified by tileWidth and tileHeight
quads[sheetCounter] = love.graphics.newQuad(x * tileWidth, y * tileHeight, tileWidth,
tileHeight, atlas:getDimensions())
sheetCounter = sheetCounter + 1
end
end
return quads
end
I also have a class called Map, which is meant to contain information for the Map. The following are the functions in it:
FUNCTION TO SET UP THE CLASS
function Map:init()
-- Load the background sprite into variable
-- Each is 500x500, total is 1500x500
self.background = love.graphics.newImage('Graphics/platform/background/full.png')
-- Load all the ground platforms into variable
-- Each is 60x30, total is 240x30
self.platforms = love.graphics.newImage('Graphics/platform/ground/all.png')
-- Load all the bigger misc objects into variable
-- Each is 15x20, total is 15x120
self.objectsBig = love.graphics.newImage('Graphics/platform/objects/15x20/all.png')
-- Load all the smaller misc objects into variable
-- Each is 15x15, total is 60x15
self.objectsSmall = love.graphics.newImage('Graphics/platform/objects/15x15/all.png')
-- Setting the size for each of the variables IN PIXELS
self.backgroundWidth = 500
self.backgroundHeight = 1500
self.platformWidth = 60
self.platformHeight = 30
self.objectsBigWidth = 15
self.objectsBigHeight = 20
self.objectsSmallWidth = 15
self.objectsSmallHeight = 15
-- Setting the map size IN TILES
-- EXPERIMENTAL, CHECK AGAIN
self.mapWidth = 432
self.mapHeight = 243
-- Setting the width of the map IN PIXELS (in relation to platforms)
self.mapWidthPixels = self.backgroundWidth
self.mapHeightPixels = self.backgroundHeight
-- Storing our map features
self.tiles = {}
-- Storing the camera points, starting from the bottom
-- The 243 is the VIRTUAL_HEIGHT, which we needed to subtract to account for the
-- offset of the screen
self.camX = 0
self.camY = self.backgroundHeight - 243
-- Cutting each of the spritesheets
self.backgroundSprite = generateQuads(self.background, self.backgroundWidth, self.backgroundHeight)
self.platformSprite = generateQuads(self.platforms, self.platformWidth, self.platformHeight)
self.objectsBigSprite = generateQuads(self.objectsBig, self.objectsBigWidth, self.objectsBigHeight)
self.objectsSmallSprite = generateQuads(self.objectsSmall, self.objectsSmallWidth, self.objectsSmallHeight)
-- 'Setting' the tiles for the map to be background
for y = 1, self.mapHeight do
for x = 1, self.mapWidth do
self:setTile(x, y, BACKGROUND)
end
end
-- 'Setting' the platform tiles for where we start
for x = 1, self.mapWidth do
self:setTile(x, self.mapHeight - 303, GROUND_MIDDLE)
end
end
FUNCTION TO 'SET' WHERE IN THE X, Y COORDINATE THE TILES SHOULD BE
function Map:setTile(x, y, tile)
-- The table 'tiles' is going to store what tile should be in what x and y position
-- subtracting y by 1 so that it's 0 indexed, not 1 indexed
self.tiles[(y - 1) * self.mapWidth + x] = tile
end
FUNCTION TO RETURN WHAT TILE IS IN THE X, Y COORDINATE
function Map:getTile(x, y, tile)
-- This function is going to tell us what tile is set at this x and y position
return self.tiles[(y - 1) * self.mapWidth + x]
end
FUNCTION TO UPDATE
function Map:update(dt)
-- Moving the camera depending on the key being pressed
if love.keyboard.isDown('w') then
-- up movement, clamped between 0 and the other
self.camY = math.max(0, math.floor(self.camY - SCROLL_SPEED * dt))
elseif love.keyboard.isDown('a') then
-- left movement
self.camX = math.max(0, math.floor(self.camX - SCROLL_SPEED * dt))
elseif love.keyboard.isDown('s') then
-- down movement, subtracting VIRTUAL_HEIGHT to account for the screen offset
self.camY = math.min(self.backgroundHeight - VIRTUAL_HEIGHT, math.floor(self.camY + SCROLL_SPEED * dt))
elseif love.keyboard.isDown('d') then
-- right movement
self.camX = math.min(self.backgroundWidth - VIRTUAL_WIDTH, math.floor(self.camX + SCROLL_SPEED * dt))
end
end
FUNCTION TO RENDER
function Map:render()
for y = 1, self.mapHeight do
for x = 1, self.mapWidth do
-- Drawing the background first
love.graphics.draw(self.background, self.backgroundSprite[self:getTile(x, y)],
(x - 1) * self.backgroundWidth, (y - 1) * self.backgroundHeight)
love.graphics.draw(self.platform, self.platformSprite[self:getTile(x, y)],
(x - 1) * self.platformWidth, (y - 1) * self.platformHeight)
end
end
end
What I'm trying to do is splice up each spritesheet, and for set the background and bottom tiles so that I can see a bottom floor. I'm trying to do this through giving numbers to the components in my spritesheet, so that, for example, in a 1500x500 spritesheet for the background, the entire thing is considered 'one' sprite, and treated as such. Its number, 1, is given in a variable. Another example is a 240x30 platform spritesheet, where each 60x30 is considered 'one' sprite, and given a corresponding number, like so:
-- Know where the platforms are in the spritesheet
GROUND_LEFT = 1
GROUND_RIGHT = 2
GROUND_SMALL = 3
GROUND_MIDDLE = 4
-- Know where the backgrounds are in the spritesheet
BACKGROUND = 1
[...]
I want to then store each in the self.tiles list, so that I can access them at any time from the table (Do note that most of this is taken from the CS50 implementation of mario, and so I understand if I've gotten any concepts wrong). When I run this code, though, I get the following error:
Error
Error
Map.lua:135: bad argument #1 to 'draw' (Texture expected, got nil)
Traceback
[C]: in function 'draw'
Map.lua:135: in function 'render'
main.lua:48: in function 'draw'
[C]: in function 'xpcall'
Essentially, I just want to know a way in which I can set the bottom platform tiles, and then be able to iterate over the map and 'set' where the other object sprites should be. The background alone works fine, but once I added the:
-- 'Setting' the platform tiles for where we start
for x = 1, self.mapWidth do
self:setTile(x, self.mapHeight - 303, GROUND_MIDDLE)
end
and
love.graphics.draw(self.platform, self.platformSprite[self:getTile(x, y)],
(x - 1) * self.platformWidth, (y - 1) * self.platformHeight)
lines, the program wouldn't run.
Any help is appreciated!
EDIT: The line I'm getting an error on is the self.platform one.
i had the same problem before, and it turned out to be a typo when typing the variable. looking at your code, i saw that there was no self.platform, instead, there was self.platforms.
try:
love.graphics.draw(self.platforms, self.platformSprite[self:getTile(x, y)],
(x - 1) * self.platformWidth, (y - 1) * self.platformHeight)

lua Error : attempt to call global 'Class' (a boolean value)

I am new to lua language .I have downloaded a class.lua file from github. I wrote a code for game developement by making classes in lua with love2d framework.I was making a pong game that needs paddles and ball,but it is giving me error like this:
Error
Ball.lua:2: attempt to call global 'Class' (a boolean value)
Traceback
Ball.lua:2: in main chunk
[C]: in function 'require'
main.lua:6: in main chunk
[C]: in function 'require'
[C]: in function 'xpcall'
[C]: in function 'xpcall'
I have spent so much time on it to solve but nothing happens.
main.lua
code is:
push =require 'push'
Class=require 'class'
require "Ball"
require "Paddle"
WINDOW_WIDTH=1280
WINDOW_HEIGHT=720
VIRTUAL_WIDTH=432
VIRTUAL_HEIGHT=243
--speed at which we will move our paddles; multiplied by dt in update
PADDLE_SPEED=200
function love.load()
love.graphics.setDefaultFilter('nearest','nearest')
--more 'retro-looking' font object we can use for any text
--os.time() give the time from 1 jan 1970
math.randomseed(os.time())
smallFont=love.graphics.newFont('font.ttf',8)
--set love2d active font to the small font object
scoreFont=love.graphics.newFont('font.ttf' , 32)
--larger font for drawing the score on the screen
love.graphics.setFont(smallFont)
--initialize window with virtual resolution
push:setupScreen(VIRTUAL_WIDTH,VIRTUAL_HEIGHT,WINDOW_WIDTH,WINDOW_HEIGHT,{
fullscreen=false,
resizable=false,
vsync=true
})
--Paddle(x,y,width,height)
player1=Paddle(10,30,5,20)
player2=Paddle(VIRTUAL_WIDTH-10,VIRTUAL_HEIGHT-30,5,20)
--place a ball in the middle of the screen
ball=Ball(VIRTUAL_WIDTH/2-2,VIRTUAL_HEIGHT/2-2,4,4)
gameState='start'
end
function love.update(dt)
--player 1 movement
if love.keyboard.isDown('w') then
--add negative paddle speed to current Y scaled by deltaTime
player1.dy=-PADDLE_SPEED
--player1Y= math.max ( 0, player1Y + -PADDLE_SPEED * dt)
elseif love.keyboard.isDown('s') then
--add positive paddle speed to current Y scaled by deltaTime
player1.dy=PADDLE_SPEED
else
player.dy=0
--player1Y = math.min ( VIRTUAL_HEIGHT -20 , player1Y + PADDLE_SPEED * dt)
end
--player 2 movement
if love.keyboard.isDown('up') then
--add negative paddle speed to current Y scaled by deltaTime
player2.dy=-PADDLE_SPEED
--player2Y=math.max( 0, player2Y + -PADDLE_SPEED * dt)
elseif love.keyboard.isDown('down') then
player2.dy=PADDLE_SPEED
else
player2.dy=0
end
if gameState=='play' then
ball:update(dt)
end
player1:update(dt)
player2:update(dt)
end
function love.keypressed(key)
--key can be acessed by sting name
if key=='escape' then
--function love give us to terminate application
love.event.quit()
elseif key == 'enter' or key == 'return' then
if gameState == 'start' then
gameState = 'play'
else
gameState = 'start'
ball:reset()
end
end
end
function love.draw()
--begin rendering at virtual screen
push:apply('start')
--recent versions of pong has grey color it sets grey color
love.graphics.clear( 0 , 0 , 0 , 255)
--note we are now using virtual width and height now for text placement
--draw welcome text on top of the screen
love.graphics.setFont(smallFont)
if gameState == 'start' then
love.graphics.printf('hello start state!', 0 , 20 , VIRTUAL_WIDTH , 'center' )
else
love.graphics.printf('hello play state!', 0 , 20 , VIRTUAL_WIDTH , 'center')
end
love.graphics.setFont(scoreFont)
love.graphics.print(tostring(player1Score),VIRTUAL_WIDTH / 2 - 50 , VIRTUAL_HEIGHT / 3)
love.graphics.print(tostring(player2Score),VIRTUAL_WIDTH / 2 + 30 , VIRTUAL_HEIGHT / 3)
player1:render()
player2:render()
ball:render()
--end rendering at virtual resolution
push:apply('end')
end
Ball.lua
code is:
--making a Ball class
Ball = Class{}
function Ball:init(x,y,width,height)
self.x=x
self.y=y
self.width=width
self.height=height
self.dy=math.random(2) ==1 and -100 or 100
self.dx=math.random(-50,50)
end
--[[places the ball in the middle of the screen with an
initial random velocity on both axes.]]
function Ball:reset()
self.x=VIRTUAL_WIDTH/2-2
self.y=VIRTUAL_HEIGHT/2-2
self.dy=math.random(2) == 1 and -100 or 100
self.dx=math.random(-50,50)
end
function Ball:update(dt)
self.x=self.x+self.dx * dt
self.y=self.y+self.dy * dt
end
function Ball:render()
love.graphics.rectangle('fill',self.x,self.y,self.width,self.height)
end
return Ball
plz help me on this matter.
Thanks in advance.
I had the same problem with the same CS50 course. I first had:
Class = require 'class'
push = require 'push'
require 'Ball'
require 'Paddle'
in main.lua and:
Paddle = class{}
in Paddle.lua.
I solved it by changing:
Class = require 'class'
to
require 'class'
and
Paddle = class{}
to
Paddle = class()
I had to read the readme from the class.lua library to figure it out.
Seems like you are on the tracks of the cs50 online course, I was having the same situation.
When I downloaded the distribution code, I saw that the class.lua file is not empty. When I changed the class.lua file with the file I have in my folder, problem was seemed to be solved.
Download the distribution code here

How to use bounding box in Love2d?

I've been using some extremely bulky code to detect collision between simple objects, and I've heard about bounding boxes. I can't find any tutorials on how to use it, so I'm asking about how to use it. Here is how I detect collision:
function platform.collision()
if player.x + player.width / 2 <= platform.x + platform.width and
player.x + player.width / 2 >= platform.x and
player.y + player.height <= platform.y + platform.height and
player.y + player.height >= platform.y then
The MDN has a rather concise article on 2D collision detection. Being the MDN, the examples are in javascript, but are easily translated to, and applicable in, any language - including Lua.
Let's take a look:
Axis-Aligned Bounding Box
One of the simpler forms of collision detection is between two rectangles that are axis aligned — meaning no rotation. The algorithm works by ensuring there is no gap between any of the 4 sides of the rectangles. Any gap means a collision does not exist.
Their example, translated to Lua:
local rect1 = { x = 5, y = 5, width = 50, height = 50 }
local rect2 = { x = 20, y = 10, width = 10, height = 10 }
if
rect1.x < rect2.x + rect2.width and
rect1.x + rect1.width > rect2.x and
rect1.y < rect2.y + rect2.height and
rect1.height + rect1.y > rect2.y
then
-- collision detected!
end
-- filling in the values =>
if
5 < 30 and
55 > 20 and
5 < 20 and
55 > 10
then
-- collision detected!
end
A live example, again in JavaScript, demonstrates this well.
Here's a quick (and imperfect) Love2D example you can throw into a main.lua and play around with.
local function rect (x, y, w, h, color)
return { x = x, y = y, width = w, height = h, color = color }
end
local function draw_rect (rect)
love.graphics.setColor(unpack(rect.color))
love.graphics.rectangle('fill', rect.x, rect.y,
rect.width, rect.height)
end
local function collides (one, two)
return (
one.x < two.x + two.width and
one.x + one.width > two.x and
one.y < two.y + two.height and
one.y + one.height > two.y
)
end
local kp = love.keyboard.isDown
local red = { 255, 0, 0, 255 }
local green = { 0, 255, 0, 255 }
local blue = { 0, 0, 255, 255 }
local dim1 = rect(5, 5, 50, 50, red)
local dim2 = rect(20, 10, 60, 40, green)
function love.update ()
if kp('up') then
dim2.y = dim2.y - 1
end
if kp('down') then
dim2.y = dim2.y + 1
end
if kp('left') then
dim2.x = dim2.x - 1
end
if kp('right') then
dim2.x = dim2.x + 1
end
dim2.color = collides(dim1, dim2) and green or blue
end
function love.draw ()
draw_rect(dim1)
draw_rect(dim2)
end
Oka explained it very well. This works for everything rectangular, not rotated and axis aligned. And you even already did it that way. This is great for buttons and the like!
But what I like doing is using (invisible) circles around objects and see if these collide. This works for everything where height is about the same as the width (which is the case for many sidescrolling platformers or top-down RPGs).
It's quite handy if you want to have the object centered at the current position. And it's especially helpful to simulate a finger on a touchscreen device, because a finger is quite a bit bigger than a mouse cursor. ;)
Here's an example on how to use this method. You can copy it as an actual game, it'll work.
--[[ Some initial default settings. ]]
function love.load()
settings = {
mouseHitbox = 5, -- A diameter around the mouse cursor.
-- For a finger (thouchscreen) this could be bigger!
}
objects = {
[1] = {
x = 250, -- Initial X position of object.
y = 200, -- Initial Y position of object.
hitbox = 100, -- A diameter around the CENTER of the object.
isHit = false -- A flag for when the object has been hit.
},
[2] = {
x = 400,
y = 250,
hitbox = 250,
isHit = false
}
}
end
--[[ This is the actual function to detect collision between two objects. ]]
function collisionDetected(x1,y1,x2,y2,d1,d2)
-- Uses the x and y coordinates of two different points along with a diameter around them.
-- As long as these two diameters collide/overlap, this function returns true!
-- If d1 and/or d2 is missing, use the a default diameter of 1 instead.
local d1 = d1 or 1
local d2 = d2 or 1
local delta_x = x2 - x1
local delta_y = y2 - y1
local delta_d = (d1 / 2) + (d2 / 2)
if ( delta_x^2 + delta_y^2 < delta_d^2 ) then
return true
end
end
--[[ Now, some LÖVE functions to give the collisionDetection() some context. ]]
function love.draw()
for i=1,#objects do -- Loop through all objects and draw them.
if ( objects[i].isHit ) then
love.graphics.setColor(255, 0, 0) -- If an object is hit, it will flash red for a frame.
objects[i].isHit = false
else
love.graphics.setColor(255, 255, 255)
end
love.graphics.circle("line", objects[i].x, objects[i].y, objects[i].hitbox/2)
end
end
-- You can use the following to check, if any object has been clicked on (or tapped on a touch screen device).
function love.mousepressed(x,y,button)
if ( button == 1 ) then
local i = objectIsHit(x,y) -- Check, if an object has been hit.
if ( i ) then
-- The object number 'i' has been hit. Do something with this information!
objects[i].isHit = true
end
end
end
function objectIsHit(x,y)
for i=1,#objects do -- Loop through all objects and see, if one of them has been hit.
if ( collisionDetected(x, y, objects[i].x, objects[i].y, settings.mouseHitbox, objects[i].hitbox) ) then
return i -- This object has been hit!
end
end
end
-- For the sake of completeness: You can use something like the following to check, if the objects themselves collide.
-- This would come in handy, if the objects would move around the screen and then bounce from each other, for example.
function love.update(dt)
if ( collisionDetected(objects[1].x, objects[1].y, objects[2].x, objects[2].y, objects[1].hitbox, objects[2].hitbox) ) then
-- The objects collided. Do something with this information!
end
end
As you can see, the collisionDetection() function is quite easy and intuitive to use.
Hopefully I could give you some more insight. And have fun with LÖVE 2D! :)

Resources