Game Dev - Having trouble with AABB collision detection during jumps - lua

My collision detection works by getting intersection between rectangles and reversing the effect. This is happening during each frame. It works great except in the case where the player is sitting on top of a corner and jumps. every once in a while the vertical intersection is greater than the horizontal intersection which makes my player slide down the side of the platform. Any suggestions?
-- detect initial collision
if mathy.hasCollided(player, platform) then
local playerBoundaries = player:boundaries()
-- list of intersections between platform and player
local bottomBoundary = mathy.bottomBoundary( playerBoundaries, platform )
local topBoundary = mathy.topBoundary( playerBoundaries, platform )
local rightBoundary = mathy.rightBoundary( playerBoundaries, platform )
local leftBoundary = mathy.leftBoundary( playerBoundaries, platform )
local smallestDist = ""
local smallestBoundary
local boundaries = {
bottom = bottomBoundary,
top = topBoundary,
right = rightBoundary,
left = leftBoundary
}
-- get the smallest intersection (thats the side we're probably closest to)
for direction, boundary in pairs(boundaries) do
if not smallestBoundary then
smallestBoundary = boundary
smallestDist = direction
end
if smallestBoundary > boundary then
smallestBoundary = boundary
smallestDist = direction
end
end
-- reverse effects depending on collision location
if smallestDist == "bottom" then
player.desiredPos:add(diffX, -bottomBoundary)
player.velocity.y = 0
player.onGround = true
elseif smallestDist == "top" then
player.velocity.y = 250
player.desiredPos:add(0, topBoundary)
elseif smallestDist == "right" then
player.desiredPos:add(-rightBoundary, 0)
elseif smallestDist == "left" then
player.desiredPos:add(leftBoundary, 0)
end
end

Its hard to tell from the short clip, but I think the issue is a result of checking for the smallest intersection instead of checking for the intersection in the direction of the object's velocity. You could try something like this in place of the smallestBoundary loop:
local boundary = nil
if (player.velocity.y > 0) then
boundary = topBoundary
elseif (player.velocity.y < 0) then
boundary = bottomBoundary
elseif (player.velocity.x > 0) then
boundary = rightBoundary
elseif (player.velocity.x < 0) then
boundary = leftBoundary
end
Of course, this isn't as robust as it can be. You might also try to combine the two approaches and do something like this in place of the smallestBoundary loop:
local yMod = math.abs(player.velocity.y)
local xMod = math.abs(player.velocity.x)
local topMod = player.velocity.y > 0 and yMod or 1
local bottomMod = player.velocity.y < 0 and yMod or 1
local rightMod = player.velocity.x > 0 and xMod or 1
local leftMod = player.velocity.x < 0 and xMod or 1
local boundaries = {
bottom = (bottomMod / MAX_VELOCITY) * bottomBoundary,
top = (topMod / MAX_VELOCITY) * topBoundary,
right = (rightMod / MAX_VELOCITY) * rightBoundary,
left = (leftMod / MAX_VELOCITY) * leftBoundary
}
for direction, boundary in pairs(boundaries) do
if not smallestBoundary then
smallestBoundary = boundary
smallestDist = direction
end
if smallestBoundary > boundary then
smallestBoundary = boundary
smallestDist = direction
end
end
This will do exactly what you're doing now, but will adjust the size of the boundary (in context of the comparison only) by the velocity of the player in that direction. So if you're moving in x with -5 and in y with -10, downward collisions in the y plane will have twice the weight of leftward collisions in the x plane. Of course, there's always the other option of adjusting the player in every plane of collision:
if bottomBoundary > 0 then
player.desiredPos:add(diffX, -bottomBoundary)
player.velocity.y = 0
player.onGround = true
end
if topBoundary > 0 then
player.velocity.y = 250
player.desiredPos:add(0, topBoundary)
end
if rightBoundary > 0 then
player.desiredPos:add(-rightBoundary, 0)
end
if leftBoundary > 0 then
player.desiredPos:add(leftBoundary, 0)
end
This last method would make the most sense, except you don't seem to handle collisions in all directions uniformly, for whatever reason, so it might not fit your architecture.
Keep in mind that I'm not familiar with the framework you're using, so this code might not work out of the box. Also, this post assumes that +y is up, -y is down, +x is right, and -x is left.

Related

How to make an object move from top to bottom in Lua?

In a game I'm making, there's a square where the game is happening inside. I want the circles that spawn in to start at the top and travel down to the bottom. In the original program, they travel from left to right. How would I go about this? I don't know how to get it to start at the top and travel down instead of starting at the left and traveling to the right.
Here's the original code (I know it's a lot, sorry). I am trying to help a friend.
--Made by Joms or /u/jomy582
--Please credit me if using this in a battle.
spawntimer = 0
timerspawn = 0
storedtime = Time.time
bullets = {}
bulletsbig = {}
bulletswarn = {}
dir = 1
bigheight = {33, 98}
function Update()
spawntimer = spawntimer + (Time.dt*Time.mult)
timerspawn = timerspawn + (Time.dt*Time.mult)
--normal bullets
--change the number in the if statement to make them spawn more frequently/less frequently
--EX: spawntimer > 0.2 spawns them pretty fast
--EX2: spawntimer > 1 spawns them pretty slow
--Make sure to change the subtraction method in the if statement
if spawntimer > 0.16 then
spawntimer = spawntimer-0.16
local bullet = CreateProjectile("bullet", -Arena.width/2, math.random(-Arena.height/2, Arena.height/2))
bullet.SetVar("deadly", true)
table.insert(bullets, bullet)
end
--warning. spawns a warning every 5 seconds
--You could change it, but that may cause some bugs
if timerspawn > 2.2 then
timerspawn = timerspawn-2.2
dir = math.random(1,2)
local bulletwarn = CreateProjectile("warning", 0, Arena.height/2 - bigheight[dir])
bulletwarn.SetVar("warningtimer", 0)
bulletwarn.SetVar("soundtimer", 0)
bulletwarn.SetVar("animtimer", 0)
bulletwarn.SetVar("anim", 1)
table.insert(bulletswarn, bulletwarn)
end
--controlling normal bullets
--a simple method that moves the bullets 1 pixel each frame
--you can change it by editing the bullet.move
--EX: bullet.Move(5*Time.mult, 0) moves the bullets 5 pixels each frame
for i=1, #bullets do
local bullet = bullets[i]
if bullet.isactive then
bullet.Move(2*Time.mult, 0)
if bullet.y > -Arena.height/2 then
bullet.Remove()
end
end
end
--controlling warning timer
--a method that controls the warning timer
for i=1, #bulletswarn do
local bullet = bulletswarn[i]
if bullet.isactive then
local warningtimer = bullet.GetVar("warningtimer") + (Time.mult*Time.dt)
local soundtimer = bullet.GetVar("soundtimer") + (Time.mult*Time.dt)
local animtimer = bullet.GetVar("animtimer") + (Time.mult*Time.dt)
local bulletSprite = bullet.sprite
local anim = bullet.GetVar("anim")
--flashing colors
--change the animtimer > TIME where time is how often you want the warning to blink
--warnings last for 3 seconds. Can change that as well
--to get different colors, find the rgb value of the color you want and insert them below
if animtimer > 0.08 then
animtimer = animtimer-0.08
if anim == 1 then
local r = 0 --put Red value here
local g = 0 --put Green value here
local b = 169 --put Blue value here
bulletSprite.color = {r/255, g/255, b/255} -- changes the color to whatever you'd like. Uses RGB.
bullet.SetVar("anim", 2)
elseif anim == 2 then
local r = 0 --put Red value here
local g = 0 --put Green value here
local b = 255 --put Blue value here
bulletSprite.color = {r/255, g/255, b/255} -- changes the color to whatever you'd like. Uses RGB.
bullet.SetVar("anim", 1)
end
end
--plays a timer evert 10 frames for 3 seconds.
--change the soundname to change the sound
--change the soundtimer > 0.16 to change how often it plays
--can change how long it lasts as well by changing the less than statement
if soundtimer > 0.10 then
soundtimer = soundtimer-0.10
Audio.PlaySound("alarm")
end
--this controls when to spawn the bullets
--change the statement to change how long the warning timer is
if warningtimer > 2 then
warningtimer = warningtimer-2
Audio.PlaySound("shoot")
local bullet1 = CreateProjectile("leftbigbullet", Arena.width/2+30, Arena.height/2 - bigheight[dir]) --where to spawn them
bullet1.SetVar("speed", -4) --how fast
bullet1.SetVar("deadly", true)
local bullet2 = CreateProjectile("rightbigbullet", -Arena.width/2-30, Arena.height/2 - bigheight[dir]) --where to spawn them
bullet2.SetVar("speed", 4) --how fast
bullet2.SetVar("deadly", true)
table.insert(bulletsbig, bullet1)
table.insert(bulletsbig, bullet2)
bullet.Remove()
end
bullet.SetVar("warningtimer", warningtimer)
bullet.SetVar("animtimer", animtimer)
bullet.SetVar("soundtimer", soundtimer)
end
end
--controlling big bullets
--this method controls the big bullets
for i=1, #bulletsbig do
local bullet = bulletsbig[i]
if bullet.isactive then
local speed = bullet.GetVar("speed")
bullet.SendToBottom()
bullet.Move(speed*Time.mult, 0)
if bullet.absx > 700 or bullet.absx < -70 then
bullet.Remove()
end
end
end
end
function OnHit(bullet)
if(bullet.getVar("deadly")) then
Player.Hurt(3)
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/

How to do tile based collision

I am trying to make a simple platform game, and obviously I need tile collision. The problem with the code I have so far is that it moves the character first, then checks to see if it is colliding with something, but sometimes it thinks its colliding at the wrong times depending on if I check the x-axis for collisions first or the y-axis first. Am I going about this the wrong way? Here's some code.
function checkCollision(val, axis, oldPos)
if axis == "x" and char.tX then
local tileX = math.ceil(val/absoluteTileSize)
local tileY = math.floor(oldPos/absoluteTileSize)
local tl, tr, bl ,br = getTouchingTiles(tileX, tileY)
local isOnFlatSurface = math.abs(oldPos/absoluteTileSize-tileY) <= .00001--might not be a good i
if isOnFlatSurface then
if tr.canCollide then
char.tX = nil
char.x = tileX * absoluteTileSize - absoluteTileSize
end
else
if br.canCollide then
char.tX = nil
char.x = tileX * absoluteTileSize - absoluteTileSize
end
end
elseif axis == "y" then
local tileX = math.ceil(oldPos/absoluteTileSize)
local tileY = math.floor(val/absoluteTileSize)
local tl, tr, bl ,br = getTouchingTiles(tileX, tileY)
if bl.canCollide or br.canCollide then
char.tY = nil
char.y = tileY * absoluteTileSize --// - absoluteTileSize
--/////////////idk why i don't need to subtract that but it works
elseif not char.tY then--start falling if walk off something
char.tY = love.timer.getTime()
char.yi = char.y
char.vyi = 0
end
end
end
local tileX = math.ceil(val/absoluteTileSize)
local tileY = math.floor(oldPos/absoluteTileSize)
It seems strange that you would use math.ceil for the x values and math.floor for the y. This may be why you are getting some strange occurrences. I would recommend this little debugging trick that may help you:
-- Since you are using LÖVE, this is what you would use:
love.graphics.setColor( 255, 0, 0, 255 )
love.graphics.rectangle( 'line', ( tileX - 1 ) * absoluteTileSize, ( tileY - 1 ) * absoluteTileSize, absoluteTileSize, absoluteTileSize )
-- assuming absoluteTileSize represents the width/height of the tiles?
This would go at the end of your drawing function and would draw a red box in the "tile" your player is currently inside.

Make projectile transition to edge of screen

I've tried googling this however the tutorials only tell you how to make a projectile transition to a certain target.In my case I want it to travel towards the target but then carry on until the edge of the screen.
This is my current shoot function:
local function shoot()
--Invert coordinates
local x = screenWidth - x
local y = screenHeight - y
--a2+b2=c2
local sqDistance = ((x-centerX)*(x-centerX))+((y-centerY)*(y-centerY))
--Sqaure root it.
local dist = mSqrt(sqDistance);
--speed = d/t
distTime = dist/BULLET_SPEED
--if distTime is negative, this makes it positive
if distTime < 0 then
distTime = distTime -distTime -distTime
end
--Create the bullet
local shot = display.newRect(1,1,6,2)
shot.x = centerX; shot.y = centerY;
shot:setFillColor(240,200,0)
shot.rotation = angleBetween+90;
bulletGroup:insert(shot)
--Remove bullet
local function removeShot()
display.remove(shot)
shot = nil
end
--Move the bullet
transition.to(shot, {time = distTime, x = x, y = y, onComplete = removeShot})
end
Found out how to do this, unfortunately it seems you have to use the physics library.
When creating the bullet add this line:
physics.addBody(shot,"dynamic")
And use this instead of transition.to to move the bullet:
--Move the bullet
local xVelocity = (x-centerX)*BULLET_SPEED
local yVelocity = (y- centerY)*BULLET_SPEED
shot:setLinearVelocity(xVelocity,yVelocity)
Make the body a bullet by setting the object.isBullet = true to act as a bullet.
See reference here http://docs.coronalabs.com/api/type/Body/isBullet.html

Tile Collision Detection

So, I have been working on this game for a bit. However, over the past day I have not been able to figure out how to work my collision detection.
The scale at default is equal to 2.
The player is 41*scale by 64*scale.
My player is centered in the middle of the screen in both the x and y axis.
Since the player is centered the world is what moves, those variables are worldx and worldy. The player always stays at the center of the screen.
My tile map is stored in an array and is based on the image pixel color. If the pixel is white at map[x][y] the value is set to 0 else it's set to the block. Meaning the block does not get rendered.
for x = 0, w-1 do --scans the image and builds the map array
amap[x] = {}
for y = 0, h-1 do
local r, g, b, a = source:getPixel(x, y)
if r == 255 and g == 255 and b == 255 then
block = 0
end
if r == 255 and g == 100 and b == 0 then
block = 1
end
if r == 130 and g == 125 and b == 0 then
block = 2
end
if r == 76 and g == 76 and b == 76 then
block = 3
end
if r == 255 and g == 0 and b == 255 then
--this is the spawn pixel yet to build
end
amap[x][y] = block
end
end --end function
function that draws the map
for x = 0, w-1 do --draws the map
for y = 0, h-1 do
if amap[x][y] ~= 0 then
love.graphics.drawq(ImgBlocks, Blocks[amap[x][y]], 32*x*(3/bscale) + worldx, 32*y*(3/bscale) + worldy + jy, 0 , 3/bscale, 3/bscale)
end
if amap[x][y] == 4 then
end
end
end --end function
The function needs to return true or false base on if there is collision between player and block.
Your tiles are 32x32, correct? (from the drawq call) I would recommend you make a function that checks if a point is in a solid tile:
function pointCollisionTest(x, y)
-- find which tile the point is in
local tx, ty = math.floor(x / 32), math.floor(y / 32)
-- check the tile
if map[tx][ty] == solid then
return true
else
return false
end
end
You'll have to change the if map[x][y] == solid logic based on how you determine solid tiles, but this code should otherwise work.
Once you have point collision, the way you make the player collide is by checking each corner of its hitbox (which you should easily be able to determine) against this function whenever the player moves. There are a few ways to do this; I use the relatively simple method of calculating the player's new position, testing it, then canceling the move entirely if the collision test returns true. You have to check/cancel the x and y components of the move separately, though, so the player can move along walls instead of sticking to them.
Are you asking for a basic 2d collision detection?
A simplified formula:
if (playerx > blockminx) and (playery < blockmaxx) and (playery > blockminy) and (playery < blockmaxy) then collission

Resources