I’ve been working on a bigger project in Zig for a while, but before diving back into it, I wanted to build something small and self-contained to get back into the rhythm - so I’m building Pong.
This is a build-along style series - figuring things out as I go. In part 1 I get the project set up using raylib-zig, draw the paddles, the ball, and lay out the playground.
I’ll be posting more parts soon - next up is ball movement and paddle collision.
It’s been fun so far. Happy to hear thoughts if anyone’s tried similar or is
doing anything raylib-y in Zig.
I welcome any feedback you might have - learning as I’m going - only been doing zig for a couple of months.
Thanks for sharing! I looked over your code, and have a few points of feedback.
Structural things:
Your collision algorithm will cause noticeable clipping, as it only checks the center of the ball in the y-axis. You’ll need to either make the ball have a square hitbox by extending the checked area in the y-axis, or use a more involved circle-rectangle overlap algorithm.
A paddle method called isColliding shouldn’t be implicitly responsible for the paddle’s coloring. I would rename it to something like updateCollision, or ideally restructure things so that isColliding simply checks if the paddle is colliding without mutating it.
You set some position values based on the window size, but not all, which kind of defeats the purpose. You mentioned in your blog that you wanted to keep the logic in Paddle simple, which is understandable, but I feel like it would be more consistent to have everything be hardcoded or have everything be based on window size.
Structs that aren’t used as config arguments shouldn’t have default field initializers. If the initial state of a struct is static (doesn’t need an init function), make a public constant called init that represents the initial state of the struct.
Thank you so much for the feedback! I really appreciate it - and value the learnings.
I have already recorded a couple more videos which I will publish twice a week (because that’s what YouTube wants). I will update with these suggestions (all of which sounds like the right thing to do) in the next video I do.
On a side note, when I publish more videos - can I ask for more feedback? should it be as additional replies to this post, or create new threads?
Would it be better if I did them as pull requests, but I am not sure how that could work with the logistics of the videos - maybe I should implement the changes as well in the video before publishing… Maybe that is also a valuable aspect to capture?
That’s fantastic and I really appreciate your support.
I’ve rejigged parts 2 and 3 as pull requests:
At your leisure, of course. You can ignore any .md files. Also, please bear in mind that I did these before your original bits of feedback, and there is no need to repeat those - I’ll apply those suggestions across the board when I do part 4, possibly along with feedback from these PR’s depending on timings.
So, part 2 looks like it just covers the code I saw before, so I don’t have any additional comments on it.
As for part 3, the main thing I’d do is put the paddle movement logic in moveUp and moveDown methods, and call them based on user input. This would be more consistent, as your other movement logic is mostly in the ball+paddles themselves, with the main loop just doing orchestration.
This is funny I actually have made a server side pong game in Zig for a school project, and I was using a Raylib client that I’ve made for local testing for a while
Looking forward to looking properly at these. I’m not really that interested in games, and it certainly isn’t what I planned on programming in zig, but your first post impressed me with how easy it can be to write one. I’ve been playing about with raylib since and I’m looking forward to more inspiration from parts 2 and 3.
I looked over this PR, and also tried building/running the code on my machine for the first time. If you haven’t tested it on Windows yet, I can confirm that it runs great!
My feedback:
std.heap.GeneralPurposeAllocator is deprecated, use std.heap.DebugAllocator (it’s basically the same thing under a different name).
You are initializing the allocator correctly, nothing to change there.
The proper way to deinit your allocator is to just do defer _ = gpa.deinit(). deinit will already print memory leak info if any leaks occur, so there is no need to @panic if leaks occur.
The showScore logic could be part of Paddle since the paddles own the scores, but that feels kinds unintuitive. I think that if you want a super ‘correct’ structure/hierarchy, you should either move the scores into the Game struct and keep showScore in Game, or make a new Player struct that contains a score, a paddle, and showScore. Either way, moving the score out of Paddle seems like the right call.
Your score rendering is fine, but just in case you want to make the font larger, I wrote an example of how you could do that in your code. I wanted to point this out because setting font sizes in dvui is a little unintuitive, and I wasn’t sure if you were happy with the font size, or if you just settled for the largest default option:
A ball may still be colliding with a paddle after bouncing off it, triggering an additional bounce. This can lead to a glitchy state where the ball is permanently colliding, constantly switching direction while embedded in the paddle. You can resolve this by making your crossing_x logic check if the ball is moving towards the paddle: