Love this video. I recently did something very similar and implemented a few different architectures like this for a simple asteroid game on GitHub pfirsich/game-architecture-comparison. I implemented pretty much the alternatives you presented and some more.
I think the key idea is to know your constraints and build only what you need. General purpose systems and large teams have very different constraints and requirements compared to a small team and highly specific system. The real trick is building only what you need, minimally and effectively, but in such a way that it leaves room to expand in the future if necessary - you have to keep the bigger picture and future in mind even if you're not yet building it, or there is a risk of painting yourself into a corner and having to rewrite
Very good advice for general game programming, and nice to have the example in Odin. Megastructs are not for game programming only, I think most software would benefit from this approach. Instead of classes, object instances and their methods, you just have chunks of data that allow for a large set of behaviour, and functions operating on that data.
How do you multithread it? If you can't reason about your data it's hard to scale multithreading (especially deterministically). I get most games don't need multithreading, but for something like mass scale rts it would be nice..
Yeah, for multithreading, you'd be looking at bespoke systems that don't fit here I've only multithreaded rendering before, so I'm only speculating here... You could split routines into threads if they don't affect each other Let's say physics in one, updating status effects in another Or you could use some spatial data structure to query entities nearby each other and send results to a job queue
(Messed up the sizes, which is really important for this, so changing it. I said it was 7 megs, but it 28 megs (7*4)) How to scale it depends on needs (which youve supplied). So, for a mass scale RTS, I built a prototype of a chunk based "ECS" (though, this idea would work for the other 2 options also). So, each block of data was a compile time constant size, 4096 being really solid. My ECS was also double buffered, and allowed reads from last frames data by any thread AND made things faster for me (bonus!) Then, for my systems I basically did: foreachcore( process chunk_stride_start+core_i ) {I say "ECS" but it was a chunked structure of arrays system per archetype (much like most ECSs are under the hood)} Its part of my billion active entity updates / second (prototype) project. I got there on my AMD 5950x + 3090 (though the video card was just mostly idling). The only entities I had in at the time however was projectiles flying all over, and so more experimentation is needed. { I dont personally care about strict deterministic behaviour (which is a long story), but given storing both old and new states, it should be very doable. I havent put a huge amount of thought into it yet though } A couple of big takeaways. Gameplay data is very often (even for an RTS) a very small portion of all the data a game needs to deal with. So, doing things like double buffering of it (or potentially even more) is very often not a huge deal. Lets say you had a million units, like actual individually instructable units (not like what Total War does). Even at full res for position, youre going to have 3 floats for position, and 2 for orientation (4 of you must). So thats 7*4 or 28 megs on the outside and can absolutely be shrunk if you need more units/less data, so 56 megs for updating its Pos+Orient data. 1 2048x2048 texture is 16 megs by itself (granted, youll be storing that as something like DXTC<blah bla> or whatever, but still) Going >1million units is still doable to about 10million, but over 10 or 20 million Id start coming up with ways to process groups of them more like particles than units.
@@marcsh_dev My units were easily 2kb in size for a basic unit implementation (for a game with mechanics, not a battle demo. ofcourse this is not including game date which was just a pointer/unit id to another table somewhere), not even with all the behaviors I currently support in Cosmonarchy Not sure about chunks btw, I have not tried/seen such implementation. I tried making an ecs, but got pretty complicated pretty quickly when I wanted to start implementing deterministic multithreading (which I need for lock step multiplayer)
2kb of dynamically changing data per frame? Thats the data Im talking about. You can have all sorts of static / per unit data, and only occasionally changing data in different spots. The point of the double buffering is solely for the small per frame subset of data.
Re Chunks: If you have a flat array, its relatively simple. You can even just do it with a single command on many different languages and systems. Ie they often have a 'parallel_for( my_huge_array, num_cores )'. Its also easy enough to go from something like std::vector to motive::vector_chunk, and just have a set of pointers to vector chunks It is slightly tricky to add and remove entities from the chunk, but not hugely different than doing it from a big array
I believe the "distinct int" can be done in C, by just wrapping an int in a struct. typedef struct { int id; } Entity_Id; void do_something_with_an_entity(Entity_Id eid) { for (Entity_Id i = {0}; i.id < 10; ++i.id) { //... } }
@@DylanFalconer It was hard for me too. But slices, custom allocators, "batteries included", defer, array programming, context etc. were too good to not swich.
Raylib is simpler to use out of the box as the API handles most things you would need to program a game on the graphics side, while, SDL is more flexible and, is designed in a way to be easily integrated with other libraries and APIs
@ 1:00, im pretty sure it will display 420, IF it reaches the printf. It either overwrites that place of memory and prints it, or u get an access violation by accessing invalid memory. *maybe* it can happen that the piece of memory being written is volatile and somehow changes value in the nanosseconds that are between the instructions
I seem to be able to successfully build and run but the game immediately closes similarly to other here. My issue is that I get this error Error reading file: ./shaders/default.vert. errno: 2 Error reading shader: ./shaders/default.vert I've try soling it but I'm at a loss here :(
How do you manage memory for short lived objects ? Community says that use temp allocator and flush everything when the message loop exits. But that's entire program's life time! is there any best strategy to manage memory for short living objects ?
You can do like community said (per frame/cycle) if this is not what you want create new scope with curly braces, allocate memory and defer it's release
@@kcvinu I tought you needed some object of unknown size. If size is known then heap allocation is best way. No allocators and no need to free used memory. It will be done automaticly.
@@wiktorwektor123 I have questions about temp_allocator. Because, 'free_all' is called at the end of the program. So the memory blocks are being remained in leaked state. That's my concern.
So I changed my function a little bit. Instead of using 'make' to create a dynamic array each time, I used a permanent heap allocated array with enough size.
For a paid Odin gamedev course, say on Udemy, I would want to see in the "syllabus" that "shipping a game" was covered which would include at least: providing the player with control customization and other settings, using the Steam API and deploying to other platforms such as iOS and Android.
Please continue making more Odin tutorials. I would awesome to have something like "Odin by example", where the complexity of each tutorial increases slowly.
Already made one and working on another! Check the link in the description (there's a free Pong course available) I'm working on my paid course right now as priority, but I'll be making Odin + Raylib videos every so often on YT as well
I would define the Header like so, to avoid any alignment issues: struct array { size_t capacity; size_t length; T data[]; } you would then initialize it all the same, except you would return &h->data; Also you can use designated initializers: h = {.capacity=capacity, .length=length}; This is more efficient since this allows padding to be explicitly overwritten with 0.
3:09 oh yeah it's "all you need", except that you can spend pretty much an infinite amount of time on any one of those things if you want to get them on a level anywhere close to being marketable. looking at the Hollow Knight example analytically, there's a ton of particle and lighting effects stacked on top of each other. if you were to develop all of those systems from scratch it would take months if not years and at the end it would still look worse. making your own game engine is great for learning (spent a decade building engines in JS, C, and Nim myself), but just don't expect anything marketable will come out of it.
There are tons of games that didn't use a 3rd party engine and look great. Tooth and Tail, Stardew Valley, Teardown to name a few. Also, pretty much every game before the 2000s used in-house custom engines. I don't know you but I'd bet you suffer from the same thing I did which is lack of focus. If you spent that time working towards one game or a series of games, it'd be nearly impossible to have a bad looking game after 10 years. Just because you didn't do it doesn't mean others can't. Also doesn't mean you can't - you just chose not to.
@@DylanFalconer please note that I did not say that custom-engine games never look great. the point is that you shouldn't expect to achieve something marketable anywhere near a comparable time frame - repeatedly. we actually want to try making some money here, right? replace "tons of" with "tiny few", especially in relation to the amount of marketable games that are not using a custom engine. sure, it's theoretically possible, like have your girlfriend fund you for a decade like Stardew Valley guy, but these really are exceptions, sorry to say. and yes, if everybody would make their own engines like back in the day then that would be what you had to compete against, and so naturally it would be viable. as for the focus, yeah, certainly, if one is capable to consistently work on only the most essential features, then they'd make fast progress. but honestly, is that realistic? I don't think that this is how our brains work in general, and if you are accountable only to yourself there's no way you'd maintain that. again - speaking generally. I am sure there are some prodigies.
This tutorial is not helpful at all. I need the links, nothing i'm getting is the same. all i want is to setup sdl and a simple text renderer that's IT why can't it be that simple omfg
I agree, the setup stage for these projects can be frustrating. The links have changed since a few years ago. Is there a specific reason want to use SDL for your text renderer?
@@DylanFalconer tbh I js want a text renderer, whatever works. Also the reason I want to use Sdl is because I'm challenging myself to make a 3d raycaster
Gotcha, in that case have you considered something like Raylib? It's pretty flexible, you can still write shaders but it has a bunch of built in functionality (including text rendering)
Keep going - you got this. Something that helps me is to focus on actions I can perform each day/week that get me closer to the outcome I want. I try not to compare myself to other people anymore, just focus on consistency
I like your content a lot; if your videos also had accompanying text/screenshots/slides it'd be easier for me to follow. Or you could even just share your screen while you go through docs/code/demos/etc. Just an idea. 🤩
There is a bit of compile time code execution using the "when statement" - I haven't tried it out and can't speak to the full utility of it as I haven't found a use for comptime in my own projects. What are you using comptime for in Zig?
@@DylanFalconer I use comptime to implement serialization, lookup tables and RTTI, but the latter is not very convenient to use. I hope they implement at least type id in the future, because it requires some hacks to implement at the moment.
I'm building my largest game, from scratch, in my own programming language in Zig, and I agree with everything in this video. Some people use commercial game engines because they come with level editors... but if your game is simple enough, and you're a competent enough programmer, you can just create your own editor. Are there are huge positives: I know exactly how my editor works, and it is minimal since it only does what it needs to do. It also only took 1 month to program. Why spend years "learning" how to make a game in Unity when you can just make your own level editor in a month lol.
Yeah, at the time I made this video the general online landscape was "What? Make a game engine? Are you insane? What a waste of time." So I just wanted to provide a counter argument. I still maintain that people who aren't interested in programming should NOT try to make their own engines. Good luck with your game :) Will you release a demo?
@@DylanFalconer Absolutely, but not until after I've announced the game. (Which I intend to do this year, for a possible 2025 release.) It's gonna be big. (At least for a puzzle game.)
Great video, I really like your description of Odin as "modern and minimal". Odin's easily the best-designed programming language I've ever used. Something that didn't make the top 11 in the video that I love about Odin is the quality of the compiler error messages, which I've found to be quite high. When I recently learned the bare-basics of Vulkan, I used Odin to go through the 40-ish hour tutorial, full-well knowing that if I had used the language the tutorial was written in (C++), the tutorial would have taken more than 100 hours.
Yeah, that's absolutely a boon! I didn't even think about it, but it's a huge benefit. I often read the standard library without hesitation. Can't say the same for some other languages, unfortunately...