Hope you enjoyed the video! Check the description if you want me to review your code. Also don't forget you can try everything Brilliant has to offer-free-for a full 30 days, visit brilliant.org/TheCherno . You’ll also get 20% off an annual premium subscription.
Hi Cherno! Can you please tell what Visual Studio theme you are using or is it a custom one, cause i haven't been able to find smth like this. Thank you in advance
Thanks for reviewing my code Cherno! I agree that there could be better way to structure the code, like having a reference to renderer rather than making it static and so on. And the initialization does seem weird when I look at it now after a year :) Thanks for the awesome feedback, I will definitely checkout the full length review on Patreon!
I love Cherno, and I love this channel, but I actually disagree with him here. I use static classes and variables by default, and only use non-static instances when needed. Especially in games, where you will only ever have one game engine and one window, both of those classes can just be static, and can be called by any other code. However, as a general rule, I normally try to minimize the number of static classes that one class interacts with. This is a great little project, btw. Good job.
Interesting take, I personally think that Static classes makes it very difficult to extend the code or write tests for the code which are very important aspects of software development imo. Obviously, some use of static classes can be justified, but if you are depending on it too much then it sounds like a code smell to me. Being said that I am open to learning more about this.
@@AftercastGames With static classes, you can’t predict when they will be initialized. This can be problematic when the order of initialization is crucial. For example, your logger needs to be initialized before the game engine itself, or network components need to be initialized separately or any other resources. As a rule of thumb, you should think twice before using static classes. Consider whether it even needs to be a class, or if it could just be functions within a namespace.
02:50 also not use "unsigned long" or "signed long" because it's same size as memory address (`sizeof(void*)`) on linux, but 32 bit on windows (both 32bit and 64bit) edit: I mean don't assume `sizeof(void*)==sizeof(long)` if you care about windows
What's next, you going to insist on proper usage of size_t, intptr_t, and uintptr_t? Suddenly int32_t and int64_t are your new best friends? And then all bit arithmetic can't be 8 based but instead needs to count from CHAR_BIT because magic numbers are bad and just maybe somebody will be targeting a more than 8-bit per char system? pfft, portability is for porters. PS, "explain the difference between size_t, intptr_t, and uintptr_t?" is a good interview question.
@@DataToTheZero I expect you are being sarcastic, but to everyone else reading this, please just use the standard types for portable code. It's not that much harder to just use the portable types, and if you care about the bit layout of a type you should really be being explicit about it anyway. Please. Please. Please I don't want to debug more issues that work on one OS and CPU but don't on another because the length of a type changed.
Meh. Windows is not always 32b long. Anyway int64_t uint64_t are sufficient. Or int_fast32_t if 32b is big enough but you want it more portable between 32b and 64b machines
Whoever made this game I LOVE YOU Because I'm working on my first game and was having second thoughts that it's not worth going through with it becuase it looks just like "Skyward Scammer". But now I'm going to finish this project if it's the last thing I do
for April fools you got to review perfect code and then suggest things that make it worse. Or, review something atrocious and say that it's perfect. idk. but it has potential :)
Maybe not as an April Fools, but I like the idea. From experience, there's a lot of value in sometimes tackling a problem from an unusual angle. This is especially true for learning as well, but unfortunately we love our one-size-fits-all way of teaching, and such things are frowned upon. I used to get in trouble, in my Math classes, for using the answer key (when available) to work backwards, in order to actually understand the concept we were learning, because their traditional method of lecture, notes, then work, didn't work for me. All the stuff that never "stuck" with me, was almost always the stuff I didn't have the answer keys for, or the times that I was outright prohibited from using my "backward" method to understand them; getting my text book (with its answer keys) taken away, and then being given only photocopies of the assignment from that textbook. So this idea could be a very interesting way to teach certain advanced topics that people really struggle with. Start out with that "perfect" code, which is so cryptic and unrecognizable to the student that they'll normally just give up on it, but then break it down into "bad" code, which despite being "bad" (and maybe funny to more advanced programmers), lands up cutting through all the in-between concepts and creating a path back to the recognizable and comfortable concepts that the amateur does understand (aka: bad code). Plus, it would be very entertaining.
They've proposed and supposedly adopted for C++26 #embed which is already available for C23. We're not quite there as far as it being usable for the majority because people don't update their compilers but once a decade. However, I'm excited for the future because if C or C++ gets it, you know other languages will copy it. I also like the filesystem::path technique because you can just write paths like you're always on a good OS, such as Linux, but then get the correct delimiter and whatnot. The real bonus is that you can just about use plain text in your source code when defining paths instead of everything in a long string or using any kind of format stuff.
@gaming53wishmaster71 Windows has a vastly larger user base than Linux as a desktop os, and this will continue. Linux is unlikely to displace Windows any time soon. Linux is way behind, and it's an inconsistent mess.
Javidx9 made a video a while ago where he made a mode7-like effect and used this exact tilemap. I guess they just took the code and pasted it into here.
15:46 I'd like to mention that in C23 there is a preprocessor directive called #embed built-in that allows you to construct a byte array from a binary file unfortunately this feature was not added to the C++23 standard however C++ may get this feature one day which would be cool
It's a little silly. The purpose of #embed is to bypass chunks of the compiler when all you want to do is embed a bunch of data. I.e. it avoids parsing an autogenerated array literal typically 2-5 times the size of the input data. But if what you needed done was bypassing the compiler, you can simply link it in the first place, e.g. objcopy -I binary -O elf64-little inputfile object.o. So the advantage is single entry point to the compiler and constexpr sizeof. C++ is all about adding stuff and painfully slow compiles so it'll probably get there.
It's proposed for C++26, so in about 12 years when everyone updates to that version you can count on seeing it everywhere. I don't know why the other guy doesn't like it, but it would actually speed up compiles because instead of parsing a giant comma separated array of 0x??'s you'll get direct inclusion. The objcopy method isn't exactly portable either.
Normally I'd agree, but this is the init for the main game loop... if this fails what are you really going to do? I say log an error and then terminate.
@@Firestar-rm8df it's a matter of principle. If you have a class function that can fail, let the user decide what to do. Imagine documenting this. "Here's the Game class. It has a void init() function which can fail but you'll never know. Just let your program crash or whatever."
@@Firestar-rm8dfI think in that case maybe but I still think it’s less error prone to have a factory function that returns an std::optional than just having the constructor throw an exception since it forced the user of your API to deal with the error explicitly. In Java its a somewhat different story because of checked exceptions, but in C++ I would do it that way.
I agree if this was something else. But it isn't. It's the top level of your application code. Looking at how the rest of this is structured an error message followed by termination is fine. I would never recommend throwing an exception from a constructor or destructor, that is messy imo. I prefer explicit return types. Returning a std::optional from a factory is a good alternative for similar cases. Especially if you make the factory function a friend of the class, and then restrict instantiation to force the use of the factory for initialization of the object. I especially like this as you can't forget to initialize portions(something I see all too often with init functions). That said, for a top level object in application code like this I still think it's kind of overkill. Now if this was a library, or something, again, yes, definitely do this, or if you are setting up a reusable framework for other games yeah you should do it. It's cleaner, and you still have a single call where you can pass all the required parameters so you don't get half initialized objects. But if the main application code for your application can't initialize really, 99% of the time all you are going to want to do is log an error and terminate nearly immediately. Maybe attempt to dump a crash file to disk, pop up an error msgbox, or attempt send a signal to some other process that we are about to force terminate if we got far enough we can do basic IO, but that's about it really for most cases. This isn't an embedded safety critical system where we keep the software limping along in whatever state we can as much as we can just so we can maybe mitigate something. It really doesn't make sense to do anything else in this case.
There are some benefits to using a fixed array for assets. You could have an enum with the names of all assets defined at compile time, which can then be used to pass in asset handles globally anywhere in your code just using that enum value. A file watcher would discover the assets at runtime so you need a gui based editor to know what the assets are.
In my system I get my assets based on their file path (for example "Resources/Meshes/cube.obj") I supposed that I would load/get assets only on application init, so I don't think I need something like a superfast std::vector, or an ultra-fast std::array. A fast enough hashmap should do the job. The benefit is that I can load my assets from their on-disk path/name, which is very convenient.
another trick is to let the last value of the enum be named size, then you can for instance store an std::array if you want to do what he did in the video. the array is then automatically set to the correct size
I think what you mean is not possible in C++. In C++ an enum is represented as literals, that are translated into numbers at compile time. There are no strings in enums in C++. I mean you could use a hashmap inside C++ that saves the name as string and the "handle" as ID if you want. But I think it's better to abstract the concepts you use for assets away and have a document that specifies what asset is what concept (e.g. floor, wall, object, player, enemy). You could still specify these mappings inside a ,.cpp or .h file if you have a smaller game or want to avoid parsing a file. This can also be useful for development, because later on you only have to write a parser that fills in the data structure you already have and the rest should still work,
@@MrDavibu See andersr9595's comment. I mean the exact same thing, using the enum as an index into an array of strings. Maybe I didn't word it properly...
I worked on a contract where the company’s developers did that - an enum and a string array (technically char*), where they’d index into the string array with the enum. It was the worst thing ever implemented and the source of way too many bugs. You never ever want code to be structured in a way that two separate data sources MUST line up to work correctly. All it takes is one value to be removed, changed, or moved in only one of the sources for everything to collapse… which is exactly what happened with the contract I worked on. So yeah, don’t do that. There’s no benefit at all to what you suggested - only ways it WILL fail.
When you say to "structure your code like a tree" at 9:00, I think what you mean to say is to "prefer object composition over class inheritance". This is by no means a new concept. There's a great section on this in the 1995 book "Design Patterns" in Section 1.6 under "Inheritance versus Composition". Yes, I quoted the section there. If you don't have the book, I'd recommend buying it (or checking it out from the library). It's a classic in Software Engineering literature, with much information still relevant.
20:52 bug introduced: you changed the expected structure of the image folders, and now all the sprites need to be folders containing a single file called ".png"... 👀
IIRC it is this way to make it separate from regular concatenation with +, because different OS can use a different path divider. Even though most of them support the forward slash out of the box.
@@Mad3011 Overloading bitshifting is bad enough, but my hot take is that overloading addition on strings was a bad idea. Addition is a fundamentally different operation than concatenation, because it doesn't compose like addition - there is no such thing as subtracting two strings, nor multiplying or dividing.
@@AlexMax2742 there doesn't need to be subtraction In the simplest terms: plus sign means addition, and concatenation is adding characters of the 2nd string to the end of the 1st It is not necessarily tied to subtraction
These are pretty healthy videos and a nice idea. Helping beginner programmers by going through their code and addressing common inconveniences is a nice idea and I think it would have helped me in a time where I was new and did identical things.
I'm just happy to see that in 2024 there are still newcomers to programming that choose C++ as their language of choice. Recruiting for C++ jobs is already getting rather difficult as other languages gain popularity.
This is interesting, so much to take in from all this lmao also extreeemely interested in seeing the pseudo-3D rendering that reminds me of SNES and Genesis games.
15:45 Is there a better or more standard way to embed binary data in C? Often I have a bunch of data structures I have to pre-compute. I could massively reduce the start-up time for some code with those things pre-baked but I can't think of an elegant way to include an arbitrary binary blob inside a C binary (other than explicitly hex encoding it in a header file which feels hacky). Also I don't think this is good practice for fonts. It makes the code less readable with no performance benefits. Just make sure you include the font file in the source so people don't need to install a system font to run your project. Embedding as binary is a little extra.
To add to the above-mentioned #embed directive, another way for older C standard would be to use assembly, though I'm not sure how cross platform that would be. This was a common thing among the GBA devs because it's faster to compile and guarantees the alignment if done properly. You can read about this in TONC Bitmap section. Using arrays is still fine otherwise, as long as they are const and are not defined in the header file, which would cause *every* file using the array to compile it again.
The source file in this case is the resource file (e.g. the .ttf), to link it you can wrap it in an object file. E.g. with GNU binutils: objcopy -I binary myfont.ttf -O elf64-little myfont.o You'd then access it with something like SDL_RWFromConstMem(&_binary_myfont_ttf_start, &_binary_myfont_ttf_size). Unfortunately the tooling for this isn't standard, but it might be available in your build system.
In C++ you could try consteval to force pre-computing during compile. It's a bit like constexpr but constexpr is allowed to fall back to runtime execution while consteval is an error if not computed at compile time. (*do your own research, I'm a bit fatigued)
@@HypocriticalLemur syntax like: Int size = getIntFromUser(); Int[size] myArr = {0}; Wont work in msvc since c-style array with dynamic size is a gcc c++ extension
@@bronkolie sure, and I have no problem with the first use of the operator on the line. But the second is between the sprite name and the string ".png"; do you add a path separator between a file name and its extension ?
@@aredrih6723 Damn, looks like you're right. It says in the docs "when appropriate", butI just checked, and it still adds a slash between a filename and extension, even if the extension is just a const char* or a std::string. Weird Edit: now that I think about it, you can have filenames like .gitignore or .bashrc. So assuming something is a file extension can't work
what are the advantages of using filesysystem::path over string? seems a bit annoying to me, is it just that you can write path/to instead of path + «/« + to?
It handles platform specific stuff such as path separators; it does path validation; it can normalize paths; you can get absolute or relative paths; change extensions, and a load more. It also means it's easier for a human to understand its sematics.
You can also easily: list the whole directory, create and destroy files or folders, check if file/folder exists, get access to every part of the address, etc
in addition to what's already been stated, it adds some extra type saftey as you know it should be a path and handled as such. And you can leverage this in a bunch of ways, such as with templates.
@@merseyviking Is there something like this for C? I somehow forgot that different platforms have different path separators. That would be very useful.
15:47 NO. God no. For the love of hod, do NOT embed resources as cpp header files. Include the ttf in your project and load it in the first time you need it. No one will subtract points if you do that (except this guy apparently).
Due to this actually being a big demand, c23 adds #embed as a preprocessor definition to do exactly what you are saying not to do. This kind of thing is great if you want a resource to be available at compile time or just to avoid wasting resources unnecessarily at runtime. Its becoming more popular now since worrying about binary size is really a thing of the past.
@@connorsweeney6210 You're taking a file, that you can view, easily, and turning it into a jumble of bytes. What's going to happen if you need to edit or replace the file? You can't. You have to pull up the original, make the change, and re-run whatever conversion. It's better to just have your code read the file. Embedding it in your code isn't going to make it run faster to any degree that matters. Maybe it did when we were coding for the NES but not any more. You're trading away maintainability for little to no reason.
The nitialization is definitely kinda 'weird' .. some things being implicitly initialized on the Objects creation, some requiring an explicit call to 'init', and even 'startGameLoop' seems to do some further initialization.. i guess that both the RAII and the deferred approaches are reasonable, but it should at least be consistent (along related Objects)
This is arguably nitpicking, but I would rather see GameEngine as a member of RPG_Game instead of a parent class. Having it as a parent class fails the classic IS-A vs HAS-A test. RPG_Game IS A GameEngine ? RPG_Game HAS A GameEngine works better.
I enjoyed the video. I took basic C courses in college and learned a bit of databases in industry. I like hearing people's opinions. Its refreshing to see logical constructive criticism. I would much rather hear Do's and Don'ts compared to politically correct ambiguity.
one thing about the std::filesystem::path concatonation you did: you did it wrong! you used the operator/ instead of operator+ which adds the separator between the filename and its extension, if done like you showed.
5:13 should we be able to see the curvature of the Earth here, especially on that height? The horizon looks completely flat, is the Earth really flat? 😱
I don't 100% agree about the Static stuff. If a method is self contained and the parameters are all it needs to perform it's function, then I see no reason for that method to be Instance only, since it is not accessing anything that is Instance only.
I’d hardly call taking a const reference to a string, initialising in one place, avoiding macros, avoiding creating objects that are mostly but not completely static, etc, premature optimisations. They just seem like logical tips and design choices.
@@MrMoon-hy6pn My point was more like most people waste so much time thinking of organizing their code that they forget the problem they're trying to solve. This guy can fix his code as he gets more mature as a programmer but it is commendable that he put in his effort and made the project work in the first place.
Haven't done C++ in multiple years, haven't even really commented in a while, but man C++ looks way more complex than I remember it being. I've been using plain C for many years now at my courses in college and uni, and looking at C++ code now is just intimidating. I know Unreal Engine uses C++, so I should probably get back into it (I'm a game dev), but the amount of different definitions and ways of doing things is quite a lot. It seems like so many people have different opinions on how to do things in C++, rather than there being agreed-upon standards and such.
Hope you enjoyed the video! Check the description if you want me to review your code.
Also don't forget you can try everything Brilliant has to offer-free-for a full 30 days, visit brilliant.org/TheCherno . You’ll also get 20% off an annual premium subscription.
Cherno make a python teaching 😊series
You don't type that out by hand.
You have an intern do it for you! 😂
Hi Cherno! Can you please tell what Visual Studio theme you are using or is it a custom one, cause i haven't been able to find smth like this. Thank you in advance
Thanks for reviewing my code Cherno! I agree that there could be better way to structure the code, like having a reference to renderer rather than making it static and so on. And the initialization does seem weird when I look at it now after a year :) Thanks for the awesome feedback, I will definitely checkout the full length review on Patreon!
I love Cherno, and I love this channel, but I actually disagree with him here. I use static classes and variables by default, and only use non-static instances when needed. Especially in games, where you will only ever have one game engine and one window, both of those classes can just be static, and can be called by any other code. However, as a general rule, I normally try to minimize the number of static classes that one class interacts with.
This is a great little project, btw. Good job.
Interesting take, I personally think that Static classes makes it very difficult to extend the code or write tests for the code which are very important aspects of software development imo. Obviously, some use of static classes can be justified, but if you are depending on it too much then it sounds like a code smell to me.
Being said that I am open to learning more about this.
L indian coder hahaha
@@AftercastGames With static classes, you can’t predict when they will be initialized. This can be problematic when the order of initialization is crucial. For example, your logger needs to be initialized before the game engine itself, or network components need to be initialized separately or any other resources.
As a rule of thumb, you should think twice before using static classes. Consider whether it even needs to be a class, or if it could just be functions within a namespace.
@@Unhinged-yu6km your name fits you pretty well
02:50 also not use "unsigned long" or "signed long" because it's same size as memory address (`sizeof(void*)`) on linux, but 32 bit on windows (both 32bit and 64bit)
edit: I mean don't assume `sizeof(void*)==sizeof(long)` if you care about windows
What's next, you going to insist on proper usage of size_t, intptr_t, and uintptr_t? Suddenly int32_t and int64_t are your new best friends? And then all bit arithmetic can't be 8 based but instead needs to count from CHAR_BIT because magic numbers are bad and just maybe somebody will be targeting a more than 8-bit per char system? pfft, portability is for porters.
PS, "explain the difference between size_t, intptr_t, and uintptr_t?" is a good interview question.
@@DataToTheZero I expect you are being sarcastic, but to everyone else reading this, please just use the standard types for portable code. It's not that much harder to just use the portable types, and if you care about the bit layout of a type you should really be being explicit about it anyway. Please. Please. Please I don't want to debug more issues that work on one OS and CPU but don't on another because the length of a type changed.
Meh. Windows is not always 32b long.
Anyway int64_t uint64_t are sufficient. Or int_fast32_t if 32b is big enough but you want it more portable between 32b and 64b machines
@@mytech6779which windows `4!=sizeof(long)`?
Whoever made this game
I LOVE YOU
Because I'm working on my first game and was having second thoughts that it's not worth going through with it becuase it looks just like "Skyward Scammer". But now I'm going to finish this project if it's the last thing I do
Glad to hear. Go for it! :)
Name checks out.
@@fangornthewise lol
@@fangornthewiseyour name doesn't check out
Hey, been lurk watching your channel for three years now. Time to give back, всего наилучшего
Благодарю! ❤
How did you get my code?
Этот код не только твой, тёзка... 🌚
он наш
@@ndrechtseiter speak english.
@@TheRealZitroX 🤓☝️
@@TheRealZitroX you have a "translate to English" button, by the way.
for April fools you got to review perfect code and then suggest things that make it worse. Or, review something atrocious and say that it's perfect. idk. but it has potential :)
But… what is perfect code?
Perfect code is basically what haskell programmers masturbate to
he would lose credibility if he did that
Maybe not as an April Fools, but I like the idea.
From experience, there's a lot of value in sometimes tackling a problem from an unusual angle. This is especially true for learning as well, but unfortunately we love our one-size-fits-all way of teaching, and such things are frowned upon.
I used to get in trouble, in my Math classes, for using the answer key (when available) to work backwards, in order to actually understand the concept we were learning, because their traditional method of lecture, notes, then work, didn't work for me. All the stuff that never "stuck" with me, was almost always the stuff I didn't have the answer keys for, or the times that I was outright prohibited from using my "backward" method to understand them; getting my text book (with its answer keys) taken away, and then being given only photocopies of the assignment from that textbook.
So this idea could be a very interesting way to teach certain advanced topics that people really struggle with. Start out with that "perfect" code, which is so cryptic and unrecognizable to the student that they'll normally just give up on it, but then break it down into "bad" code, which despite being "bad" (and maybe funny to more advanced programmers), lands up cutting through all the in-between concepts and creating a path back to the recognizable and comfortable concepts that the amateur does understand (aka: bad code).
Plus, it would be very entertaining.
@@pandalonium int main() { return 0; }
3:43 - "Okay, im excited, lets play this gam..." *ERROR: THIS CODE EXECUTION CANNOT PROCEED...*
Lmao xD
Lol guess he skipped over the troubleshooting and left it in the Patreon version.
@@JavedAlam-ce4mu ye, but funny cut xd
I think it was a fairly simple fix at least, looks like that thing that happens when you forget to put the dll in the same directory as the exe
They've proposed and supposedly adopted for C++26 #embed which is already available for C23. We're not quite there as far as it being usable for the majority because people don't update their compilers but once a decade. However, I'm excited for the future because if C or C++ gets it, you know other languages will copy it. I also like the filesystem::path technique because you can just write paths like you're always on a good OS, such as Linux, but then get the correct delimiter and whatnot. The real bonus is that you can just about use plain text in your source code when defining paths instead of everything in a long string or using any kind of format stuff.
Totally seeing the remnants of one lone coder engine here!
2:39 I usually support Linux and Windows but not MacOS since I have never used it
that's not very chill
And me idk if i would support windows or mac as for i think windows has to meany holes and is a dieing os linux to the win
@@gaming53wishmaster71 And I don't try to support anything other than my particular device b/c I'm lazy
MacOS can be a nightmare to support
@gaming53wishmaster71 Windows has a vastly larger user base than Linux as a desktop os, and this will continue. Linux is unlikely to displace Windows any time soon. Linux is way behind, and it's an inconsistent mess.
5:12 is that seriously Mario Raceway from Super Mario Kart??? XD
lol I didn’t even notice that!
Javidx9 made a video a while ago where he made a mode7-like effect and used this exact tilemap. I guess they just took the code and pasted it into here.
I immediately thought the same as soon as I saw it - straight away I was like "have they seriously ripped off the mario kart track?" 😂
15:46 I'd like to mention that in C23 there is a preprocessor directive called #embed built-in that allows you to construct a byte array from a binary file unfortunately this feature was not added to the C++23 standard however C++ may get this feature one day which would be cool
It's a little silly. The purpose of #embed is to bypass chunks of the compiler when all you want to do is embed a bunch of data. I.e. it avoids parsing an autogenerated array literal typically 2-5 times the size of the input data. But if what you needed done was bypassing the compiler, you can simply link it in the first place, e.g. objcopy -I binary -O elf64-little inputfile object.o. So the advantage is single entry point to the compiler and constexpr sizeof. C++ is all about adding stuff and painfully slow compiles so it'll probably get there.
It's proposed for C++26, so in about 12 years when everyone updates to that version you can count on seeing it everywhere. I don't know why the other guy doesn't like it, but it would actually speed up compiles because instead of parsing a giant comma separated array of 0x??'s you'll get direct inclusion. The objcopy method isn't exactly portable either.
Congrats on nearing end of October with 666K subscribers :)
Wow i dident even notice he had 666k wow lol happy Halloween in deed
You don't put stuff in a c'tor that can fail, hence the init function which offers a return type.
Normally I'd agree, but this is the init for the main game loop... if this fails what are you really going to do? I say log an error and then terminate.
@@Firestar-rm8df it's a matter of principle. If you have a class function that can fail, let the user decide what to do. Imagine documenting this. "Here's the Game class. It has a void init() function which can fail but you'll never know. Just let your program crash or whatever."
@@Firestar-rm8dfI think in that case maybe but I still think it’s less error prone to have a factory function that returns an std::optional than just having the constructor throw an exception since it forced the user of your API to deal with the error explicitly. In Java its a somewhat different story because of checked exceptions, but in C++ I would do it that way.
I agree if this was something else. But it isn't. It's the top level of your application code. Looking at how the rest of this is structured an error message followed by termination is fine.
I would never recommend throwing an exception from a constructor or destructor, that is messy imo. I prefer explicit return types.
Returning a std::optional from a factory is a good alternative for similar cases. Especially if you make the factory function a friend of the class, and then restrict instantiation to force the use of the factory for initialization of the object. I especially like this as you can't forget to initialize portions(something I see all too often with init functions).
That said, for a top level object in application code like this I still think it's kind of overkill. Now if this was a library, or something, again, yes, definitely do this, or if you are setting up a reusable framework for other games yeah you should do it. It's cleaner, and you still have a single call where you can pass all the required parameters so you don't get half initialized objects. But if the main application code for your application can't initialize really, 99% of the time all you are going to want to do is log an error and terminate nearly immediately. Maybe attempt to dump a crash file to disk, pop up an error msgbox, or attempt send a signal to some other process that we are about to force terminate if we got far enough we can do basic IO, but that's about it really for most cases. This isn't an embedded safety critical system where we keep the software limping along in whatever state we can as much as we can just so we can maybe mitigate something. It really doesn't make sense to do anything else in this case.
There are some benefits to using a fixed array for assets. You could have an enum with the names of all assets defined at compile time, which can then be used to pass in asset handles globally anywhere in your code just using that enum value. A file watcher would discover the assets at runtime so you need a gui based editor to know what the assets are.
In my system I get my assets based on their file path (for example "Resources/Meshes/cube.obj")
I supposed that I would load/get assets only on application init, so I don't think I need something like a superfast std::vector, or an ultra-fast std::array. A fast enough hashmap should do the job. The benefit is that I can load my assets from their on-disk path/name, which is very convenient.
another trick is to let the last value of the enum be named size, then you can for instance store an std::array if you want to do what he did in the video. the array is then automatically set to the correct size
I think what you mean is not possible in C++.
In C++ an enum is represented as literals, that are translated into numbers at compile time. There are no strings in enums in C++.
I mean you could use a hashmap inside C++ that saves the name as string and the "handle" as ID if you want.
But I think it's better to abstract the concepts you use for assets away and have a document that specifies what asset is what concept (e.g. floor, wall, object, player, enemy). You could still specify these mappings inside a ,.cpp or .h file if you have a smaller game or want to avoid parsing a file. This can also be useful for development, because later on you only have to write a parser that fills in the data structure you already have and the rest should still work,
@@MrDavibu See andersr9595's comment. I mean the exact same thing, using the enum as an index into an array of strings. Maybe I didn't word it properly...
I worked on a contract where the company’s developers did that - an enum and a string array (technically char*), where they’d index into the string array with the enum. It was the worst thing ever implemented and the source of way too many bugs.
You never ever want code to be structured in a way that two separate data sources MUST line up to work correctly. All it takes is one value to be removed, changed, or moved in only one of the sources for everything to collapse… which is exactly what happened with the contract I worked on. So yeah, don’t do that.
There’s no benefit at all to what you suggested - only ways it WILL fail.
What a speedrun! Harder than it looks, that was pretty impressive for a first go.
When you say to "structure your code like a tree" at 9:00, I think what you mean to say is to "prefer object composition over class inheritance". This is by no means a new concept. There's a great section on this in the 1995 book "Design Patterns" in Section 1.6 under "Inheritance versus Composition". Yes, I quoted the section there. If you don't have the book, I'd recommend buying it (or checking it out from the library). It's a classic in Software Engineering literature, with much information still relevant.
20:52 bug introduced: you changed the expected structure of the image folders, and now all the sprites need to be folders containing a single file called ".png"... 👀
Holy crap, they have overloaded the division operator to use it for concatenating OS path strings
Yep, and here I thought overloading the shift operators for io was bad already. xD
IIRC it is this way to make it separate from regular concatenation with +, because different OS can use a different path divider. Even though most of them support the forward slash out of the box.
Even worse when you accidentally use that overload on a URI out of habit.
@@Mad3011 Overloading bitshifting is bad enough, but my hot take is that overloading addition on strings was a bad idea. Addition is a fundamentally different operation than concatenation, because it doesn't compose like addition - there is no such thing as subtracting two strings, nor multiplying or dividing.
@@AlexMax2742 there doesn't need to be subtraction
In the simplest terms: plus sign means addition, and concatenation is adding characters of the 2nd string to the end of the 1st
It is not necessarily tied to subtraction
These are pretty healthy videos and a nice idea. Helping beginner programmers by going through their code and addressing common inconveniences is a nice idea and I think it would have helped me in a time where I was new and did identical things.
I'm just happy to see that in 2024 there are still newcomers to programming that choose C++ as their language of choice. Recruiting for C++ jobs is already getting rather difficult as other languages gain popularity.
Rust programmers learning there’s no Rust jobs
Realtalk thought, I love C++ because it gives you options for relative safety but still allows you to do hacky things if you need to
@@RetroAndChill Rust lets you do wacky stuff too, just makes you put it in an unsafe block so it's easier to debug.
@@adygombos4469 And then you might as well use C++
@@adygombos4469too much hassle with rust. c/c++ is much easier
bro saw that macro with replacement text and got legit pissed lmaoo
Amazing vid as always, Cherno. Could you please make a video dedicated strictly to best practices?
That's his entire channel
Ending reminds me of Mode 7 from SNES.
This is interesting, so much to take in from all this lmao also extreeemely interested in seeing the pseudo-3D rendering that reminds me of SNES and Genesis games.
You make me feel so old that you don't immediately recognize Mode 7 graphics from Mario Kart on the SNES :)
15:45 Is there a better or more standard way to embed binary data in C? Often I have a bunch of data structures I have to pre-compute. I could massively reduce the start-up time for some code with those things pre-baked but I can't think of an elegant way to include an arbitrary binary blob inside a C binary (other than explicitly hex encoding it in a header file which feels hacky).
Also I don't think this is good practice for fonts. It makes the code less readable with no performance benefits. Just make sure you include the font file in the source so people don't need to install a system font to run your project. Embedding as binary is a little extra.
C23 introduced the #embed preprocessor statement. If using a newer version is not a hindrance, you can take a look at that?
To add to the above-mentioned #embed directive, another way for older C standard would be to use assembly, though I'm not sure how cross platform that would be. This was a common thing among the GBA devs because it's faster to compile and guarantees the alignment if done properly. You can read about this in TONC Bitmap section. Using arrays is still fine otherwise, as long as they are const and are not defined in the header file, which would cause *every* file using the array to compile it again.
@@VerMishelbYou can declare e.g. mFontBytes as extern in a header and define the actual data in a separate source file
The source file in this case is the resource file (e.g. the .ttf), to link it you can wrap it in an object file. E.g. with GNU binutils:
objcopy -I binary myfont.ttf -O elf64-little myfont.o
You'd then access it with something like SDL_RWFromConstMem(&_binary_myfont_ttf_start, &_binary_myfont_ttf_size). Unfortunately the tooling for this isn't standard, but it might be available in your build system.
In C++ you could try consteval to force pre-computing during compile. It's a bit like constexpr but constexpr is allowed to fall back to runtime execution while consteval is an error if not computed at compile time. (*do your own research, I'm a bit fatigued)
2:58 - also dont use platform-specific compiler C++ extensions. Got caught on GCC dynamic c-style arrays not compiling on windows msvc
What do you mean? What was the problem exactly?
@@HypocriticalLemur syntax like:
Int size = getIntFromUser();
Int[size] myArr = {0};
Wont work in msvc since c-style array with dynamic size is a gcc c++ extension
Hahaha, 18:55 looks like Pirate Software (Maldavius Figtree)'s code.
21:02 never used the filesystem path api before but shouldn't there be a + instead of a / between the filename and extension ?
The std::filesystem::path class has the / operator overloaded to concatenate two paths, while adding a path separator in the middle if necessary
@@bronkolie sure, and I have no problem with the first use of the operator on the line.
But the second is between the sprite name and the string ".png"; do you add a path separator between a file name and its extension ?
@@aredrih6723 Damn, looks like you're right. It says in the docs "when appropriate", butI just checked, and it still adds a slash between a filename and extension, even if the extension is just a const char* or a std::string. Weird
Edit: now that I think about it, you can have filenames like .gitignore or .bashrc. So assuming something is a file extension can't work
Either path+".png" or replace_extension(path, "png").
4:55 the outplay thoo
Woupd you ever go over some of the godot engine source code? Would be cool to hear from a game engine guy what you like or don't like about it's code
where can i buy this hoodie ?
@ 16:14 Not a headafile! Anything but those!
I’m a web dev, no idea what is going on in any of your videos but I still love to watch them.
what are the advantages of using filesysystem::path over string? seems a bit annoying to me, is it just that you can write path/to instead of path + «/« + to?
More functionality I assume
It handles platform specific stuff such as path separators; it does path validation; it can normalize paths; you can get absolute or relative paths; change extensions, and a load more. It also means it's easier for a human to understand its sematics.
You can also easily: list the whole directory, create and destroy files or folders, check if file/folder exists, get access to every part of the address, etc
in addition to what's already been stated, it adds some extra type saftey as you know it should be a path and handled as such. And you can leverage this in a bunch of ways, such as with templates.
@@merseyviking Is there something like this for C? I somehow forgot that different platforms have different path separators. That would be very useful.
I initializer my pointer arr without using for loop
@11:16 - How did you select all the static keywords like that?
Holding alt and dragging over the code allows a more squared highlight
@@dmitrikozak7837 OMG!!!!
You have literally made my week!
Thank you.
17:44 how did he change ASSETS to assets so fast??
Regardless of the issues, what a nice game.
Couldn't RPG Assets in 17:34 be done with a single string and then split by ", " or " " instead of hardcoding it like that?
15:47 NO. God no. For the love of hod, do NOT embed resources as cpp header files. Include the ttf in your project and load it in the first time you need it. No one will subtract points if you do that (except this guy apparently).
Due to this actually being a big demand, c23 adds #embed as a preprocessor definition to do exactly what you are saying not to do. This kind of thing is great if you want a resource to be available at compile time or just to avoid wasting resources unnecessarily at runtime. Its becoming more popular now since worrying about binary size is really a thing of the past.
why not ?
Why not?
Why not, brony?
@@connorsweeney6210 You're taking a file, that you can view, easily, and turning it into a jumble of bytes. What's going to happen if you need to edit or replace the file? You can't. You have to pull up the original, make the change, and re-run whatever conversion. It's better to just have your code read the file. Embedding it in your code isn't going to make it run faster to any degree that matters. Maybe it did when we were coding for the NES but not any more. You're trading away maintainability for little to no reason.
The nitialization is definitely kinda 'weird' .. some things being implicitly initialized on the Objects creation, some requiring an explicit call to 'init', and even 'startGameLoop' seems to do some further initialization.. i guess that both the RAII and the deferred approaches are reasonable, but it should at least be consistent (along related Objects)
This is arguably nitpicking, but I would rather see GameEngine as a member of RPG_Game instead of a parent class. Having it as a parent class fails the classic IS-A vs HAS-A test. RPG_Game IS A GameEngine ? RPG_Game HAS A GameEngine works better.
yes!!
I love the way you say 'code' lol
I enjoyed the video. I took basic C courses in college and learned a bit of databases in industry. I like hearing people's opinions. Its refreshing to see logical constructive criticism. I would much rather hear Do's and Don'ts compared to politically correct ambiguity.
5:12 This is like Dragon Ball Z on Gameboy Advance!
Do you accept rust code?
Hes the cherno for c++ maybe need the rherno for rust
He's done C# before, and he said he's open to accepting other languages. Although I don't know about Rust in particular...
@@zanagiLOL
no, even ascii shader files are separate includes. but that needs to be editable. the font file does not.
make an opencl video
The Cherno : that was a journey after finishing the game in 5 mins. 😂
one thing about the std::filesystem::path concatonation you did: you did it wrong! you used the operator/ instead of operator+ which adds the separator between the filename and its extension, if done like you showed.
15:56 How can we embed the font and take byte into cpp? can u explain dear master? for example define the fonts in a global h file?
The folder idea makes me think you have no idea what you are doing.
I think every developer has the famous singleton phase, it will stop after someone ask you to test your code and then you see why singletons are bad 😂
"I absolutely love the tiling. The fact that this texture does not tile whatsoever, but it's so confidently used."
😂💀
5:13 should we be able to see the curvature of the Earth here, especially on that height? The horizon looks completely flat, is the Earth really flat? 😱
I wanted to check if you have some thoughts on COW(Copy on Write) in C++. can you make a video on it.
the background of the end was DP1 from mario kart ... ...
This is obviously Mode 7 on a SNES. I am 100% sure they use a SNES emulator and use that to draw the end screen ;)
Do Tokyospiff Code review 😂
This would be amazing 🤣
If by hand you mean copy paste and tab to next instance of character, then yes.
It took me 3 minutes to figure out what this "card" you kept talking about was. I realized you're just an aussie pronouncing "code" :D
I don't 100% agree about the Static stuff. If a method is self contained and the parameters are all it needs to perform it's function, then I see no reason for that method to be Instance only, since it is not accessing anything that is Instance only.
was that a map from wacky wheels?
I don’t think that code review should be "paid", because it is made to improve reviewed code
jeeeez! He doesn't owe you a thing and he's published a free review any way
@@deadliaskiYeah people complain way too much when it comes to making stuff paid as a content creator
You didnt notice the ground textures being from Mario Kart? Wonder why Mario Kart?
Does anyone recognize the brand of his hoodie?
trying to locate this hoodie also. any luck?
Me watching the video whilst having no Idea what is going on
main function consisting of 2 meaningful lines is my biggest trigger. why do people do this is beyond my understanding
One simpe rule for mewbies to make their code and life better... composition over inheritance!
Spending any time developing for Mac seems like a waste 3% of PC are Macs. If it is not a mobile app really point to think about those OS.
Bro that's super mario kart from snes lol
I hate seeing code files that could have been YAML or even XML or JSON.
Just wondering. Have you ever rewritten someone’s code to make a more efficient program? More content potential.
Coding structure can significantly improve overtime with experience if you are learning along the way.
I won't make apple ports simply out of spite.
Saurabh Mehta ×
Soraab meta ♡
i feel kinda bad id be infuriation if i was on the receiving end of this
Oh... I thought your name was ''Chemo", as in chemotherapy, oops
The Cherno make a series making an AI model (YOLO) , i think you teaching that stuff will be very exiting!
When your subscriber count is stuck at 666K 😂
Is he used to his feet?
I'm on minute 5, didn't see the code yet, but I love it. Best game 2024.
Daily jeet code review
This is a straight rip from javid one lone coder the scene at the end
100% new game engine in C++ and in SDL??? ))))
Great video. Next video review please.
6-8 minute ads....jesus UA-cam needs to stop
lol it's not a TimeStamp Yan, it's a Card.
Very interesting!
Damn, i do this all the time
Mr skill issues trying to roast others 🤣🤣
What does "skill issues" mean in this context? It's an often used phrase in programming comments. Is it a derogotory thing?
16:30 beautiful
Nice 😊
Premature optimization is bad. This code is better than doing nothing,
I’d hardly call taking a const reference to a string, initialising in one place, avoiding macros, avoiding creating objects that are mostly but not completely static, etc, premature optimisations. They just seem like logical tips and design choices.
@@MrMoon-hy6pn My point was more like most people waste so much time thinking of organizing their code that they forget the problem they're trying to solve. This guy can fix his code as he gets more mature as a programmer but it is commendable that he put in his effort and made the project work in the first place.
@@Nitu-s7ebro the guy asks for a code review and got the code review, what is the problem here?
@@poleve5409 I don't.
video starts at 3:44 . thank me later
Haven't done C++ in multiple years, haven't even really commented in a while, but man C++ looks way more complex than I remember it being.
I've been using plain C for many years now at my courses in college and uni, and looking at C++ code now is just intimidating. I know Unreal Engine uses C++, so I should probably get back into it (I'm a game dev), but the amount of different definitions and ways of doing things is quite a lot. It seems like so many people have different opinions on how to do things in C++, rather than there being agreed-upon standards and such.
I coded my game on rocks and use AI to read it
Do you sleep enough?
❤❤