Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Writing a game engine in pure C: The Graphic Initialization (prdeving.wordpress.com)
279 points by buba on June 5, 2019 | hide | past | favorite | 120 comments


As a tutorial series, I think using plain C is a cool approach and will help illuminate what's going on under the hood and what parts of the engine are really essential.

But if you yourself want to write your own game with just you or a small team of like-minded people, I would highly encourage using C++. As long as you don't have too many cooks arguing about which C++ features to throw into the pot, you can pick a subset of C++ that isn't much more complex than C (which is already more complex than most realize) and you'll get a much cleaner, safer language. C++ has saner rules for implicit type conversions, namespaces, overloading, and a cleaner notation for dynamic allocation. Those alone make it a sufficiently "better C" to be worth using in my book.

Going farther, even if you don't like "object-oriented programming", I think classes offer modularity and encapsulation features that make them worth using, even if you never once write the keyword "virtual" or use subclassing. (Fun fact: the first version of C++ did not have virtual methods!)

I like C and enjoy programming in it, which I've done for over 20 years. I've written a successful open source project in it and am writing a book that uses C as one of the implementation languages. Even so, I generally only use straight C if I'm writing a library that I want C users to be able to consume. Otherwise, I think C++ contains any number of "better C"'s within it, and it's mostly a matter of choosing which better C you want.


”...it's mostly a matter of choosing which better C you want.”

For a beginner that can make things more difficult: in addition to the actual thing you want to learn, you need to first become a capable curator of language features from the past 35 years.

JavaScript today has the same problem: you can’t just start writing a web app because two lines into a tutorial you’ll be barraged with “So this is actually an ES2017b.71 feature that we’re enabling using babel-ts-flooginator, therefore you also need TypeScript and that means you need to...”

C feels clunky today, but writing more code in exchange for not having to curate the language can be helpful for learning.


This is such a great observation.

It makes me wonder if this can be solved technically by published curated language subsets. Racket does this by supporting a bunch of different dialects, specifically to make it easier to teach [0].

One good do something similar for other languages. Step one is probably just writing a doc that says "Here's a standard subset of C++ we call Blah. These are the features it uses and these are the ones it doesn't: ..."

Then you could add tooling so that it will warn you if you use a prohibited feature.

Of course, this just pushes the problem up a level: now a new user has to know which curated sublanguage to use. But that's arguably simpler than doing it on a per-feature basis. At least they can just order a combo instead of having to pick a la carte.

[0]: https://docs.racket-lang.org/drracket/languages.html


> It makes me wonder if this can be solved technically by published curated language subsets.

I'm biased, but I generally find the Google C++ Style Guide to be a good curation of C++: https://google.github.io/styleguide/cppguide.html

I generally stick to it in all projects I write. The only exception is that in a few cases I'll use features that are disallowed in Google C++ primarily for historical reasons, most notably exceptions: https://google.github.io/styleguide/cppguide.html#Exceptions


The problem with exception-less C++ is that new, delete, ctors, and dtors become timebombs when they fail in a way that would have generated an exception.


I'm not sure why you were downvoted. I almost exclusively write exception-less C++ (compiling everything with "-fno-exceptions"), but correctly handling errors in constructors and destructors is absolutely an important concern. It's not hard to do, obviously, but it does require forethought and heavily encourages delegating complex logic to other parts of the code.


If new fails your program crashing is probably going to be the only outcome anyway, might as well fail from an unhandled exception.

And if you're putting things that can throw exceptions in your ctors or dtors you're probably already writing code that is a timebomb


dtors that throw come with their own share of problems: If they do so while another exception is in flight (dtors are still called for objects when the stack is being unwound(?)), C++ crashes the process.


For our code base we avoid exceptions, except when due to awkard workarounds, or kludges mostly due to some API that's written in a way that you need to communicate with it with exceptions.

That to be said, even if you don't use exceptions in C++ - you must write exception safe code - e.g. as much as possible no manual "begin"/"end" but wrap things behind RAII, such that "end" is still called, in case of exception. Hence allocation through unique_ptr, shared_ptr, etc. is preferred over new/delete.

Because you never know the function you are calling whether deep down it won't throw an exception...


There are a few that people use! Here's a simple one with links to a few others: https://gist.github.com/bkaradzic/2e39896bc7d8c34e042b

And some people choose to use more comprehensive documentation, like Google's C++ style guide, at the cost of imposing more restrictions that are largely in place for corporation-specific reasons that smaller personal codebases don't necessarily need to regard.


Choosing a curated language subset is exactly the "how do I curate the language" problem being referred to. The solution is not to subset a language, but to refuse to superset it. Stop building DSLs and frameworks and domain-specific abstractions and just stick to a flexible core.


>JavaScript today has the same problem: you can’t just start writing a web app because two lines into a tutorial you’ll be barraged with “So this is actually an ES2017b.71 feature that we’re enabling using babel-ts-flooginator, therefore you also need TypeScript and that means you need to...”

That's not a problem with javascript, it's a problem with the development culture, its schizoid relationship with the language and its obsessive need to be "cutting edge." You can, absolutely, just start writing a webapp because vanilla JS and even jQuery still work perfectly well. What you can't as easily do is find tutorials that don't assume Javascript has to be compiled from another language and pushed through a complex tech stack before you can even approach it.


I was mentoring these kids writing some code in C++ and I saw they were using all this syntax and functionality that was familiar from other languages, but that I had no idea was in C++. You see I used to program on games in C++, but like twenty years ago. I have learned a lot of languages since then, and don't really have a reason to go back to C++ although I liked it fine as a language. But it is possible to write C++ now that is really alien to me.


yep, I choose C89 for this project because there's no Object/Prototype/Class/overloaded bullshit, you get what you wrote and no more, i think it's easier to understand than any other high level language where you have to use obscure things by design.

Actually, this tutorial is not just about "building a game engine in C89", it's more about learning the concepts behind game engines, the language and the code is just illustrative examples.


I like C, but you definitely don’t “get what you wrote” in any meaningful way. You absolutely have to understand all sorts of nuance about undefined behavior, the size of primitives on various architectures, etc. I’m not advocating for C++, but pointing out that C is far more error prone and surprising than many HLLs. And this doesn’t touch on the regular error prone things about C, like memory management, array indexing, etc.


yep, i said "get what you wrote" and it's true, if you want your iterations to not get out of range you have to do it yourself, if you want to make a dynamic array you have to do it yourself and if something is not working as expected means that you wrote something wrong, that's far from the opaque flexible behaivour on javascript, f.e.

I meant that i think is easier to understand C than other languages cause you see all the flow without weird library stuff, maybe i'm wrong, dunno :(


Like I said, I wasn't commenting about out-of-range or those other things, I was commenting about the surprising undefined behaviors. :) JavaScript and many other HLLs have their criticisms, but they are generally far less surprising than C. In particular, there are languages like Rust and (to a lesser extent) Go which lack many of the dynamic runtime features of scripting/VM languages and behave very similarly to C, but with far fewer footguns (e.g., no architectures with 9-bit chars!).


Although this was mostly a decompiled project, I really appreciate the architecture and code style of https://github.com/OpenTTD/OpenTTD which seems to use OOP minimally and more c oriented code.


Absolutely, i stick to C89 in this serie for clarity as you said, knowing what a game engine does under the hood is the only point here and C is a self explanatory language by design (even if it can be "tricky" sometimes), Im also doing tricky things with it (not that tricky anyways) like the method-like function pointers in the structures that can be "obscure" for those who only work with high level languages but i'm trying to explain everything with detail.

As i said previously, i'd never do a profesional game proyect (or even a hobby one to be sold) with C89, it takes lots of boilerplate and it feels like reinventing the wheel over and over. I'll have to code a hash map approach, a growing array (already did), a "garbage collector" in C89, things that you either don't need or have in the C++ Stdlib, so yep, you are absolutely right.

Also, i enjoy C :D

Maybe after this i could do the same with C++ macroprogramming


I like your approach, mostly because it's bottom-up, and because this is an excellent opportunity to illustrate common pitfalls. Those pitfalls can be identified using sanitizers (-fsanitize=undefined,leak,address,thread etc). With the correct CI setup you'll have a safety net, where sanitizers provide precise motivations why one approach works, and another also works, but at risk of undefined behaviour.

I have no opinion on use of C89 instead of C++, since that's just an implementation detail. Also, for your purposes, it works to your advantage, since C is basically the white-box of C++.


> C++ has saner rules for implicit type conversions

I say this partly in jest, but once I have wrapped all my primitives in structs C has a perfectly reasonable rule for implicit type conversions: "don't".


As an embedded firmware developer, I really wish there was a way to outright disallow implicit type conversions in C source code. You'd have to exclude libraries, but I'm creating "typedef enum" to show what I'm doing AND help make sure I don't somehow screw it up. If all of those typedefs are interchangable with each other (and ints and chars), I lose out on part of the functionality.


To get nominal typing in C, wrap things in a struct. There shouldn't be any performance overhead (the generated code should often, if not always, be unchanged), and the boilerplate can be manageable (and you can unpack things locally when it starts to get too messy).

See https://dlthomas.github.io/using-c-types-talk/slides/slides.... (... which I should really turn into a blog post or something)


Some ABIs pass structs containing a single element differently (and in a less performant way) than primitive data types.


You should always be aware of the details of whatever platform you're targeting, to be sure.

That said, can you name a few that work this way? If I've worked on such a platform, it's been a long while.


Apparently ARM64 does this for return values. I remembered the `#[repr(transparent)]` proposal that precisely tackled this problem for Rust: [RFC 1758](https://github.com/rust-lang/rfcs/blob/de0917dca549694fffbe3...).


Thanks, I'll dig in! Is this limited to floating point? That doesn't surprise me nearly as much.


Haha, yes. I've considered similar things before too.

Part of the problem then is that working with any standard library functions is a huge pain. You have to manually "cast" back and forth all the time.


Any library that's not on board needs to be wrapped, for sure. Fortunately you can do it in just the prototypes if you keep things ABI compatible. It fits well with the practice of decorating functions with empty structs, which is always ABI compatible (... in C, with mainstream compilers. In C++ it is explicitly not ABI compatible, sadly).

It's extra important, in writing this kind of C, to pick a granularity of types such that they help you make the distinctions you need without drowning you in casts. And I'm not at all sure such a granularity always exists. It worked out very well on the (greenfield) project I built this way, though.


I'm a (primarily) C developer who would love to use C++ this way. I have some C++ experience but not enough to know which elements I should include or exclude. Or even how to begin effectively deciding that.

Can you point to any resources I can use to learn?


Speaking from my own very limited experience, I think C++ can be pretty nice if you basically write straight C but borrow from the C++ STL. Smart pointers (std::unique_ptr, std::shared_ptr, etc), collections (std::vector, std::unordered_map, std::set, std::stack, std::priority_queue), and stuff like std::tuple and std::optional really make C feel less clunky without getting too gross.


Ha, I was just thinking about where to find some STL-style collections earlier today and was lamenting not knowing having a great lib in mind.

Thanks for the suggestion! Seems like a good place to start.


Glib (https://developer.gnome.org/glib/2.60/glib-Miscellaneous-Mac...) is a great STL like library for C. It's got the basic data structures, a good API and plenty of utilities. My favorite is g_autoptr (https://developer.gnome.org/glib/stable/glib-Miscellaneous-M...) for deterministic cleanup. There's enough to make C a reasonably pleasant scripting language.


If you want to know what a reasonable subset of C++ looks like don't hesitate to check out the source code for Doom 3.


Which contains: #define private public #define protected public


C is a fine language which many game/graphics developer prefer over C++. More features is not always better.

Here are some reasons to choose C: - longevity. The language never changes. C++ does a good job, but not as good as C.

- portability. It runs everywhere.

- readability. Its a simpler language to read and debug.

- size. You can have the whole language memorized, and know how to write it very well.

- prefer C community over the C++.

- avoiding pandoras box. Once you allow one C++ feature, its easy to add another.


I’d like to know which book is that.



Interested people may also be interested in Handmade Hero[0]. The game is being developed — from scratch, engine and all — in real-time and streamed by Casey Muratori. It’s cpp technically, but he uses very little and sticks to mostly C if I remember correctly.

[0]https://handmadehero.org/


Handmade Hero is an impressive endeavor, but by now it's got over 500 1-2h sessions. That's a lot for anyone to catch up on.

Have there been any efforts to write episode summaries/articles on the techniques he demonstrates?


Every video on his website has pretty great annotations placed on top of them which describes every section of every video, and all the questions at the end of session QA.

Go to https://handmadehero.org/watch and scroll down the previous episodes section.

Also all the episodes aren't as long as they look, a large chunk of the time is on the QA at the end. That said it is a huge investment at this point, but definitely worth it. Some of his high-level ideas are great, and I've learned a lot.


I'm not following Handmade Hero, but out of curiosity: why does it matter how many sessions are there? It's not like watching Game of Thrones where people can spoil it for you. You can follow the tutorial at your own pace :)


There is an excelent episode guide at https://hero.handmade.network/


Handmade Hero is not at all a demonstration of how one would actually approach making a game in C/C++, though. In real life, you would use existing libraries instead of reimplementing everything from scratch.



I'm well past my fifth iteration... does it count as an iteration if you never actually finish a version before starting it over?


sad, but true


handmade hero is a piece of art, upvote


Starting with state management in the first post is a different approach I wouldn't normally expect.

That being said I don't know what it is but I prefer writing games in C. I experiment with higher level languages and use them for rapid prototyping. However, and maybe its simply nostalgia, I always come back to C.

And it's not even my favorite language by a long shot. For practically everything else, save for systems programming, I prefer languages like Common Lisp and (more recently) Haskell. Imperative programs are notoriously difficult to comprehend and maintain but there is something about game programming specifically that C leverages which makes it a good fit for game engine development; the mix of pointers and machine-sized types perhaps? I'm not sure. But it works really well.


I remember writing a blog post years ago about how I enjoyed C and Clojure, apparently two extremes, equally.

My current conclusion is that we just enjoy problem solving within mental frameworks, and each language family gives unique mental frameworks that come with different challenges.

Now that I've slowed down life and am planning in years instead of weeks, I think I would absolutely enjoy (and I think my kids would too) making a new video game from scratch with SDL in C.

The only difficulty is, every time I try this, I keep running into weird issues that only I seem to be having and can't find a solution to online. For example, the last time I tried making a lemmings style game in C with SDL, I ran into weird framerate issues where for apparently no reason or pattern, it would go incredibly slow sometimes.


C gives you all control over your machine, there are no obscure libraries, grabage collector or unexpected stuff, if something doesn't work as expected you know you have fcked it up. I'm using C here cause 1st, i like it a lot, and 2nd, i think it's the clearest language out there, you can follow the code execution from start to end and "almost" know what's happening, it doesn't have function overloading, obscure scopes, garbage collectors or automatic weird typing.


Perhaps, but C is unsafe both in terms of types and memory (Which has to be considered if choosing C for a project)


HN users tend to parrot the safety line a lot because most people are working on web applications that store somewhat important user data, so gaining access to a remote machine due to a buffer overrun issue is catastrophic, but in games, especially in single player games, safety is less of an issue or even a non-issue. With a multiplayer networked game, cheating, attacks on servers and attacks on other players clients are a concern, but in a game that is entirely or mostly single player the worst outcome of unsafe code is that it crashes to desktop.


Ignoring the almost barbaric lack of abstraction in C, you could avoid a whole load of these bugs and make your customers not want your head on a spike at next to no cost by using a different language.


Nobody's complaining about the barbaric OS and network stack written in C. The safety nazis' bleating means little if they can't produce a viable alternative. Where are the high performance OSs written in Ada or whatever next big thing is?


What? Operating systems are big projects and the tools to write them safely (and open source) haven't existed for very long, so it's a question of manpower and effort not technology. C has almost no performance benefits these days, either, due to advances in compiler optimization; if anything it can be less as other languages can provide more static guarantees to the compiler to use when optimising.

That network stack written in C gave us heartbleed for example. If C arrays were bounds checked by default (e.g. they carry around their size), this could've been easily avoided at no runtime cost.

How can you honestly complain about people wanting more safety in software? If it's speed ("high performance") then you're probably implementing yourself in C (e.g. Bounds checking) something that is built in either at runtime or even statically as can be done in some cases.


What do you propose? Replacing the system stack with Ada?

Modern successors to C, such as Zig, and modern successors to C++, such as Rust, are still too young to be used for serious re-implementations of the lower layers.


Modern C++ would be a huge improvement to start with.

Rust is probably there, if under specified. D is definitely fine for writing operating systems (Question being whether to use druntime or not).

I know nothing about Zig, beyond having had a look and not being hugely impressed.


true, i'd never do a C89 game from scratch as a profesional project, but this is not a profesional project, it's more like a hobby-academic approach to the lower level of game engine design :D


Indirection, pointer casting, and the C standard library actually make things quite opaque in practice. Do you sbrk your own memory or use malloc? Malloc may not be as mysterious as GC, but it’s still a runtime-managed resource.


You can replace malloc with your own implementation. Just have a big static array to dole out bits of memory. The loader or startup code will prep it for you.


I have some recent experience with doing game programming in C and while I think the experience was overall positive, I'm not sure that I would do it again unless my aim was portability.

I found that when my project grew to a certain size, I started running into many cases where I needed many implementations of some base type, the kind of pattern that lends itself well to classes and simple inheritance. This pattern is totally 100% possible to roll yourself in C, but requires a bunch of boilerplate each time and I found that every time the implementation came out slightly differently, or that I had to go back and rewrite a bunch of code because I needed one more layer of flexibility than I anticipated.

I also really wish templates were in the language. Maybe not to the C++ level of flexibility, but at some point, you're going to need a dynamically allocating array or hashmap of a specific type, and your choices are either going to be *void or some macro thing that makes your eyes cross.

Also, build tools in 2019 are still kind of bad. I use CMake and try to do things "the right way", and I still run into trouble occasionally, mostly surrounding external dependencies and generation of files outside the scope of the C compiler. It's a shavable yak, but it's still a yak.

Still, the lack of mental overhead and the much faster compilation times compare favorably to C++. But if portability wasn't a goal, I'd seriously consider an alternative like D, Zig, or Nim next time.


>I needed many implementations of some base type, the kind of pattern that lends itself well to classes and simple inheritance.

Inheritance is an anti-pattern, you can accomplish the same with composition.

>you're going to need a dynamically allocating array or hashmap of a specific type, and your choices are either going to be void or some macro thing that makes your eyes cross.

I don't understand, why would you need a *void for a specific type? If you meant heterogeneous types, you can use a typedef union.


In all my gamedev I've found that the best structure was how Lua does meta-tables.

You can then use that simple mechanism to implement inheritance, component based design, message passing or whatever structure makes the most sense for your game design. I'm personally tend to lean towards component based for reusability and performance but that's by no means a hard-and-fast rule.


interesting, i'd suggest you to follow the blog, iv'e already implemented something like a dynamic array in the first post and will have to implement some other "modern" things in the near future, it's awesome what you can do in C89


> Imperative programs are notoriously difficult to comprehend

Which programs can most programmers read and understand?


Very few in their entirety I'm sure.


I'm not really a fan of C for games just due to exploding complexity the moment game logic enters the picture. While the language is fairly clean, I find that it gets messy really quickly once the number of structs and functions starts to take off, and then I end up flipping things over to a reduced set of C++ features.


I agree. I still use C a lot, but I often couple it with luajit for scripting.


Reading the title my mind immediately went to

  mov ax, 13h
  int 10h
I'm old.


Oh man, the memories. It was such a revelation discovering how much faster it was to POKE pixels directly into memory in QB rather than use the PSET built-in. And then much of that knowledge was directly transferable to Turbo Pascal and later C.

it really boggled the mind that it was possible to blt a sprite in rows rather than a pixel at a time, and under the hood, the computer was actually copying 4 bytes per instruction. Anyone else remember implementing the 320 row offset as the sum of two bit shifts? By the early 2000s, I seriously doubt that was any faster, but we all did it anyway for "performance".

And don't even get me started on palette hacks— redefining the 256 colors into bars of dark-to-light runs of a single colour, so that you could do smoke or halo effects by just shifting every affected pixel by one or two in either direction.

I don't know if he's around these days on HN, but a shout-out to Mark Sibly (Blitz) for bringing a lot of this stuff to the QBasicNews forum back in the day.


y << 8 + y << 6 + x

(I think the bit shifting and addition trick would still win over an IMUL.)

Edit: also, wasn't there a way of (ab-)using LEA to do some of this?


It seems clang-7 agrees with you— it compiles the multiplication down to shl+add, so the perf is completely identical, see:

http://quick-bench.com/CdxqB_qPD3VS5H7igB31FFoqLLo


But you are also right that it is not a huge difference :) In no-optimization mode, it keeps the imul in for Multiply. the result is 1.2477 vs. 1.2668, relative to Noop.

The site is really cool, thanks for pointing me to it!


Interestingly, gcc-8 does some really weird stuff. Its version of Multiply is 1.4 times slower than its version of Shift, but according to the disassembly, the Shift implementation is actually using an imul, whereas the Multiply implementation is doing a lea/shl.


Brought a smile to my face. I see this as a shibboleth for the oldschool coders.

For those missing the joke, that's the assembly language commands to switch to VGA mode from DOS. Set AX register to 13 hex and call interrupt 10. That gives us VGA graphics mode 320x200 pixels! Related: In the 1990s Michael Abrash via Dr. Dobbs Magazine, introduced the world to "Mode X" @ 320x240, which had the advantage of square pixels. That series of articles was responsible for my long-standing subscriptions to Dr Dobbs Journal.


320x240 was one of the benefits, but the more important part was that it allowed you to use all the memory, double buffer, use split screens (for HUDs) etc. Addressing became more complicated, though. (And IIRC, Doom ran in 320x200 Mode X, for some design/monitor reasons)


If that brought a tear to your eye, you might enjoy:

http://www.pouet.net/prod.php?which=53816 "Puls" by Řrřola, 256b, MS-Dos

Along with this interesting explanation https://meatfighter.com/puls/ of what's happening with the "binary-search" raytracing, lattice-effect, and tricks to make it fit in 256b.


I'm not that old, I would have used a higher level C function to call the bios

(And use VESA modes like 640x480x256 - thank Deity for DJGPP)


I share the same feelings with you and all the people that replied. That's why some days ago I started a set of single-file libraries for opening a window and pushing pixels in the same fashion: by writing directly to memory (to a RGB or Indexed (256 palette) frame buffer).

The goal is to have multiple implementations, in different languages and OSs. For example, right now I wrote (partial or full) support (in C) to:

PNG, windows GDI, linux X11, linux OpenGL, linux Framebuffer and Javascript Canvas

https://github.com/feiss/zero

(I've just started! expect it to be incomplete, broken, faulty and buggy!)


A starting point... after which the countless "out_port" incantations begin.

All just to get to unchained mode (aka Mode-X) with hardware scrolling & split screen, 256 kB VRAM, VGA latch fills and VRAM to VRAM latch blitting. It was amazing to be able to set or copy four 256 color pixels by writing just a single byte.

Of course VGA latch blitting didn't make any sense on 486s, but on 8088 it could be almost 4x performance boost — 256 colors with similar CPU load as CGA's 4 colors! At least as long as you needed to blit something to an x-coordinate that's divisible by 4...


Same here. Felt cheated when I saw SDL mentioned :)


I must have done this in DOS like, a million times!


So much memories! Thank you!

also old!


Ah the days.


Personally, I prefer GLFW over SDL:

https://www.glfw.org/

GLFW just goes from zero to OpenGL context ASAP, plus input events. With SDL I found that their drawing primitives are too limiting for my needs, and if you want to do any custom drawing at all, you need to abandon their drawing primitives entirely and just write GL codel. At that point, GLFW makes more sense.

SDL does have a few other nice things like audio support and text rendering, which you'd have to figure out separately with GLFW, but to each their own.


Yes but SDL supports mobile and consoles and with either one, you can get an OpenGL or vulkan context pretty easily. Audio is honestly the other big plus (do you really want to navigate windows audio driver handshakes or pulseaudio...).


my first idea was to use glfw indeed, but i took SDL cause it's easier to set up and it gives me audio, image loading/parsing and input.

The project will not stick to SDL anyways, the idea is to use it as a high level API, not to build a game with SDL. I would like to use this engine for PSP also, and i'll have to take SDL out and put osLib in. problems for future me...


I gave up on SDL2 for graphics after looking at the code that would be necessary to apply the simplest OpenGL shader.

The alternative path for C++ is SFML, which has sensible abstractions for most of what you need.


SDL2 has much better support for Android though.


are there any concrete, tangible advantages of glfw over freeglut? I see the glut api as the paragon of usefulness and simple elegance, and I'm really curious how glfw can be any better than that.


I haven't used glfw, but from the docs it leaves you in control of the main loop, whereas glut takes that over and only offers you callbacks. When I used glut (many many years ago) that was sometimes annoying.

glfw doesn't seem to have a function to draw teapots though, which should really rule it out as a serious contender.


legacy glut does not allow loop control, but freeglut does


There are plenty. Freeglut not being in active development for a while, AFAIK, will not support high-dpi devices (where the window resolution and surface resolution do not match), or newer drivers for different input devices. Freeglut locks you in to OpenGL instead of other rendering APIs. Getting multi-monitor setups up and running is harder. Joystick support is paltry to nonexistent.


For more inspiration I'd check out the Quake engine source. It's quite the masterpiece. There's also been some modernization over the years by LordHavoc of the DarkPlaces engine https://icculus.org/twilight/darkplaces/ - I'm constantly impressed how well the HD remaster on DarkPlaces looks on modern 4k hardware.


The Wolf3D source is probably an even better start. Much simpler, the bad part is that it's 16-bit Borland C.

Andre Lamoth's DOS game programming books are a good read as well, he used 16-bit Microsoft C. Sprinklings of assembler in both.


This looks like a lot of complexity up front with no foreseeable payoff.

Why are we implementing a whole dynamically allocated stack for states before doing anything domain specific?

How many states could you possibly be expecting to have in a full game? 3? 12? 100? The examples of states were like the menu, action screen, and pause screen. So it sounds like very few. Drop the realloc'ing and free'ing and just statically allocate N states and be done with it. Save this complexity for something that really needs it.

Plus are you going to free the stack any time other than when you quit the app? I doubt it. The OS will free everything for you when you quit so there's no reason to waste time on that either.

The code so far looks like mostly a waste of time.


Theres also wdk which can give you open gl es/desktop context and and this is separate from window creation so you can also create a context for headless rendering. Supports window and linux.

https://github.com/ensisoft/wdk/blob/master/sample/triangle....


uh, interesting....

C++ tho, but i could port it, i'll give it a glance


Right now I'm heavy into C++, especially C++17, but mostly due to the current project I'm working on (tools engineer @ gamedev)

20 years ago, my first project was being part of 4 people team porting Metal Gear Solid from PS1 (PSX) to PC. This is when I saw how good written "C" code could be done. Even if it had only japanese comments in the code (understandable).

The pure and simple structure, where most of the functions had TCL-like interface:

int ACT_Work( int argc, sometype* argv ) { // bit of argc/argv parsing Do_Work() }

then above was exposed to a TCL-like scripting language, used for setting up level objects... We are talking about 500-600kb of memory used by the main executable + 300-400kb of 'overlay' memory where "shared, e.g. dll/so-like" code gets replaced depending on what objects needs to be in the game (Mentioned "overlay" as this was very well established practice back in the DOS days).

Things really were plain and simple. API-s too. It was so easy to grasp what's going on.

Then again, since this was running on what looks like really limited machine, it had to do only such and such. No fancy animation controllers, advanced physics, collision, etc. etc.

at some point, especially if it's a content creation tool (think Maya, or any Autodesk product, Houdini, etc.) then you need to be able to handle tons of "nodes", "entities", meta-data, etc. - but when it comes to a game, even nowadays - pre-cooking gets you there, and quick deserializing - either piecewise, or whole sections, and then pointer fixup.

Point is, is your game, and engine meant to be content creation tool too?


e.g. "C" is still valid choice, if your data is well organized, known, and no additional heavy modifications are need - e.g. you don't change much - mostly load "pre-cooked " data, play it, keep few (okay dozen, maybe hundreth, even thousand) variables around and that's it.

Once you get to the point of needing various tweaks, modifications, changes, more advanced UI, etc. - you may need better suited language, because malloc/free won't cut it for you.


This is super cool, well done OP! It looks like you've got some pretty in depth and interesting articles around the rest of your site too. Big props to you and everyone else who puts themselves out there and works to create something that helps others.


I finished my first game-ish C/SDL project a few months back, and found it to be a very instructive experience. It compiles to WebAsm so you can fiddle with it here:

https://nematode.farm


Is there a way in modern operating systems to draw pixels directly in software without using any external libraries (Ala SDL or Windows Graphics)?

Like just switch mode of a context and draw pixels as you want?


Seems like it is going to take a long time to get to a fully functional game if this is the second article and they're only just calling SDL_CreateWindow.


Well, the point is to learn how to build an engine with modern capabilities (those that you are supposed to use with C++ like namespaces, methods, ECS architecture, networking...), SDL is just a tool to not having to write the whole window/input/media/opengl logic from scratch. I could write a SDL tutorial and make a game in 3 hours; it's not as interesting i think, for me the objective is not to write a game but to explain how engines works and desmitify them a little :S

But you are right, it'll take a daaamn long time. I've the third and the forth parts almost ready, covering the engine architecture itself, the private scene scope and the image->texture->sprite stuff but i havn't started yet with ECS, fs, configurations, physics, networking... this field is really extense.


One post a week is about as fast as most people can go if they are into nuts-and-bolts technical topics and they are trying to juggle work and life and other demands on their time. That's about the best I could ever manage when I used to be blogging as I learned DirectX. And that was using C#, so you didn't have to boil the ocean and implement all your own fundamental data structures as you go (see the previous post in this series).

It's four and a half years into Handmade Hero at this point :-)


Just want to explain how things works, i don't know if i'll ever finish this proyect, but if i don't i want the things i did explain crystal clear, i want every concept well presented so every single post in the serie can be a piece by its own and you can learn something. I'm now trying to write two articles a week but it's hard, coding C89 is not fast, there are a lot of cavities and undefined behaivours xD


such is life. that is the reality of making games without an engine.


It's pretty simple to get a working window in SDL, though. That part doesn't need to be complicated.


[flagged]


Could you please not post unsubstantive comments to HN? We're trying for a bit better than internet default here.

https://news.ycombinator.com/newsguidelines.html

You might also find these links helpful for getting the spirit of this site:

https://news.ycombinator.com/newswelcome.html

https://news.ycombinator.com/hackernews.html

http://www.paulgraham.com/trolls.html

http://www.paulgraham.com/hackernews.html


This is moreso "how to start using SDL2".

These tutorials exist and are really well thought out here: https://lazyfoo.net/tutorials/SDL/


this is not about SDL, it's just an easy way to open a window, import assets, handle events and reproduce sound.

Would you like to see a tutorial about how to do that in c89 compatible with windows, mac, Gnu/linux, consoles, etc? it'd take half million lines and 3 years.


This is an awful SDL engine example. I've made rendering engines before. You should for example have a second windowless openGL context with its own thread so that you can be sending drawing commands at the same time you are updating GPU ram buffers. Generally the main thread is required to be handling input (due to cross platform requirements) and should only be doing that. Everything else including file loading should be running in async threads. It would require you to be a competent programmer though.

There are better SDL tutorials from the 90's.


That crosses into personal attack, which we ban people for, and name-calling, which the site guidelines ask you not to do.

Would you mind reviewing https://news.ycombinator.com/newsguidelines.html and taking the spirit of this site more to heart? If you know more than others, the thing to do here is not to put others down, but to offer some of what you know, so we all can learn. Or you can ask about why they did X rather than Y. Perhaps they also know quite a bit, and chose X for an interesting reason.


[flagged]


"It would require you to be a competent programmer though." is not blunt criticism. It is simply unkind and unnecessary. You can be blunt without being nasty.

While I do sometimes think reactions on HN could use a little "lighten up already", I agree with 'dang' here. You definitely crossed the line in this case.


Although multi-threaded engines are now standard, this model adds complexity and is overkill for most games beginners will make (and many commercial indie titles). There is also no reason not to start with a single threaded renderer and move to multi-threading in a later article. Right now it isn't even past creating a window...

Honestly, if you're going to be so critical you should suggest the tutorial should use Vulkan instead of OpenGL, but wait neither of those support all gaming platforms so you have to write a low level API wrapper for Metal and DirectX12 as well, then write a shader translator... Etc. The gap is large between a toy engine and a commercial engine, but there are a lot of fun games you can make with a toy.


Good thing WebGPU native implementations are coming along to fill this role.


good thing this is not an SDL tutorial xD

i'll address that things when needed but the project has like 500 lines, don't think that momment is now, to be honest...

Anyways, thanks for your support, lovely fellow


When I was younger my programs would start to fall apart after a few thousand lines. It took a LOT of practice, both writing programs from scratch and working on large programs that were correctly designed.

One thing to remember is the act of coding is a bit different from architecting a program. If you are unsure of how it is going to work it is probably not going to work and as your program grows in size will become unmanageable as you continually have to rewrite it to take things into account that you didn't plan on. It helps to become familiar with design patterns and to pseudocode the overall structure. Expressing your desired architecture into code is a big enough challenge without trying to do it at the same time.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: