So everything in my code is fine and everything was going as expected. But then I realized 2 things, 1, my paddle doesn't go past size 3 despite having self.paddle.size = math.min(4, self.paddle.size + 1), and 2, when my paddle is on size 1 or 2, its fine, but on size 3 and everything gets a funky. Sometimes the powerups cant collide with the paddle, and sometimes some balls cant either, they just go through. Everything should be fine, I dont understand why paddle size 1 is okay but 3 isn't or why it wont go to 4 despite it supposed to be updating based on the score.
--[[
GD50
Breakout Remake
-- PlayState Class --
Author: Colton Ogden
[email protected]
Represents the state of the game in which we are actively playing;
player should control the paddle, with the ball actively bouncing between
the bricks, walls, and the paddle. If the ball goes below the paddle, then
the player should lose one point of health and be taken either to the Game
Over screen if at 0 health or the Serve screen otherwise.
]]
PlayState = Class{__includes = BaseState}
--[[
We initialize what's in our PlayState via a state table that we pass between
states as we go from playing to serving.
]]
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.balls should now be a table to store multiple balls
self.balls = { params.ball }
self.level = params.level
self.recoverPoints = 5000
-- make 2 seperate tables for the ball and key powerups so they dont conflict. Keep track of whether they've spawned
self.ballPowerups = {}
self.keyPowerups = {}
self.ballPowerupSpawn = false
self.keyPowerupSpawn = false
-- the powerups will spawn depending on the time. Set a timer and spawn interval
self.timer = 0
self.interval = 5
-- keep track of whether theres a locked brick so we know when to stop spawning the key powerup
self.lockedBrick = false
-- make a table of bricks so that we can randomize what bricks the powerups spawn from
self.randomBricks = {}
-- set the global variable from brick.lua, hasKey to false to tell it when or when we dont have a key already
hasKey = false
-- give ball random starting velocity
self.balls[1].dx = math.random(-200, 200)
self.balls[1].dy = math.random(-50, -60)
end
function PlayState:update(dt)
-- update the timer
self.timer = self.timer + dt
if self.timer >= self.interval then
-- if theres a locked brick in this map, set lockedBrick to true
for k, brick in pairs(self.bricks) do
if brick.isLocked then
self.lockedBrick = true
end
-- decide if this brick can be used or not randomly
if love.math.random(1, 2) == 1 then
table.insert(self.randomBricks, brick)
end
end
if #self.randomBricks > 0 then
-- choose a random brick
local chosenBallBrick = self.randomBricks[love.math.random(#self.randomBricks)]
local chosenKeyBrick = self.randomBricks[love.math.random(#self.randomBricks)]
-- always spawn ball powerup
local ballPowerup = Powerups(chosenBallBrick.x + chosenBallBrick.width / 2, chosenBallBrick.y + chosenBallBrick.height / 2, 9)
table.insert(self.ballPowerups, ballPowerup)
-- spawn a key powerup if theres a locked brick and we dont have a key. Then set keyPowerupSpawn to true
if self.lockedBrick and hasKey == false then
local keyPowerup = Powerups(chosenKeyBrick.x + chosenKeyBrick.width / 2, chosenKeyBrick.y + chosenKeyBrick.height / 2, 10)
table.insert(self.keyPowerups, keyPowerup)
self.keyPowerupSpawn = true
end
-- set ballPowerupSpawn to true and reset the timer
self.ballPowerupSpawn = true
self.timer = 0
end
end
if self.paused then
if love.keyboard.wasPressed('space') then
self.paused = false
gSounds['pause']:play()
else
return
end
elseif love.keyboard.wasPressed('space') then
self.paused = true
gSounds['pause']:play()
return
end
-- update positions based on velocity
self.paddle:update(dt)
-- update all the balls in the table
for k, ball in pairs(self.balls) do
ball:update(dt)
end
-- update all the ball powerups in the table and check if any of them have gone past the screen, if so, remove them
for k, ballPowerup in pairs(self.ballPowerups) do
ballPowerup:update(dt)
if ballPowerup.y > VIRTUAL_HEIGHT then
table.remove(self.ballPowerups, k)
end
end
-- update all the key powerups in the table, also check if they've gone past the screen
for k, keyPowerup in pairs(self.keyPowerups) do
keyPowerup:update(dt)
if keyPowerup.y > VIRTUAL_HEIGHT then
table.remove(self.keyPowerups, k)
end
end
-- if there has been a ball powerup spawn, check if theres a collision with the paddle with every ball powerup in the table
if self.ballPowerupSpawn == true then
for k, ballPowerup in pairs(self.ballPowerups) do
if ballPowerup:collision(self.paddle) == true then
-- if there is a collision, spawn 2 new balls. Make them spawn near the paddle, and give them random velocities
for i = 0, 1 do
local ball = Ball(self.balls[1].skin)
ball.x = self.paddle.x + self.paddle.width / 2 - ball.width / 2
ball.y = self.paddle.y - ball.height
ball.dx = math.random(-200, 200)
ball.dy = math.random(-50, -60)
table.insert(self.balls, ball)
end
-- since it collided with the paddle, remove it and set its spawn to false
table.remove(self.ballPowerups, k)
self.ballPowerupSpawn = false
end
end
end
-- if theres been a key powerup spawn, go through each key powerup in the table
if self.keyPowerupSpawn == true then
for k, keyPowerup in pairs(self.keyPowerups) do
-- if theres been a collision with the paddle, we now have a key,
-- the brick isn't locked anymore,
-- and we can remove the key powerup from the table and set its spawn to false
if keyPowerup:collision(self.paddle) == true then
hasKey = true
self.lockedBrick = false
table.remove(self.keyPowerups, k)
self.keyPowerupSpawn = false
end
end
end
for k, ball in pairs(self.balls) do
if ball:collides(self.paddle) then
-- raise ball above paddle in case it goes below it, then reverse dy
ball.y = self.paddle.y - 8
ball.dy = -ball.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 < self.paddle.x + (self.paddle.width / 2) and self.paddle.dx < 0 then
ball.dx = -50 + -(8 * (self.paddle.x + self.paddle.width / 2 - ball.x))
-- else if we hit the paddle on its right side while moving right...
elseif ball.x > self.paddle.x + (self.paddle.width / 2) and self.paddle.dx > 0 then
ball.dx = 50 + (8 * math.abs(self.paddle.x + self.paddle.width / 2 - ball.x))
end
gSounds['paddle-hit']:play()
end
end
-- detect collision across all bricks with the ball
for k, brick in pairs(self.bricks) do
for i, ball in pairs(self.balls) do
-- only check collision if we're in play
if brick.inPlay and ball:collides(brick) then
-- add to score
self.score = self.score + (brick.tier * 200 + brick.color * 25)
-- trigger the brick's hit function, which removes it from play
brick:hit()
-- if we have enough points, recover a point of health
if self.score > self.recoverPoints then
-- can't go above 3 health
self.health = math.min(3, self.health + 1)
-- make the paddle bigger, not going above size 4
self.paddle.size = math.min(4, self.paddle.size + 1)
-- multiply recover points by 2
self.recoverPoints = self.recoverPoints + math.min(100000, self.recoverPoints * 2)
-- play recover sound effect
gSounds['recover']:play()
end
-- go to our victory screen if there are no more bricks left
if self:checkVictory() then
gSounds['victory']:play()
gStateMachine:change('victory', {
level = self.level,
paddle = self.paddle,
health = self.health,
score = self.score,
highScores = self.highScores,
balls = { self.balls[1] },
recoverPoints = self.recoverPoints
})
end
--
-- collision code for bricks
--
-- we check to see if the opposite side of our velocity is outside of the brick;
-- if it is, we trigger a collision on that side. else we're within the X + width of
-- the brick and should check to see if the top or bottom edge is outside of the brick,
-- colliding on the top or bottom accordingly
--
-- left edge; only check if we're moving right, and offset the check by a couple of pixels
-- so that flush corner hits register as Y flips, not X flips
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
-- slightly scale the y velocity to speed up the game, capping at +- 150
if math.abs(ball.dy) < 150 then
ball.dy = ball.dy * 1.02
end
-- only allow colliding with one brick, for corners
break
end
end
end
-- if ball goes below bounds, revert to serve state and decrease health
for k, ball in pairs(self.balls) do
if ball.y >= VIRTUAL_HEIGHT then
table.remove(self.balls, k)
end
end
-- only remove health, reduce paddle size, and change the state of the game if all the balls have been removed.
if #self.balls == 0 then
self.health = self.health - 1
-- make the paddle a size smaller
self.paddle.size = math.max(1, self.paddle.size - 1)
gSounds['hurt']:play()
if self.health == 0 then
gStateMachine:change('game-over', {
score = self.score,
highScores = self.highScores
})
else
gStateMachine:change('serve', {
paddle = self.paddle,
bricks = self.bricks,
health = self.health,
score = self.score,
highScores = self.highScores,
level = self.level,
recoverPoints = self.recoverPoints
})
end
end
-- for rendering particle systems
for k, brick in pairs(self.bricks) do
brick:update(dt)
end
if love.keyboard.wasPressed('escape') then
love.event.quit()
end
end
function PlayState:render()
-- render bricks
for k, brick in pairs(self.bricks) do
brick:render()
end
-- render balls
for k, ball in pairs(self.balls) do
ball:render()
end
-- render ball powerups
for k, ballPowerup in pairs(self.ballPowerups) do
ballPowerup:render(dt)
end
-- render key powerups
for k, keyPowerup in pairs(self.keyPowerups) do
keyPowerup:render(dt)
end
-- render all particle systems
for k, brick in pairs(self.bricks) do
brick:renderParticles()
end
self.paddle:render()
renderScore(self.score)
renderHealth(self.health)
-- pause text, if paused
if self.paused then
love.graphics.setFont(gFonts['large'])
love.graphics.printf("PAUSED", 0, VIRTUAL_HEIGHT / 2 - 16, VIRTUAL_WIDTH, 'center')
end
end
function PlayState:checkVictory()
for k, brick in pairs(self.bricks) do
if brick.inPlay then
return false
end
end
return true
end```