[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)