[HN Gopher] Space-shooter.c: cross-platform, top-down 2D space s... ___________________________________________________________________ Space-shooter.c: cross-platform, top-down 2D space shooter written in C Author : ingve Score : 147 points Date : 2021-12-11 11:47 UTC (11 hours ago) (HTM) web link (github.com) (TXT) w3m dump (github.com) | cozzyd wrote: | $ 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. | markus_zhang wrote: | Kudos for not using something like SDL2. I'd be very reluctant to | do so because C is so barebone, but I admire anyone who can pull | this off. | jstimpfle wrote: | I totally recommend to go for it. It's not that scary, just a | few concepts to learn. For example on Windows, you roughly need | to know about HANDLE, HWND, WNDCLASSEX, RegisterWindowClass(), | CreateWindow(), how to write a window proc to handle events | coming from the OS, how to write a simple message loop to pump | those events. To put something on the screen, look up the | drawing context HDC, the RECT structure, and use FillRect() to | draw colored axis-aligned quads. | | 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. | harel wrote: | I love this. My ambition is to become less busy (i.e., retire) to | do stuff like that; fun code, with no obligations. | tsherif wrote: | Thanks! My first child was born last year, and I started a new | job 3 months earlier, so time was definitely not an abundant | resource! But between the stress of all that and the pandemic, | I found squeezing in an hour or two for this project here and | there was one of the few things keeping me sane, and that's | what got me through it. | krapp wrote: | I know it goes against the HH ethos but... you should consider | using SDL2 in the future for projects like this. You'll get | greater (and better tested) cross-platform compatibility, support | for more peripherals, better image support, etc, and not have to | write most of this on your own. | desine wrote: | I think you're missing the point. | krapp wrote: | I'm not missing the point, I'm just saying that in general, | outside of specifically having the goal of not using any | third party libraries, SDL is a good idea for a project like | this. | flohofwoe wrote: | IMHO SDL mostly makes sense on Linux because it hides a lot | of really ugly window system and GLX setup code. | | On 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. | jstimpfle wrote: | Libraries like that might make it easier to get started, | but they tend to limit what you can do. For example, I | recently created a Desktop GUI app that could take inputs | from a networking socket. How do you do that cleanly? There | is often a way around limitations, for example by creating | a separate thread, or by polling every so many milliseconds | (which is ugly and a waste of resources). In my case, | interfacing directly with Win32 without a 3rd party layer | in between, it was easy to create an Event Object for the | socket and am calling MsgWaitForMultipleObjects() in my | message loop. Not sure what's a good solution to do this | with SDL, but why should I even bother... | tsherif wrote: | In a sense, I did use SDL. The source code of SDL, along with | GLFW and Sokol, were my primary references when writing the | platform layers. | mkotowski wrote: | From the description of the project, I would dare a guess that | the intent of this project was to go as self-contained as | reasonably possible: | | > [...] written in standard C11 using only system libraries | (with system libraries defined as anything included in the C | standard library or supported operating systems). | mahesh_rm wrote: | Can't build on mac, anybody figured that out? | tsherif wrote: | For now, it's Windows/Linux only. I hope to write a Mac | platform layer eventually. I just don't currently have a Mac | machine to work on... | ryandrake wrote: | Unfortunately, the Mac port would likely involve calling into | (or out from) a little Objective-C, unless you used the | ancient Carbon APIs for graphics and window management. | OliverM wrote: | So it's not possible to interface with Metal or the | Accelerate framework on macOS using pure C? I've found some | outdated C wrappers for metal but nothing up to date, and | wondered if something in the later versions assumed Swift | or Objective C library consumption... | tsherif wrote: | Ack! Right I hadn't thought of that. I'll have to decide if | I'm ok with breaking the "written in standard C" constraint | on this project... | sjmulder wrote: | There's no platform code for macOS yet. (It's a goal of the | project to not use cross-platform abstractions) | aninteger wrote: | Does Mac not ship with X11 anymore? | sjmulder wrote: | Cool. Also nice to see a well written ARCHITECTURE.md. I like | this bit about dealing with memory: | | > 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) | b20000 wrote: | very cool project. i wonder if the libraries used in this | project also offer the guarantee of static allocations only / | dyn allocations only at startup. i've been using the JUCE | toolkit for a project the past 10 years and it has allocations | all over the place. need 16 bytes? malloc. need to concat that | string? a few mallocs for handfuls of bytes. i am stuck with | it, so i have had to override malloc and friends and build my | own fixed block size allocator. very annoying to find out you | are only doing static allocations but then all the libs you use | together amount to 3000 malloc calls per second. i even found | out that a call like glBufferSubData will call malloc or | related functions. so doing that on each frame seems to be a | bad idea as well. | tsherif wrote: | > i even found out that a call like glBufferSubData will call | malloc or related functions. so doing that on each frame | seems to be a bad idea as well. | | 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? | b20000 wrote: | I did not look up any documentation to verify this but | since I did override all allocation functions I could keep | statistics of how many calls I was getting for each | function, and when I commented out the glBufferSubData | calls I could see my stats drop. The contribution was | somewhere around 300 calls/s I think... this is with the | panfrost driver with gallium/mesa on an ARM platform. My | plan is to use persistent buffers which are memory mapped | once. Hopefully that will get rid of these allocation | calls. | tsherif wrote: | Thanks! This was a revelation for me on a few levels: | | 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. | lfowles wrote: | I practice something similar, I've statically allocated the | physical RAM modules when I built my computer. O:) | agys wrote: | The scroll-speed and the graphics remind me of the magnificent | Xenon II Megablast. | | https://www.youtube.com/watch?v=v9nD9DQwd80 | malkia wrote: | Bitmap Brothers had such unique art style! G.O.D.S.! | kingcharles wrote: | And the amazing music by Bomb the Bass: | | https://www.youtube.com/watch?v=bWbJeUEKzc8 | tsherif wrote: | Hadn't seen this before, but that's a flattering comparison. | Thanks! | techjuice wrote: | Nice work you have here, I will go ahead and ask for the curious | ones what is your background and what would your top recommended | books, videos, and additional educational resources that you used | to learn C and game development in C? | | 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? | tsherif wrote: | Thanks! | | 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/ | swalls wrote: | Not OP but I'm currently writing a game in C: just throw | yourself into it. The language itself is minimal enough that | you probably won't need much guidance other than looking up | library functions and with modern tools like the various | sanitizers in clang and valgrind it's hard to go _too_ wrong. | larodi wrote: | Loving the fact that OOP here ends with the structures and no | heavy java-style class bloat. The code is very well ordered, and | to my surprise - not that long! Which speaks of proper | architecture. Good work, good example! | tsherif wrote: | Thanks! I definitely tried to focus on only using as much | abstraction as was actually helpful in getting it to work the | way I wanted it to. | pengaru wrote: | Always a pleasure to look at someone's self-contained small game | written in C, especially when the source is relatively well | organized. | | 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. | tsherif wrote: | Sweet, thanks for sharing! I'll definitely take a look. | desine wrote: | Oh wow, it's all done using no 3rd party libraries too. Gonna | bookmark this just to browse through later | rwmj wrote: | The title led me to think it might be a single file, but still | this is good stuff. | | 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 | tsherif wrote: | That's an interesting idea. Probably won't change the format | for this project, but something I might consider in future | ones. Thanks for the tip! | MLafayette wrote: | > The title led me to think it might be a single file | | You think Node.js is a single file? | mkotowski wrote: | What are upsides of using this format instead of a more generic | array of pixels (something like output of imagemagick to .h | files or PPM files)? | | BTW I know one can use resource files (.rc) on Windows to embed | various resources, but is there an analogous mechanism for | linux binaries? | yissp wrote: | The examples in the wiki article show the difference pretty | well. XPM lets you sort-of see what the image is just by | looking at the source file. | | 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", | "**..*...........", "*.*.*...........", | "**..*..**.**..**", "*.*.*.*.*.*..*.*", | "**..*..**.*...**", "...............*", | ".............**." }; ___________________________________________________________________ (page generated 2021-12-11 23:00 UTC)