0
\$\begingroup\$

I watched https://www.youtube.com/watch?v=F86edI_EF3s.

I believe the following code is the complete code

https://github.com/games50/breakout/tree/master/breakout13

And

https://github.com/games50/breakout/blob/master/breakout13/src/states/PlayState.lua is the code in play.

relevant code:

-- detect collision across all bricks with the ball
for k, brick in pairs(self.bricks) do

    -- only check collision if we're in play
    if brick.inPlay and self.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 = 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,
                ball = self.ball,
                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 self.ball.x + 2 < brick.x and self.ball.dx > 0 then
            
            -- flip x velocity and reset position outside of brick
            self.ball.dx = -self.ball.dx
            self.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 self.ball.x + 6 > brick.x + brick.width and self.ball.dx < 0 then
            
            -- flip x velocity and reset position outside of brick
            self.ball.dx = -self.ball.dx
            self.ball.x = brick.x + 32
        
        -- top edge if no X collisions, always check
        elseif self.ball.y < brick.y then
            
            -- flip y velocity and reset position outside of brick
            self.ball.dy = -self.ball.dy
            self.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
            self.ball.dy = -self.ball.dy
            self.ball.y = brick.y + 16
        end

        -- slightly scale the y velocity to speed up the game, capping at +- 150
        if math.abs(self.ball.dy) < 150 then
            self.ball.dy = self.ball.dy * 1.02
        end

        -- only allow colliding with one brick, for corners
        break
    end
end

Question

Why does the above code check if self.ball.x + 2 < brick.x and self.ball.dx > 0 then?

I am especially confused because the code already did the collision check with if brick.inPlay and self.ball:collides(brick) then, but now the code adds a +2.

The same goes for the other similar sections self.ball.x + 6 > brick.x + brick.width and self.ball.dx < 0 I don't know why +6 is also checked.

I also do not understand why ball velocity (dx) is also used for checking.

According to the comments in the source code, it seems to be a process at the edge, but I don't really understand it. Is this a common process (or patterns) in game programs (especially in 2D collision)?


I thought it was to solve the following problem(tunneling), but I felt it was something different and could not understand the code myself.

In games physics engines, what is "tunneling", also known as the "bullet through paper problem"?


In writing this question, I think I am getting a little more organized.

the ball needs to bounce at a leftward velocity if it is on the left edge, and the ball needs to bounce at a rightward velocity if it is on the right edge, so I think (not sure exactly) this is what is being handled here.

I assume the +2 and +6 are arbitrary by the author. I just don't know why those values should be added instead of the current position.

By using the algorithm above code, is it possible to determine which side (top, bottom, left, or right) the collision occurred with?

I am having a hard time imagining it clearly.

\$\endgroup\$
2
  • \$\begingroup\$ Much like SO, the norm here is to post the relevant code directly in your question as formatted text rather than off site links. Links can go dead over time & even when they don't they still add friction to the process which often reduces the likelihood of getting answers and impedes others using this post to try to answer their own questions. \$\endgroup\$
    – Pikalek
    Commented Oct 9, 2023 at 2:05
  • \$\begingroup\$ @Pikalek I added code just now. \$\endgroup\$ Commented Oct 9, 2023 at 2:14

1 Answer 1

2
\$\begingroup\$

The double check aspect of the code is discussed at this point in the video. The first check lets you know that some sort of collision occurred. However, in order to bounce the ball correctly, you need to know more about the collision. Hence the additional checks.

The dx test is for checking whether the collision occurred on the left or the right side of the brick and discussed here in the video:

"...if the dx is negative, there's no way our ball is colliding with the left side of our brick..."

You are correct that the offset added to the checks isn't related to tunneling. As discussed here, it's a fix for dealing with corner hits:

"it plays a little bit rough with corners because you could theoretically get into a position where you come in at an angle and it's intersecting with the paddle in two positions, both on top and on the left or on the bottom and on the left, so in that case adding two sort of prioritizes the Y being hit"

Note: I believe the presenter says "paddle" by mistake - a few seconds later they refer to it as a brick collision & the code being referenced at that point in time is the brick collision code you are asking about.

The offset is is also mentioned in the code comments here:

-- 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

and also here:

-- 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

The correction for the other direction needs to account for the size of the ball. A mirrored correction would be -2, but the ball is 8 pixels, so the result is +6. This is an excellent example of how magic numbers (see also this SO post), while sometimes convenient in the moment, can make code harder to understand. It's a bit out of order, but a clearer, more descriptive way to write the code is mentioned at this point & then the +6 ball offset is discussed.

Using ball.width - 2 would have made the mirrored logic a bit more obvious. For that matter, I would argue that using self documenting code, something like ball.width - CORNER_CASE_OFFSET would have been better yet.

In summary, it's a small offset meant to deal with a simultaneous collision edge case and it appears to have been chosen either experimentally or arbitrarily.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .