Ping: A Pong Clone

By Jake Meinershagen

So, here we are for game one of the 20 Games Challenge. I wanted to keep the time for development on this one low, so I only really worked on it yesterday. I think the total time was about 5-6 hours. There are definitely some edge cases and bugs I could work out with some dedicated testing, but I would like to move on to the next games. So, I'm going to call it good here and move forward. As with any devlog, I'll mention a few of the more interesting things I ran into yesterday.

I should note that I'm using Godot 4 for these projects. I think I forgot to mention that in the first post. This one was done in Godot 4.2.

The Game is here
The Source Code is here


I was able to do all of the graphics with in-engine stuff. I used color rects for the paddles and walls. The only sprite I made was the ball, there wasn't an obvious way to make a circle. I did wind up having a problem where my pixel art was blurry when I first imported it. I remembered this problem from Godot 3, I spent about 15 minutes trying to figure out which import setting it was. Turns out they moved the setting for the filter to the texture section on the node instead of in the import tab. You can also set the default filter for new textures in the project settings. Project Settings > General > Rendering > Textures > Canvas Textures > Set to 'Nearest'. Thanks to this video for being a quick source of that info.


Trying to figure out how to bounce the ball at different angles when it hits the paddle was an interesting question. I wound up googling it and saw an answer in a different language that involved sine and cosine. I decided to click off and try to figure it out myself from that hint. So, I got the good ole unit circle out.

Unit Circle Image courtesy of wikimedia commons.

I created a relative position of the collision and then moved that to a 0 to 1 scale. This means that when we multiply the relative position by 180 we get an angle on the top half of the unit circle. So, sin is 1 at 90 degrees and cos is 0 at 90 degrees. Which means sin needs to be our x component. We multiply that x component by the x component of the normal for the collision to make sure we are moving in the correct x direction. The cos at 0 degrees (when the bounce is at the top of the paddle) is 1 but that is the negative y in Godot. So, we just plop a negative on there and we are good.

I'm not flat-out multiplying by 180 because you can wind up with the ball bouncing straight up and down, which isn't so great for pong. So, we multiply by 170 to get an angle in that range and then add 5 to make that angle centered. Other games I saw clamped the angle to about 45 degrees which is probably a better idea. In that case, we would multiply by 90 and then add 45 to center that.

Here is the code:

# relative position on paddle from 0 to 1
var rel_pos_norm = ((collision.get_position().y - collision.get_collider().position.y) / 
                        collision.get_collider_shape().get_shape().get_rect().size.y) + 0.5
var angle_in_rad = deg_to_rad((rel_pos_norm * 170) + 5)
# here sin is from 0 to 1 and cos is from 1 to -1, top half of unit circle
var direction = Vector2(collision.get_normal().x * sin(angle_in_rad),
$DebugVector.set_point_position(1, 20 * direction)
velocity = direction * SPEED

A lot of this is easier when you can actually watch it working, so I added a Line2D that I could change to show the direction. That is what the $DebugVector is.

Now that I think about it, it would have been cleaner to just multiply by 90 on a -1 to 1 scale because then my y component would have been sine as one would expect. Or, even better, since the range was already -0.5 to 0.5 I could multiply by 180. This would put our resulting vector in the right half of the unit circle. Oh well.

Woofenator made a great devlog that was listed in the challenge listing. Their approach to setting the ball angle was interesting and different from what I did. Simpler too.

Exporting to HTML

I exported the game as an HTML build so I could post it on For anyone else doing that with Godot 4, you will need to turn on the experimental feature for SharedArrayBuffer. I don't love having to use an experimental feature to post the game, but it looks necessary. Godot 4 seems to require it[1] with no way to turn it off[2]. It doesn't seem like the experimental feature on Itch will break too much stuff[3], especially for simple games. So, I guess it's fine. I'll try to keep an eye on whether I see any issues.

And that's it. Hopefully I'll move on to Game 2 soon. I still haven't decided on which one I want to do. Since one of the goals is to reuse assets, I'm leaning toward breakout. Jetpack Joyride sounds fun too though.

See you in the next one,
Jake Meinershagen



Previous TOC Next