2

I have my powerup class and I've done all the code in playstate.lua to inact the powerup once it hits the paddle by generating a new ball. The problem is in my actual game, balls spawn even before the powerup is there, and all of a sudden I have like 100 balls everywhere.

I've looked over everything, and I don't see anywhere where there would be an error, especially one like 100 balls spawning when the powerup hasn't even spawnned yet.

I made my powerups class

--[[
    -- Powerups Class --

    Is used to call powerups that appear at random.
    They fall from whatever place they were in.
    If they touch the paddle then 2 more balls spawn.
    If they go past the screen they are simply removed.
]]--

Powerups = Class{}

--[[
    Initalize the variables
]]--
function Powerups:init(x, y)
    -- input the x and y that will be used
    self.x = x
    self.y = y

    -- set dx and dy to 0 at first, it shouldnt move
    self.dx = 0
    self.dy = 0

    -- all powerups are 16x16
    self.width = 16
    self.height = 16
end

function Powerups:update(dt)
    self.dy = 60

    self.y = self.y + self.dy * dt
end

function Powerups:render()
    love.graphics.draw(gTextures['main'], gFrames['powerups'][9], self.x, self.y)
end

-- Collision detection using AABB
function Powerups:collision(paddle)
    if self.x > paddle.x + paddle.width and self.x + self.width < paddle.x then
        return false
    end

    if self.y > paddle.y + paddle.height and self.y + self.height < paddle.y then
        return false
    end

    return true
end

Then I implement everything in the PlayState class

--[[
    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 = { params.ball }
    self.level = params.level

    self.recoverPoints = 5000

    self.powerups = {}
    self.powerupSpawn = false
    self.timer = 0
    self.interval = 10

    -- 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)
    self.timer = self.timer + 1

    if self.timer >= self.interval then
        self.powerupSpawn = true
    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)

    for k, ball in pairs(self.balls) do
        ball:update(dt)
    end

    for k, powerup in pairs(self.powerups) do
        if self.powerupSpawn == true then
            powerup:update(dt)
        end

        if self.powerupSpawn == true then
            if powerup:collision(self.paddle) == true then
                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)
                    table.remove(self.powerups, k)
                end
                self.powerupSpawn = false
            end
        end

        if powerup.y > VIRTUAL_HEIGHT then
            table.remove(self.powerups, k)
        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)

                    -- multiply recover points by 2
                    self.recoverPoints = self.recoverPoints + math.min(100000, self.recoverPoints * 2)
                    
                    -- make the paddle bigger, not going above size 4
                    self.paddle.size = math.min(4, self.paddle.size + 1)

                    -- play recover sound effect
                    gSounds['recover']:play()
                end

                if love.math.random(1, 3) == 1 then
                    local powerup = Powerups(brick.x + brick.width / 2, brick.y + brick.height / 2)
                    table.insert(self.powerups, powerup)
                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)

            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
    end

    -- for rendering particle systems
    for k, brick in pairs(self.bricks) do
        brick:update(dt)
    end

    for k, ball in pairs(self.balls) do
        ball: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

    for k, ball in pairs(self.balls) do
        ball:render()
    end

    for k, powerup in pairs(self.powerups) do
        powerup:render()
    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
2
  • 3
    The logic of Powerups:collision is incorrect: and should be replaced with or Commented Aug 10, 2023 at 13:40
  • 1
    @ESkri , thank you so much. After some more edits, that was enough to solve all my problems. I cant believe it was so small. Thank you again. Commented Aug 10, 2023 at 13:52

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.