I am very new to coding and pico 8 has made it that much easier for me to start. It's been a lot of fun!
In the last couple of months, I've been playing around with code and following various tutorials on youtube. Recently I started working on a rudimentary pinball game. It now has a bouncing pinball on scrolling table using the full vertical length of the map. While collision works for the most part, the ball does often 'catch' on sides of flagged tiles.
I would really appreciate some advice on what I am doing wrong in the collision functions.
... and a supplementary query I have is can I use existing code to change the angle at which the pinball deflects? I suspect it will require trigonometry but thought I'd check before moving on...
The easiest way would be to check the collision when you hit.
When you do, is there an obstacle to the left and right as well ? If it is, then you are doing a vertical rebound. Invert only Y.
If there is instead an obstacle above and below, it is a horizontal rebound. Invert only X.
If it is none of these, invert both X + Y.
@dw817's answer is correct for the special cases of a horizontal wall and a vertical wall, but not for a wall at an arbitrary angle. The key observation here is that when the ball hits a wall, the wall will exert force only along the direction normal (perpendicular) to the wall - not parallel to the wall! So you want to flip only the part of the velocity that is normal to the wall.
For a vertical wall, the normal is parallel to the x axis, so flipping the x velocity is correct. For a horizontal wall, the normal parallel to the y axis, so flipping the y velocity is correct. (For fully-elastic / energy-preserving collisions.)
For an arbitrarily-angled wall, you might try something like this:
-- project (vx,vy) onto (wx,wy) to find parallel component local r = (vx*wx + vy*wy)/(wx*wx + wy*wy) local vx_par = r*wx local vy_par = r*wy -- flip normal component but keep parallel component unchanged -- v == v_par + v_norm, so 2*v_par - v == v_par - v_norm, as desired. local vx_bounce = 2*vx_par - vx local vy_bounce = 2*vy_par - vy
wy are the differences between the endpoint coordinates of your wall,
vy are the object velocity vector.
I haven't actually tested this code, so, uh, no guarantees that I haven't made any typos or otherwise messed things up.
As for "catching" - I haven't looked at your code, so I don't know what's wrong, but are you perhaps continuing to check collision after the ball is already stuck in the wall? Here are two simple ways to deal with this:
- On collision, if the ball is inside the wall, move the ball along the normal vector until it is outside the wall.
- If the ball is moving further into the wall, apply collision. If it is moving out of the wall, do not apply collision.
If you have sufficiently complicated shapes or fast-moving objects you may need to use fancier methods, be careful about timestep size, etc.
@dw817 I'm not sure what you mean by checking for collision when you collide (hit). And when you say 'is there an obstacle to left and right', do you mean in adjacent map cels?
@luchak the variable angle work through is a good point for when I start adding variously shaped obstacles to the table. I'll try to see how I incorporate it into my existing collision functions first but I suspect I will need to rewrite. As to the issue of catching - I think I understand what you mean so will try to rearrange how my collision functions update each frame.
All still feels a bit tricky. Thanks again to you both!
Here's a version of your cart that I think does what you want.
It uses a rudimentary version of what luchak described "On collision, if the ball is inside the wall, move the ball along the normal vector until it is outside the wall."
oldy variables in the
update_game function keep track of where the ball is before it has been moved. Then we reset the ball's x or y coordinate if there is a collision in the x or y direction. This should ensure that the ball always ends up outside of a wall.
There was also a bug with your
ballhit_tile functions. They would always return after the first loop iteration, so they didn't look at all the positions you wanted. So instead of
for i=0,7 do -- One way or another we are returning so will break out of the loop early if condition then return true else return false end end
They now look like this
for i=0,7 do if condition then return true end end -- Only return false if we checked 0 through 7 and didn't find anything return false
For your second question about changing the angle, you're right that it requires trigonometry. Here is an example where the ball flings off at a random angle every time it hits a wall. Check out the
For your purposes you will probably want to take into account the current angle the ball is traveling at to calculate the angle it should bounce off at. You can use
atan2(ball_dy, ball_dx) to get the ball's current angle of velocity. Note that PICO-8 uses a convention where angles are between 0 and 1 rather than the typical 0 to 2*pi.
Thanks @caterpillargames for taking the time to looking through my code and make suggestions on both of my questions. These are simple steps I can definitely build on! And I appreciate you adding changes to the code, including the trigonometry.
@dw817: I will have to study your code. It's very different from anything that I have done so far (I'm still at very basic mget/fget level in my code).
[Please log in to post a comment]