Help with determining velocity after moveWithCollisions: 'bounce'

Hi, I'm getting my feet wet in the Playdate SDK and having trouble making a simple Pong clone.

I'm trying to write a general collision detection system for the ball / puck and struggling with the moveWithCollisions function. Here's a demo of what happens with the current solution.

Kapture 2022-07-25 at 20.59.18

Here's my code for now, the main focus being the update callback:

function Puck:init(x, y, speed)
    local image = gfx.image.new(10, 10)
    gfx.pushContext(image)
        gfx.fillCircleInRect(0, 0, image:getSize())
    gfx.popContext()
    self:setImage(image)
    self:setCollideRect(0, 0, self:getSize())
    self:setGroups(puck_coll_group)
    self:setCollidesWithGroups({bound_coll_group, paddle_coll_group})
    self:add()
    self:moveTo(x,y)

    self.speed = speed

    -- determines where to "serve" the puck
    math.randomseed(pd.getSecondsSinceEpoch())
    local angle = math.random(0, 360)
    local v_x = math.sin(math.rad(angle))
    local v_y = math.cos(math.rad(angle))
    self.velocity = geo.vector2D.new(v_x, v_y):normalized() * self.speed
end

function Puck:collisionResponse()
    return "bounce"
end

function Puck:update()
    local next_x = self.x + self.velocity.x
    local next_y = self.y + self.velocity.y
    local actual_x, actual_y, collisions, length = self:moveWithCollisions(next_x, next_y)
    if length > 0 then
        for index, collision in ipairs(collisions) do
            local bounce_vector = geo.vector2D.new(actual_x - collision['bounce'].x, actual_y - collision['bounce'].y):normalized()
            self.velocity = bounce_vector * self.speed
        end
    end
end

What I've written is the fifth or sixth solution I've tried. It seems the most simple and intuitive to me personally. My trig is a little rusty to say the least though.

My thought here would be something like:

  1. Puck is going to bounce off the wall
  2. 'bounce' point from the collision table is going to tell me where it'll bounce.
  3. The vector between the collision, and the point where it will bounce, will be the new velocity vector of the puck (once normalized).

My confusion here lies in: does the 'bounce' collision type "know" about this kind of directional bouncing? Is it doing the math like this StackOverflow solution does? I tried implementing this particular solution, but I'll admit I'm a bit confused, and had no such luck.

It doesn't help that I'm without a lot of the debugging solutions that I would ordinarily have. If anybody has tips for that too, I'd be glad to hear it.

FWIW, I'm developing on Mac.

Okay, here's another option, with a less janky behavior than the first solution, but still so far away from perfection. Followed this StackOverflow solution and made sure I understood what kind of data each step ended up with (the dot product of two vectors is a scalar, who knew?)

The main part I changed is the update function:

function Puck:update()
    local next_x = self.x + (self.velocity.x * self.speed)
    local next_y = self.y + (self.velocity.y * self.speed)
    local actual_x, actual_y, collisions, length = self:moveWithCollisions(next_x, next_y)
    if length > 0 then
        for index, collision in ipairs(collisions) do
            local collision_normal = collision['normal']:normalized()
            self.velocity = ((collision_normal - self.velocity):scaledBy((collision_normal * self.velocity) * 2)):scaledBy(-1):normalized()
        end
    end
end

It's also worth noting that I also updated it so that I know that self.velocity is always a normalized vector, and I multiply each component of the vector by speed when trying to figure out the next position to go to.

Kapture 2022-07-25 at 22.07.07

If it’s a frictionless bounce you can just do something like:

if collisions[index].normal.x ~= 0 then
	self.velocity.x *= -1
end
if collisions[index].normal.y ~= 0 then
	self.velocity.y *= -1
end

Thanks! Yeah, that's definitely an option but it only works if the walls and paddles are parallel to the x or y axis.

I think I might opt for something like that in the short term (just to work on something else), but I want to extend some of the mechanics to allow for rotated colliders.

I also realize there may be a way to calculate the reflection angle based off of the 'move' and 'normal' vectors. And then it'd be "as simple as" just creating a new vector, with that same angle of incidence, originating from the point of collision.

I'll try that out tonight and see what gives. I might see if I can make a little prototype of these different collision and vector reflection methods too for people who are more visual learners like me :slight_smile:

Kinda thinking aloud here but for angled objects, AABB collisions probably aren’t the right tool anyway?

My simple brain says to use line collisions for angled objects and just rotate the ball velocity vector around the normal of the line.

Maybe AABB first to get a list of possible collisions then check for line intersections?

Oh jeez. Yeah, you're totally right. I forgot that the AABB colliders can't be rotated. Hmmmm this changes things.

I wanted to allow the player to rotate the paddle using the crank. I'll see if I can get a solution like your suggestion up and working in a separate pdx package. But if not, then maybe I just scrap the rotation and keep working where all colliders are aligned with xy axes.

Thanks for the help!! This also explains why the 'normal' value in the collisionInfo table is stated in the docs to be 1, 0, or -1! I was so confused how that could represent any normal value when a sprite is rotated outside of 90 degrees, but now that totally makes sense.

I still think I want to understand more about why the general solution isn't working here, even with colliders that are axis-aligned! That would help solve my original question.

1 Like

I wonder how b360 works under the hood…