Space-shooter.c: cross-platform, top-down 2D space shooter written in C
github.com
github.com
$ time make
rm -rf build
mkdir build
cp -r assets build/assets
gcc -std=c11 -Wall -Wno-unused-result -fno-common -DSOGL_MAJOR_VERSION=3 -DSOGL_MINOR_VERSION=3 -D_POSIX_C_SOURCE=199309L -o build/space-shooter -g -DSPACE_SHOOTER_DEBUG src/shared/*.c src/game/*.c src/platform/linux/*.c -lX11 -ldl -lGL -lm -lpthread -lasound
real 0m0.437s
user 0m0.288s
sys 0m0.079s
that's what I like to see...Conversely, I recently wrote a simple grafana plugin (my first time using typescript and "modern" web tooling) and I don't know what the hell yarn does but I can compile and play this game several times before it finishes.
Still looking forward to the day cargo will support them.
Having said that, the more AOT compiled languages that join the Turbo Pascal/Modula-2/Oberon/Delphi/Eiffel/Go compile times club the merrier.
> Almost all memory allocations in space-shooter.c are static, with dynamic allocations only used to load image and sound assets when the game initializes. This leads to a nice "programmer peace of mind" benefit that once the game initializes, I no longer have to worry about errors related to allocating or freeing memory.
It's something I also do myself in C projects - in fact in small programs I hardly ever find myself doing dynamic allocations at all. Functions that generate data don't allocate the memory themselves but receive a reference to an output location. That principle extends to larger programs. (It's not novel either, most system level libraries do this)
That's interesting and not what I'd expect at all! Is there documentation you could point me to or did you find that out with some tooling? And is there a better way to update attribute buffers for instanced draw calls?
1. That memory management doesn't have to be scary with a little forethought, at least for programs where you can set a reasonable upper bound on the resource requirements.
2. That it's possible to structure at least a subset of programs in such a way that error states can only be entered during initialization, and that makes the rest of the program much easier to reason about.
During the pandemic lock-downs a musician buddy pulled me into a 24-hour IRC-hosted "wild" compo, which usually sees mostly ANSI/music submissions. I took some boilerplate code from another C game I had shipped and hacked something together in an all-nighter we called SARS:
https://git.pengaru.com/cgit/sars/.git/tree/
https://www.pouet.net/prod.php?which=85496
It uses GLAD, SDL2+OpenGL, and autotools in terms of third-party dependencies. There are some git submodules but they're in-house developed vendored stuff I try to reuse in the interests of saving time and not repeating bugs/fixes across projects.
Being a 24-hour hack it's super rushed and messy in places. But I think it may be interesting to skim relative to space-shooter.c just to see how differently one can structure these things, since it too is quite small and pure C with a smattering of GLSL.
You could probably make it a single file if you used the wonderfully obscure XPM image format for your sprites and assets. It is both a C source fragment and an image format in one! https://en.wikipedia.org/wiki/X_PixMap
BTW I know one can use resource files (.rc) on Windows to embed various resources, but is there an analogous mechanism for linux binaries?
E.G. compare this
static char blarg_bits[] = {
0x13, 0x00, 0x15, 0x00, 0x93, 0xcd, 0x55, 0xa5, 0x93, 0xc5, 0x00, 0x80,
0x00, 0x60 };
with this static char * blarg_xpm[] = {
"16 7 2 1",
"* c #000000",
". c #ffffff",
"**..*...........",
"*.*.*...........",
"**..*..**.**..**",
"*.*.*.*.*.*..*.*",
"**..*..**.*...**",
"...............*",
".............**."
};Optionally, later you move to a custom memory-backed backbuffer allocated using CreateDIBSection(), so you can just set each pixel using the CPU as a uint32_t RGBA value. That allows you to go wild, you can proceed to write your own 3D game engine with nothing to distract you - it's you, the CPU, and the backbuffer memory. (It will be running at software rasterizer speeds, of course - but it should be easy to get very good performance at say 640x480).
It shouldn't take you more than a few hours to maybe 2 days to get the ball rolling, depending on your prerequisites. I initially found the Win32 API to be a bit arcane with its overboarding use of preprocessor and of typedef'ed types (even "pointer-to" typedefs like LPCSTR instead of simply "const char *"). But beyond these superficialities, I find that large parts of it are fairly well designed, and anyway the code to interface with the OS can all be kept in a central place.
Once you're a bit accustomed to these things, maybe afterwards you'll look back and wonder how you could put up with the piles of abstractions all these fluff libraries put on top. And personally, while this approach is not suited to quickly hack up a GUI in a day, I find it's a great feeling to be in control of everything, and this will show in the quality of the product as well.
https://github.com/andyvand/OpenTyrian
AAA PC shoot’em up for PC from 90s.
Also, have you thought about making your own indie game studio to do this full time or on the side if you are not already doing so?
If you go through the architecture doc (which I'm almost done writing), you'll find links to most of the references I used: https://github.com/tsherif/space-shooter.c/blob/master/ARCHI...
Top one, though, would be Handmade Hero: https://handmadehero.org/
// Player constants
https://github.com/tsherif/space-shooter.c/blob/1b97e85cc7f2...
$ space-shooter
FATAL ERROR: Unable to initialize renderer.
PS: figured it out, I had copied the binary to a separate folder but didn't copy build/assets too; doing that fixed the issue. Submitted PR to add note to Readme to mention this in case anyone else faces this issue.PPS: another issue is that once you copy it to another folder and create a symlink to it, it crashes again with the same error i.e. I doesn't resolve to the real path to get the assets location.
(reached level 4, but I can do better)
Metal is an Objective-C API. It is possible to call arbitrary Objective-C APIs from pure C using the Objective-C runtime functions, and Apple themselves recently released a Metal API wrapper written in C++ that works this way. [1] I wouldn't recommend that approach, though. With Objective-C being an almost-pure superset of C, it's a lot easier to just build your code in Objective-C mode and make native Objective-C calls where necessary.
But it's not a big deal to move all the ObjC code into a separate .m source file and expose a smaller and higher level C API.
sudo apt install linux-libc-dev libx11-dev mesa-common-dev libasound2-dev
(x) doubtOn Windows (with Win32+DXGI+D3D11) and macOS (with Cocoa+Metal+MetalKit), things like setting up a window, 3D device and swap chain is just a few lines of relatively straightforward code, so SDL is by far not as useful there as on Linux.
??
https://github.com/libsdl-org/SDL/blob/main/src/video/windows/SDL_windowsevents.c#L1430
https://github.com/libsdl-org/SDL-1.2/blob/main/src/events/SDL_events.c#L403
https://github.com/SDL-mirror/SDL/blob/master/src/events/SDL_events.c#L769
Also, it seems to create a separate listener thread just to pump the messages, which I'm not sure I like.I could imagine SDL supports new event types by requiring the user to create _yet_ another thread, which waits for e.g. network events, then submits them to the SDL main event queue.
> […] written in standard C11 using only system libraries (with system libraries defined as anything included in the C standard library or supported operating systems).