Pong Clone in C++ // Code Review

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey what's up guys my name is luciano welcome back to another code review for those of you who are new to this series it's basically a series where i take a look at the code that you guys send me and i well i review it this is still one of those like raw series where i just haven't really decided what direction to take this in just because a code review is such a general thing in a way it's kind of difficult to put it into the format of a video because a code review in real life as a software engineer could take hours and is very boring so it's called the code review series but really it's the cherno just talks about code series anyway today i thought that what we would do is kind of simplify this thing so far we've looked at basically two little game engines you could say we've had the the 2d game engine made by like a 13 year old and the kind of step up to the 17 year old and today we've got a 19 year old so we're building i can only go older and older with each episode but today we have a 19 year old computer science student who has just made a little pong clone so you know the game the arcade game pong he's made this for windows with the objective of being making everything from scratch so this is this is interesting because i think that this is a i think this is actually like a manageable size c plus project that you guys could try and implement if you're following along with my c plus plus series then that's great you're learning all about c plus and you probably want some kind of project to tackle but if you go to my game engine series a game engine is quite a large project it's probably something i'm going to be working on like for the rest of my life so clearly it might be a little bit of a step up this on the other hand is something that is realistic to actually kind of finish so i'm excited to dive into this is something that potentially you guys could take a look at yourselves and hopefully this and what we look at today will help you out if you're in that boat but first i want to mention that this video is sponsored by skillshare for those of you who still have somehow not heard of skillshare even though they've been an amazing sponsor for this channel this entire year skillshare is an amazing online learning community where millions of creative and curious people from all around the world come together to learn new skills it's the perfect platform to pick up a new skill especially if you find that you're short of time and you want just bite-sized concise lessons that you can follow along and learn something rapidly i love the fact that skillshare has an incredible selection of really well put together classes on pretty much any topic you could think of anything from illustration to video production to photography to editing photos to business and marketing and productivity and art lots and lots of art they've got you covered no matter what you are interested in and coming in at just under ten dollars a month for an annual subscription it's really easy to dive in and get started but it's even easier because for a limited time skillshare is giving you guys free skillshare premium all you have to do is click the link in the description below of this video and sign up so definitely put that down time to good use and try out skillshare for free using the link in description below thank you again to skillshare for sponsoring this video and all the other videos this year it's been a very skillshare year alright so without further ado let's dive in and take a look at this little small pong game so here is the github repository if we look through this it's just a bare bones pong clone made for windows from scratch you can see we have like two paddles very a very blurry looking ball and it uses uh cmake right so all we have to do is clone this obviously i'm going to find a directory and just type in git clone there we go i didn't actually check to see if it had any uh sub modules or anything like that but i guess we'll find out soon enough over here inside the directory i'll make a build directory and then just run cmake to build the project files okay and once we are done with that let's go ahead and open the solution file and we can see what is up first thing i like to do is just give this a quick build so ctrl shift b to build the entire solution and wait for this to compile make sure it does compile step one okay and then i will set the startup project to be le pong and we can take a look at presumably the main file let's just run this code see uh if everything works properly first okay so there we go we've got pong um i'm not sure before you read the instructions we'll space blast them to start and then we have up and down arrow keys for the right paddle and then uh w and s keys for the left paddle okay cool so there we go that looks like pong to me i guess it works pretty well um let's try and deliberately lose here and it resets okay pretty cool so i notice the window's not resizable which is uh unfortunate but that's pretty much as simple as you can get also i'm not not a huge fan of this blurry light bulb thing i don't know how this is rendered maybe it's a texture maybe it's some fancy shader or something i'm not sure let's take a look at the code okay so we have a main function we have a main file that's really clean i really like this i love looking at a main file and it's not just cluttered with a bunch of code this person has not written their code all in one file which is fantastic that in itself is a really good setup for a larger project uh pong could easily be written in one file i probably wouldn't have too much of an issue with it but it's obviously nice to see something like that let's naturally follow this to maybe this header file and then we'll take a look at this macro and some other things as well okay so in general this code looks great using some kind of documentation here that's even like a return kind of keyword i imagine if you hover your mouse over this you can see that visual studio will pick up i guess this syntax and you can see that like there's a special return thing and it returns this text so in in in other words the documentation has been done really well there's all this no except stuff which i mean i'm not a huge fan of just exceptions and the fact that because of exceptions and there's you know like no discard and there's all these attributes surrounding uh c plus plus and in general even you know trying to write a fully correct c plus data structure these days in 2020 it's just littered with so many keywords and so many extra compiler like um directives i guess and other things and it's just i don't know i feel like staples plus is getting a little bit a little bit messy let's be honest but anyway nonetheless they're here that's fine looks really clean i like the the names the use of namespaces here and the documentation as i mentioned um everything looks really good and this looks really clean which is great it's a really good start so far let's take a look at this macro and see what it does okay so this macro goes over here um and we have this kind of if condition then oh well this this does assert or return a certain return with a condition and then the value so this will return the value so it runs the condition and if the condition fails it returns a value so in other words this will cause the main function to return negative one if init fails i'm not going to lie that took me like a minute to work out otherwise there's also this a certain log function which i guess if the condition is false the condition the condition itself is the thing that you run that's why there's a semicolon here otherwise uh it will log the message that you want so interesting little assertion macros um i mean they don't really like their recessions but they don't do they don't break the debugger on a particular line which is personally something that i like and i think in general that's what a session macros do um so this this is definitely interesting i don't think i've ever seen anything like this before um it's kind of a certain return situation i mean i probably would have just separated this out into like some kind of a cert macro maybe that would run this um but see the the other problem with this is that to me an assertion macro implies that it's a debug only thing right if you start wrapping your recession macros uh if you start wrapping important code in the session macros to me that that says that potentially in release mode that gets nuked right because an asset is really like a debug thing um you can have obviously release mode assets and like verify or like there's a bunch of other like um i guess uh i was gonna say stereotypes conventions you know that um software uh developers use but um yeah so in general not a huge fan of this you know um init returns a bull in right so i would have just done something like we'll uh you know init or something like that equals that and then if not init or you know if error or you know you can call this whatever you like then maybe assert and return negative one that to me just makes a lot more sense than wrapping all of that kind of in a macro um but you know to each their own okay so we what does initializing this game do right so we have a static here um we uh that we initialize here we have game lifetimes okay wow this code is really nicely written um and i mean like my style isn't to to you know have like a little uh comment here this person did mention they're a computer science student i can see that um i feel like this is like the thing that they teach you um in computer science i mean you know it's kind of like one of those things where like clean up game systems cleans up the game's systems really you know this is one of those things that like you you the code is self-documenting to a point right inner game systems initialized game systems cleans up games this is just noise at this point you know none of these comments need to exist this makes perfect sense um and i think that uh you know outside of like a university assignment which i don't know if this is or not but outside of that it's just not necessary to have anything like this because it's it's the code is self-documenting this is interesting though we so we have um a static constant expression lifetime right which is a class holding the unit and cleanup functions of any item which have these kind of function pointers right so this is a void function that returns a bully and this is a void function that returns did i say a void function returns a ball in i meant a function with no parameters that returns a boolean and this is a the same thing but a void function um you can pass these two in uh and then i'm assuming it gets added into some list which is really interesting so i've seen this kind of pattern i don't know what it's called i never know what these things are called but basically what this ends up looking like is a little array of pairs of you know like constructor struct constructor struct constructor struct right so i think you might know where i'm going with this this is the great thing about rai so resource acquisition is initialization i really like that saying but the point is this is what objects and seatbelts do they have a constructor they have a destructor i would have probably implemented this with classes instead of having loose functions like this this is a very c style way of writing c plus anywhere because you can see that i mean i haven't seen a single class yet which is fine no sorry that's a lie lifetime is a class but you get what i'm trying to say right um and you know having function pointers here totally fine code um i i wouldn't look at this and think that it's not great code or anything um it actually looks kind of advanced to me to be honest uh i wouldn't i wouldn't expect code like this from a first year i don't know if this person is in their first year but they are 19. um so this is like a good job i guess so far but yeah some of these things again just just some just personal preference i would probably use like a class because that implicitly has that construction destruction to it if you really want to tie it to that um but then again you know if you look at what lifetime does lifetime has its own kind of a knit and clean up function as well that wraps these and they are constant expressions so yeah it's a really interesting uh way of writing this as well anyway i'm getting a little bit ahead of this so let's uh go back to in it and see what hap what actually happens so again a certain return value so it returns false if that's false interesting but the problem is this is a nightmare to debug right if i want to see what happens putting a break point here trying to debug macros not really that very not really nice right if you just had something like this right um if blah blah return false really easy right you put a little break point you can hit like f10 oh it's on this line i guess i'm returning false super easy super nice so i much i would take this any day over this right um so initialize we try and emit items here and this is what i mean right so there's a constant array reference of lifetimes and those lifetimes contain those function pointers that call in it so we can literally go through all of them and call init and basically what ends up happening is for these lifetimes for example we call the unit functions for all of these um and you know there's probably like a matching pair of that for these shutdown functions which are cleanup functions so that's kind of interesting haven't seen that in a while um same with this right we have these init functions these are our state lifetimes that's what we're kind of iterating iterating over and calling each of these functions because these are in fact function pointers which of course you know are these functions right and so that we can we can kind of call them in a way dynamically it's almost like adding like a registry of of startup and shutdown functions so that's pretty cool actually um i'm enjoying this code so far so try init items obviously that's what that's what that does um if succeeded is false then they clean up items starting from i guess the ones that we got up to so we basically have this list of things to initialize and then as soon as we run into an error we roll that back um so that's kind of interesting kind of unnecessary if it if succeeded is like this is a cool pattern for the sake of having it but for the sake of the game it's like well succeeded we failed to initialize the window just shut down the app like immediately who cares about cleaning anything up we don't need to clean anything up operating system will reclaim the memory like that be way better than trying to roll back everything this is good code though this is good code though i'm just talking for more of a practical sense all right um so this is this is cool this is nice this is one of those things that you would you would kind of show to a university professor and be like hey i know what's up i know what i'm doing so this is good all right um uh blah blah blah okay i'm not gonna like think about that too much so far from a systems programming point of view this is pretty nice code okay what does run do this is the main game loop so again we have that so a return pattern thing running is set to true um it's a static okay call on begin run so this is what actually shows the window says resizable to false log contact specifications so this just logs the opengl version and everything um reset game state okay so this what is this is this a ball dot reset okay so you've added reset functions to every object okay i wouldn't do this what i would do instead is simply recreate those objects right so you have a ball a ball in memory right what is a ball in memory ball and memory is a game object that hasn't moved direction and move speed a position probably some other stuff right that memory is fine that's your ball as soon as you wrap up the ball or you want to reset you clear that ball from memory and you re-initialize it with a fresh ball that is by far the best way of doing this now if we're talking about massive heap allocations and more complex things of course that's not always ideally the way to do this but the thing is every time you add a new parameter to your ball or to your game object you have to add it to that reset function if you don't then that's a problem right maybe game object i haven't looked yet but maybe your game object or whatever you called it maybe that has its own reset function that's virtual and then you call that base function from your override of that function that cleans up all specific things it's it's a little bit of a mess and it's it's just really annoying to have to go to that reset function and keep adding keep adding variables that need to be reset and the other question is what do they get reset to maybe the speed should be one by default not zero right because zero means it doesn't move at all or something like that right that's super annoying as well so if you just have a ball that gets constructed with a speed of one done right just stomp the ball with a fresh ball and obviously the way that you would do that um if we go here reset game state right is this ball you know just ball equals ball right and then i don't know if that needs parameters i don't even know how it gets created in the first place um let's look at this uh flow rate yeah actually that's a good point what creates a ball let's quickly try and track down that constructor um here it is it's a static thing right so this you know just recreate the ball like that easy right recreate them with these parameters and you're done it's also really nice to have it in a particular place now in this in this case these are clearly static constant expressions if they're static constant expressions why are they in the pong class these are clearly the bull radius is clearly a ball thing so is ball program which i assume is a shader program id right this stuff is both specific so in a way you could just move it to the bull class have them be initialized with these variables and have a default constructor that you can then simply call in keep losing everything um you can simply call by uh learn to navigate code channel um by just doing that right so that's what i would do instead of having a reset function for every game object just because that's really annoying to maintain okay um yeah and in general don't be afraid to reconstruct objects right a little stack allocation like this walk in the park right not not even something to worry about um same with the paddles okay so that's that um playing fault okay let's go back to uh where were we before this we were in position paddles on terrain so again you know this system this this is like a bit of a reset thing where does it go or it goes a keyboard or offset or whatever that's cons that's 50. it's always 50. right just initialize a paddle at that position maybe um and then that way you can like recreate it or you can just in general have a function called create paddles then we'll just always set it to that instead of just having to override these kind of static objects so that's something that i would have um all right let's go back to here and then we get into um okay we see the random number generator using s rand wouldn't recommend doing that in because that's like a very c style way of generating generating random numbers um the random number generating c plus plus is a lot better um i think if you if you i'll link a video up there that's me making a game in an hour that's where i used the cpl plus random generator if you're interested because it will get you higher quality random numbers believe me that's a thing okay um and that's for future you know pong is going to be fine with srand it's going to be fine um okay that's that's our on begin run function uh then we uh you know this is true so we go to on update on render i love this separation right it's good that you don't just have you know or actually i was going to say i love that separation on update and render needs to be two distinct things whether or not they have to be two distinct functions is up to you it's fine to have a function called on update that does updating and rendering as long as you're not kind of doing it within one function you're not doing kind of update balls position render ball update enemies position render enemy you don't want to do that right because that that's gonna mess you up um you need to update all of your state process that and then do the rendering usually at the end um so it's good to see this kind of separation again it doesn't have to be two functions in hazel we usually have just one on update function that will update everything and then you can submit stuff to the render queue for rendering and doing other stuff like that uh okay so on update we'll get the time delta okay don't like this i don't like doing this um so we have these constants so time get happens this is a very kind of global approach to this um it's it's a lot i think it's a lot easier to understand if inside here you calculate delta and you just pass in the time step right because that way um there's no ambiguity to like how it gets calculated and having to like you know have this static last time in here and you know not really knowing how everything flows um it's just usually just a lot easier to calculate and then pass it in and that way the burden of calling this function does never falls on on update i understand that you've probably got on update yes so you've got one on update function for your application and then you call all of these with delta maybe call this one with that that delta as well and that way everything is really nicely organized because this is an update function and an update function always has a delta just a bit of a code thing so how do we update the ball position plus equals move direction times move speed times delta interesting um now this is fine i just don't like the idea of delta being in these parentheses because delta is you should be moving by this is actually what it should be right this makes a lot more sense i'm moving by this amount but i need to only move this much because that's how much time has passed right so it's kind of like you have your move speed calculated in like meters per second we'll say and then the times delta is what actually makes it be per second right so i don't really like the other way that you wrote it this is this is technically makes more sense right um update with delta and window size super weird to take in window sizes into like an update function right you should definitely have a global way see in this case you should use a global global thing right you should have a global way to retrieve the window size you have one window that's never going to change you're not going to have two pong windows and even if you do you can think of something then right you're going to have a global window so have some kind of static window class or static application class that can get you the window size right taking it into here is a little bit weird um game object update so i guess here you're calling the root or the uh the base class which just does that which is fine collide with terrain um clyde's top clyde's bottom so the terrain is just the the bounds of the window um and then if it does collide um i i would say maybe don't use auto everywhere by the way i've noticed this a little bit um and by a little bit i mean in most places to be honest now that i'm looking at it this is a bullying right just just write bull it's the same amount of characters and it's just so much more readable right so definitely do that um that yeah definitely have a bull here whether or not this has to be constant as well this is one of those things where you've just got like and this is well this is clearly a float just just write flow you know it's not not that difficult if you have these huge class names like i get it you know vector 2i maybe i would write auto instead of that not that's a huge class name but you know boolean and float and keep your primitive types mostly mostly just use them um and again with this const situation i don't know i just feel like this is just so it just pollutes your code so much this is gonna get you the same result right um making this cons not making this const i don't think it's gonna make any difference in code generation to be honest if it does that's super weird to me um so it should be okay um all right uh same with this right like again it's fine if you if you feel safer with const and you like that and it enforces you to make sure that that's something that you're definitely not changing leave it but the auto thing the auto thing has to go um okay so let's collide with terrain collide with terrain well okay so we're here yep okay and that's your paddle update okay in general pretty good now um uh how long have i been recording for good thing i checked because this camera shuts down after 30 minutes of recording and i was like 29 minutes 11 seconds um okay so i before we wrap this up i want to take a quick look at your rendering in general though so far this is really nice code it's really it's quite easy to read it uses some cool advanced things that's great i think that if someone who is potentially looking to hire you looked through this code they would be mostly happy they would be like oh cool this person has demonstrated quite a bit and also it's just really neat and it's really well organized and i'm i'm loving that so this is great um okay so paddle make pedal fragment shader obviously this is a game not an engine so it's fine that this doesn't have like some kind of shader class or renderer class and all of that stuff it's good to see it like this it does have a graphics file which we might look into in a minute but let's take a look at this i guess just because we're naturally here um so uh make paddle fragment shaders so again we have like some code here that literally just um shades the thread color like this here's a little tip you can remove those extra ones for white um that's fine and then let's see where that gets called because i'm actually kind of interested in that so um in paddle programs this is a paddle program create a program with wind size uniform very interesting function and i guess this this loads the window size uniform the provided shaders are then destroyed that um that i think that that that definitely sounds weird but i'm sure you probably mean these for these yeah the unit because when in opengl when you like create a shader program you wind up with a program but the individual shaders can kind of be removed they're kind of like intermediate files like dot or dot obj files when you're compiling with visual studio you don't really need the intermediate files once you have the exe once you have the program at the end of the day but this just sounds funny um all right cool so make a quadratic shader and then obviously this is the function that we're interested in um i noticed that like in a lot of these cases you're not using header files for like little uh function declarations i imagine that's probably because they're just going to be tied to this file um but i will say that having them all over your code like this is not that great and i don't know if this necessarily would would be good if you split it up into multiple files or not but i don't like scrolling through this code and every now and then seeing like a declaration rather than a definition i think that's just a little bit messy uh in an otherwise very clean project okay so create program from shaders i don't think i want to look at this um it's interesting that you have these gl namespace things i'm not sure what's going on here so what is this so you have your own little wrapper for opengl functions um oh are you retrieving them yourself that's the question hey you are that's kind of cool i guess you did say from scratch okay well done so um this uh code is actually loading the required opengl functions from um the driver instead of actually uh you know using something like glad or glue or any other libraries that do that for you and then also i'm noticing obviously in this gl um thing uh that there's yeah there's always pixel format stuff so we've got win32 api code that's actually creating the window so we're not using anything like glw um yeah so that's pretty cool um i guess you know this really is from scratch um it's very windows it's windows only obviously um it's going to be hard to bring that out to other platforms i mean you'll probably have to rewrite this file per platform which is why i'm using something like glfw and glad is good but obviously this is intended to be an exercise and for that well done i don't think i've ever done this before definitely you know done win32 api stuff before but i've never actually loaded my own open gel functions just because there's so many of them i'm likely going to need all of them and glad just does everything for me in the exact same way that i would do it so basically no reason to do that but obviously as an educational point of view this is cool because you're actually doing everything yourself which is um great okay so that's the whole gl thing let's look at the graphics class as well um and again this is like initializing we're loading uh the dll file and then dynamically probably you know loading all the function pointers to all of those and this is freeing them in cleanup and create shader from source just compiling shader stuff um yeah uh oh actually one last thing that i might look at is the ball so how is the ball ah it is some cool shader thing i thought it was i was so sure it was a texture that's actually really funny so there you go you got a little simple glow so we've got the normal the norm norm texture position which is impossible to see what it is because i don't know what the where the vertex shader is let's go let's try and find that make textured quad vertex shader and then this is just the uh the texture coordinates so do not call them texture position that that's um no idea what that is the only reason i suspect it's a vertex it's a texture coordinate is because it's in location one i guess and it's an attribute and it's a vector um in fact i probably should verify that that's the case um yeah it is right so these are texture coordinates so from zero zero one one zero okay all that stuff right so um i would call them texture coordinates for sure um and then don't even get me started on graphics and rendering um so uh all this stuff so we we have the times two minus one right so this um times too much fun this is interesting so this is this basically maps our texture corners into a negative one to one space instead of zero to one um and then we just do a little dot product between the texture position oh with itself sure so square distance to center this just squares it essentially um and then we do one minus uh square distance to center so by doing this what this actually does is make makes it so that the middle of the screen is zero zero um and then or the middle of the geometry we're drawing i should say because that's what has the texture coordinates is zero zero and then the left side is negative one right side is positive one and same with bottom and um top respectively so and then we have an intensity one minus square distance center a little bit of a fall off here some attenuation and then that's our intensity um in all channels including the alpha channel so that's kind of cool real cool little glow um and uh do collide with don't mind if i do um so this is uh yeah just some collision code and i expect this is just abb stuff it could be a little uh circle to box collision i've lost everything as usual where's my do collide function um it is yep so we have a little radius of the thing and then we just check the magnitude and see if it's correct so that's good because the ball is in fact a circle and we should be using that kind of collision for that um and that's it yeah so in general um i don't know what else there is to say i'm just going to go through everything make sure that i haven't missed anything huge but i don't think i have cool we've got a proper timer with query performance counter that's also nice to see um yeah and on our window creation function i'm not going to go over this stuff but obviously win32 api code instead of glw or whatever and you guys can see that it's like 250 lines of code it's not a huge deal to create your own window it's just that um you know obviously something like gfw makes it a lot easier for you and also does all the platform implementations for you macos and linux as well not just windows so in general that's why people use that but windows itself is not particularly difficult to set up um okay that's it so in summary i enjoyed this code i think it's written in a very neat way there are obviously some things that i would change a little bit and i did mention them but in general well done um this is really good i think that if a recruiter or some kind of person you know who was looking to hire your work with you looked at this code they would mostly be quite impressed with your code style um and that that's great and also you've obviously shown that you can write stuff from scratch and you're not overly reliant on libraries which again from a practical point of view is unnecessary but from a um you know look at me this is what i'm capable of point of view is perfect so well done i hope to see much more code like this in the future hope you guys enjoyed this video if you did please don't forget to hit the like button what did you think of this code did you disagree with me with anything i said would you add any other stuff drop it in the comments below let's have a nice discussion don't forget if you want to submit your code for a code review i do have an email address in the description below channel review gmail.com feel free to send me any kind of code that you want me to review doesn't have to be c plus doesn't have to be um i haven't looked at anything that's not c plus yet i realize that but doesn't have a pc plus plus doesn't have to be a game or game related or whatever i'll i usually just pick the projects based on what i think how i think they will do for videos and how helpful they will be to the community obviously my channel is built around simple sports and game related stuff so that's why i'm picking a lot of this stuff but i'm not not not just kind of tied into that and i mean there are like hundreds of emails in my inbox so i might not get to you but it's always worth a shot to submit it and if it's really exciting and interesting then of course i will try my best to take a look at it thank you guys for watching don't forget that you can check out the link in description below to get your free trial for skillshare premium and i will see you guys next time [Music] goodbye [Music] foreign
Info
Channel: The Cherno
Views: 56,032
Rating: 4.9517627 out of 5
Keywords: thecherno, thechernoproject, cherno, c++, programming, gamedev, game development, learn c++, c++ tutorial, game engine, how to make a game engine, game engine series
Id: jhH4z-f-B9o
Channel Id: undefined
Length: 34min 31sec (2071 seconds)
Published: Fri Dec 04 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.