Writing a 3D Game in C (in 2022)
aarongeisler.comVery nice.
That said even C has already a way too rich and complex syntax. ISO is doing planned obsolescence with its syntax instead of fixing it, and no the linux kernel is not written in C, but using a gcc-massively-extended C dialect.
Then it needs discipline: c89 with benign bits of c99/c11/etc.
A good move is to write/use a C coded game engine, I wish godot was plain and simple C.
If Godot was written in C, I don't think I'd need anything else.
Still... Godot makes HEAVY use of C++'s exclusive features. The GUI system relies on complex static and virtual inheritance and the scripting engine makes serious use of macros for automatic class integration. ...and that's just me scratching the surface with my limited knowledge!
A modern 3D engine, written in conservative C, fully libdl-ized (100% of system dependencies are dynamically loaded, "libc" included). Proper compile-time(OS abstraction) and runtime(fallbacks) tables of functions. The hard part is the discipline to stick to c89 with benign bits of c99/c11/etc to avoid planned obsolescence via C syntax and/or the compilers.
I feel like this subthread is less about what would make a good game engine and more about aesthetics and personal itches.
That said, make SDL2 the core and use Lua with LuaJIT for the scripting language and consider me sold.
Also something something Webassembly, I don't know. There's a reason the road to Hell is paved with game engine implementations.
That would be the technical core only and, of course, the 3D game engine (or 2D), could be "bad" (and that's a big word), even with a clean technical core, and the other way can be true too, a game engine can be "good" (still a big word) with an horrible code base.
SDL2 is only a part of the OS abstraction, it is way below the game engine layer. I don't know if SDL2 is properly "libdl-ing" everything (its "libc" dependencies included). On elf/linux platform, it means there is no C runtime "main()" function but the SYSV ABI entry point (which is basically a main() anyway), and the build products should be pure and simple(aka with the least amount of relocation _types_) ELF64 shared libraries. This has to be inspected with tools like readelf: namely, there would be only libdl in the DT_NEEDED ELF section. That said, even with clean libdl-ing, care must be provided on the used symbols with their versions (they must freaking old, and I mean REALLY old since there is a manic abuse of versioning from the glibc devs, probably msft grade planned obsolescence). Ofc, some peculiar libs (libm?) should be statically linked into binaries directly.
On top of SDL2, some runtime and compile-time tables of functions are still required though, and this is a process which must be started right at the beginning, and NOT on the main platform (then probably NOT doz), that to be sure those runtime and compile-time tables of functions are designed right from the start. I am talking about the OSes, but the hard part is 3D APIs: vulkan/metal/console, to please all those APIs and make they work on many drivers... oooof!
The devs of "drag" (steam), did the right thing: code and dev on elf/linux, test on elf/linux, and also build for doz and test on doz. Basically, seeing doz as on embedded platform. Weirdly enough those guys were quickly hired by a doz-only game company :( . Those guys were making a good game and it was technically amazing.
Oh, one very important thing I forgot to mention: why, at least on elf/linux, c++ is extremely toxic for game development, mainly because of gcc devs.
As I said, game binaries should "libdl" everything, namely be pure and simple ELF64 binaries (with the least amount of relocation _types_).
Well, the gcc standard c++ library just does not allow to do that at all, period. It does not have a "libdl" mode, and even worse, it seems it has hard dependencies into glibc internals.
The only mitigation is to maximize libdl-ization of the game binaries and to use a VERY VERY old set of glibc libs (cf what godot does, at a cost of kludge), because, the libstdc++ will be bound to (symbol/version)s of this set of glibc libs.
> deep cloning of data with simple assignment a = b;
> deep equality checks bool are_equal_by_value = memcmp(a, b, sizeof(some_struct_t));
Errr, wat? We seem to have very different understandings of "deep" in this context. Neither of the above are "deep" in the sense that pointers within the struct will not be followed with their referenced data copied/compared. These are shallow operations.You are correct in the case of nested pointers. I am using primitive types nearly exclusively. Good observation!
I updated the post - really appreciate the attention to detail.
Interesting read, thanks.
> C++ is the obvious choice but the syntax and endless features overwhelm me. I’m not a fan of OOP for game development - class-heavy C++ was not the right move
You can well use C++ with a very modest style, even completely without OO if needed; C++ has many advantages over C for large projects; the type system is more mature and it offers very useful features for modularization and memory management (which were already present in C++98, so it's easy to get a working compiler on virtually all platforms where there is a C compiler).
That makes a lot of sense. Any resources you would recommend? I am getting tired of making C "wrappers" to work with C++ SDKs. Will probably need to bite the bullet on this.
What kind of resources are you interested in? If it's about the 98 version of C++ I can recommend Lippman's C++ primer, 3rd edition (which is more suited from my point of view for the given purpose than later editions).
Where's the source code for the game itself? The github page you linked to lists resources, and a Steam page for the game, but not the code itself. That would be the most interesting thing for HN, otherwise this just seems like a submarine ad for your game.
If you're going to use FOSS code for your project you should release it as such.
That said, congrats on releasing. I play around with C a lot but couldn't imagine sticking with it long enough to finish something bigger than a library.
bool are_equal = memcmp(&a, &b, sizeof(some_struct_t));
Isn't this a bug? Structs are allowed to have padding bytes between bytes occupied by fields, which will have unpredictable ("garbage") values. So a pair of structs with all fields pairwise same can have different padding bytes, so checking equality with this method will give a false negative.Nice, thanks for sharing!
If you don't like C++ because of classes and clutter, would Rust be a viable alternative (asking because I have not much experience in C and in Rust, so maybe if I ever try to write a very simple game engine, I might like to try in Rust)?
I don't understand why you would use C over C++ for anything other than random embedded devices where you have very intimate knowledge of the hardware. I guess you just really hate any hint of a type system?
I'm not suggesting C is better or that I made the correct choice here. My takeaway is that C is "doable" but not the most practical.
All the best! There are many _huge_ projects written in pure C. Don’t let people tell you C is bad for your project size.
Reflections on building and publishing a game using a 50 year old language.