wolfwings: (Default)
[personal profile] wolfwings

ObPreface: I'm an old-school gamedev/coder, that has never found an IDE beneficial so far yet, still hand-write my Makefiles instead of using autoconf, etc. Because I've never actively sought out 'invasively cross-platform' support, if it wasn't on x86 or arm running a modern *NIX-like, DOS, or eventually Windows, I flat didn't care.

To the point that my 'gamedev' for a Windows game in 2024? Uses the Windows Subsystem for Linux to run MinGW locally on the system and cross-compile back out of Linux into Windows binaries again. A fun aside: Windows binaries run just fine inside of WSL instances, so you don't even need to copy things out to run when testing things, just run it in place in the build output directory no problem. And add an exception to Windows Defender for the appropriate directory underneath \\wsl.localhost\ for whatever Linux distro you go with.

That said... let's start talking about status, plans, and progress.

The overall plan of the game engine is a focus on mostly 2D games using full GPU acceleration, the initial game targeting 640x360 as the internal game resolution for example, and only Windows 10 and up on 64-bit CPUs less than 10 years old for compatibility.

I'll get everything implemented in OpenGL 4.6 and only support SPIR-V shaders so migrating to Vulkan eventually will be feasible. That's... surprisingly far down the road however though! Drawing a first triangle isn't actually a helpful starting point for a game engine because you need so many OTHER things more actually!

Most think they need to get something rendering on-screen, but then you keep breaking that part as you bolt in other pieces you actually need more, and it ends up being a lot more work to push those blocks around because they end up so far up the stack. It's like trying to build a roof first then assemble the walls underneath it.

So instead, let's look at the fundamental needs of a game engine:

  1. Scripting so you don't need to recompile the whole engine to change behavior.
  2. You can load all of those as loose files, but generally lots of small files performs poorly even on an NVMe, so you want some kind of archive format to keep a collection of files inside, so you can load assets from there.
  3. Input handling that can support mouse + keyboard + multiple gamepads + touchscreen, etc, etc. So it needs to be fairly generic, more of a 'binding engine' really to avoid limiting what users can do.
  4. Timers. Yes, technically, per-frame actions are a sort of timer as well.
Shockingly... this is 99% of what a modern game engine actually does. Everything (and I mean EVERYTHING) else is some new function/asset you tie into the scripting engine. Audio? UI? Textures? 3D triangle mesh support? It needs to be scripted, and can often be made 'opaque' to the scripting engine itself.

So this adds a sort of 'corollary' item: The ability to extend the scripting engine with new data types.

These extensions will usually be compiled instead of more scripting, as they'll be doing low-level stuff like feeding audio to a buffer, etc, that the scripting engine simply doesn't need to know the gory details of.

So let's look at what needs to be implemented first:

  1. OS-specific support. (AKA: The 'WinMain' stuff in Windows terms.)
  2. Archive handling so we have something to load.
  3. Scripting now that we have something to run.
  4. Input handling now that we have something to trigger.
After that it's all about adding additional data types to the scripting engine and making assets in those data types, so we're well into "building a game" instead of "writing a game engine" then.

So... Windows specific stuff, how complex is that these days if you only target modern Windows 10 and up codebases, expect Unicode support, etc?

Shockingly light actually.

You only need to:

  1. Define and register a Window Class
  2. Create a Window with a Window Procedure callback using that Windows Class
  3. Show the Window
That's literally it.

So many complex tutorials from days gone by grossly over-complicate this process, but it's literally just setting up a WNDCLASS structure handed to RegisterClass, create a HWND with CreateWindowEx using that same Window Class name you just registered, calling ShowWindowAsync so it shows up, and running the basic Windows Event Loop checking for messages using PeekMessage so you can do other things when there's no messages.

For the Windows Procedure callback I mentioned, for a game engine instead of a normal application, it only needs to handle a handful of message types:

  • WM_CREATE: Called when it's created, do any complex graphics configuration here.
  • WM_DESTROY: Just call PostQuitMessage.
  • WM_GETMINMAXINFO: Commonly missed in tutorials, it lets you specify the minimum window size when it's being resized.
If it's not one of those? Call DefWindowProc and let Windows handle it for now.

Then in the main message loop back in WinMain? If you get a WM_QUIT then just ExitProcess and you're DONE. Don't free resources, don't dismantle complex structures, don't even close files, just exit.

Otherwise? The minimal message loop just fetches a message and if it's not WM_QUIT you pass it through TranslateMessage and then DispatchMessage so it can get passed up to the Window Proc up above if needed.

That's a minimal Windows setup when you're not setting up complex dialog boxes and stuff just want a rectangle to render graphics like OpenGL or the like into.

With that out of the way I'll go into archive formats in my next post, because that will be better suited to discussing theory and options before we start poking at actual implementation details.

Style Credit