On this page
A while back I wanted to explore game development, classic 2D games because I can’t waste too much of my life in the rabbit hole of 3D. Me and a friend were avid C64 hackers back in the 80s and I haven’t tried to make any games ever since. First I looked at available paths using Dart, like Flame for example. But I wanted to target low end very cheap hardware, be highly performant with good physics and be able to handle lots of players. Flame might have managed that, but it was too unproven.
So my search for a good 2D engine ended up me finding ORX. This is a hidden gem of an engine, very good performance, highly portable written in C and with a very friendly and capable maintainer! But… C? Nope, that will not cut it for me. I do love that ORX itself is written in C because it means the performance and portability is good, but it also means we can fairly easily wrap the engine and use it from another language. In my case… Nim!
This resulted in creation of Norx, a Nim wrapper of ORX.
…so ok,
ORX
This engine is a bit different than other engines since it is “data driven”, this means a lot of behaviors and configuration that would be a bit complex to express in code can be handled purely through the use of “ini files”, which simply are plain easy to read text files. This mechanism means you can get going quickly, and you can also solve some common patterns quite easily, like for example animations. Now, in combination with quite a fair bit of documentation and examples, this is powerful.
ORX is also hardware accelerated, is a complete single library handling audio, input, graphics and has a good integrated physics engine. It supports a lot of platforms and is under the zlib license. And above all - there are a bunch of games already written with it.
Creating Norx
Nim is a superb language that normally compiles via C, but it is much more capable and more high level than C. And truly brilliant in wrapping and using C libraries. Norx was first auto generated (using the tool c2nim) from ORX headers, then given various “post processing” (name manglings, moving comments to proper places etc) and finally also tweaked by hand a bit. Norx had a set of “low level” wrappers, one per C header basically. And then we also started making some high level modules, where we put Nim code abstractions making it all nicer from a Nim perspective.
This first version of Norx was enough to get our game Tankfeud going. But around 2024-12-29 the pain of updating Norx got too big. Every new ORX release meant running c2nim over the headers and then fixing up the mess by hand, and the “fixing up” part was a pile of mangling scripts in Nim and (I am not joking) Pharo/Squeak. Sooner or later ORX would move faster than I could follow, and then Norx would start rotting. That is when I found Futhark.
Futhark
Futhark is a Nim package by PMunch that uses libclang to parse C headers and emit Nim bindings. In other words, a much better and more general purpose version of the hack I had been hand carrying. I use my own fork of Futhark since we needed a fix for how static const declarations come out, but all the heavy lifting is Futhark itself.
The generation now lives in one Nim program, scripts/create_wrapper.nim, and instead of shelling out to a tool we import Futhark and drive its importc macro directly from Nim. The script hands it every header under orx/code/include (base/ first, the Android headers excluded) plus one renameCallback that does all the Norx naming:
proc renameCallback(name: string, kind: SymbolKind, partof = "",
overloading: var bool): string =
# Enum values and constants just lose the orx prefix
if kind == SymbolKind.EnumVal or kind == SymbolKind.Const:
return (if name.startsWith("orx"): name[3..^1] else: name)
# Split orxObject_SetSpeed into module "object" and name "SetSpeed"
let splits = name.split("_", maxsplit = 1)
var module = splits[0][3..^1]
module[0] = module[0].toLowerAscii
let shortName = splits[1]
# Names too generic to stay unique keep their module prefix:
# orxObject_CreateFromConfig -> objectCreateFromConfig
if shortName in protectedNames:
return module & shortName
overloading = true
# For modules like object, vector, input, physics... use the short name
result = if module in shortenedModules: shortName else: name
result[0] = result[0].toLowerAscii
So orxObject_SetSpeed becomes setSpeed, while orxObject_CreateFromConfig stays objectCreateFromConfig, because createFromConfig would collide with the same name in other modules. The protected list also covers Setup, Init, Exit, Get and reserved Nim words like Set, Mod and Div.
The generated output is then post-processed a bit, because a faithful translation of the headers is not the same thing as a pleasant Nim API:
orxBOOLbecomes a distinct type. Same C ABI, but you can no longer accidentally pass an integer where ORX expects a boolean, while converters in both directions keepif isActive("Quit"):andobject.enable(true)working.- The vector unions become plain tuples. Futhark faithfully reproduces the nested C unions in
orxVECTOR, which is hopeless to use from Nim, so the five vector types (orxVECTOR,orxSPVECTOR,orxRGBVECTOR,orxHSLVECTOR,orxHSVVECTOR) are replaced by simpletuples. That is what the whole “value vector” layer with+,*,lengthandnormalizeis built on. - Comments and constant noise get stripped, and the result lands in
src/wrapper.nim.
So instead of the old pile of one wrapper per C header we now have one generated file, around 7000 lines with some 1370 procs, covering everything liborx exports:
# src/wrapper.nim - generated, never edited by hand
proc setSpeed*(internal_pstObject: ptr orxOBJECT;
internal_pvSpeed: ptr orxVECTOR): orxSTATUS {.cdecl,
importc: "orxObject_SetSpeed".}
On top of it sit the hand-written modules (basics, vector, objects and the small subsystem modules) that add Nim types, overloads, templates and converters, and norx.nim is the single module you import to get all of it. Those modules cannot be generated, so instead src/annotation.nim lets us pin the ORX source they were written from, right in the Nim file:
## @file orx/code/include/base/orxType.h:"/* *** Boolean constants":24:4fe621ff98a401dcb4c8ec303d698eb3
## Boolean constants
const
orxFALSE* = 0.orxBOOL
orxTRUE* = 1.orxBOOL
The annotation says: this code was written from these 24 lines of that header, and here is an MD5 of them. ./build.sh regenerates the wrapper and then compiles norx.nim with -d:processAnnotations -d:errorOnAnnotationChange, so if ORX changes any annotated section the build fails and points straight at the Nim code that needs a human (or an agent) to look at it. If the change turns out to be harmless, refreshing the hash is a one-liner, and if it is not, the annotation has already told you where the real work is.
Updating Norx to a new ORX used to be days of tedious work. Today it is: bump the submodule, run ./build.sh, fix what the annotations complain about. So Norx is now at 0.8.1, tracking ORX 1.17 and requiring Nim 2.2.4 or newer, and the repository has moved along with the game to tankfeud/norx - with API docs and a small website at tankfeud.github.io/norx.
Examples
Norx has grown a set of examples that are all runnable straight from a checkout. Some are minimal and some are complete games:
samples/ball- the smallest possible Norx program and the one shown on the website - one object, one input, one update callback, and ORX loadsball.iniby itself. Start here.samples/sample1- the standard ORX pattern:bootstrapplusinit/run/exitcallbacks and a rotating logo with a fade-in effect. This is the recommended skeleton for your own game.samples/sample2- the same trivial scene, but with ORX’sexecuteloop replaced by a hand-rolled main loop in Nim. Deliberately not the recommended ORX style, but it shows you exactly whatexecutedoes for you.samples/pong- a complete two-player Pong: fixed-step collision handling, scoring to seven, sounds, pause and restart, and a--startup-test trueself test that validates the scene and the game rules without a human watching.samples/boulderdash- a complete Boulder Dash style game: dig through dirt, collect diamonds, dodge falling boulders and reach the exit before the time runs out, with generated caves and CC0 Kenney art and sounds. Also with a startup self test.samples/qr-code- renders a QR code on screen using theqrcodeNim package, showing how to create many small objects at runtime from configuration.samples/official- Nim ports of the official ORX C tutorials (objects, clocks, viewports, animation, physics, sound, FX, scrolling, locale, spawners, lighting), contributed by @jseb.
Getting one of them going takes a few minutes, and you do not need to install ORX system wide - every sample links (with an embedded rpath) against the ORX libraries in the checkout:
git clone https://github.com/tankfeud/norx.git
cd norx && git submodule update --init
cd orx && ./setup.sh # restart your shell to get $ORX set
cd code/build/linux/gmake && make config=debug64
cd ../../.. && nimble install # back in the repo root
cd samples/ball && nim c ball.nim && ./ball # hold the right arrow key
ESC quits in most samples, and the key below ESC opens the ORX console where you can inspect objects live while the game runs.
And here is the thing I did not expect: Norx works great with AI coding. The wrapper is generated and machine checked, the samples are self-contained, each one builds and runs against the in-repo ORX, and both games can smoke test themselves with --startup-test true. The repository also carries instructions for the agents themselves (AGENTS.md, plus a skill and a small test tool for my agent harness), so you can point an AI coding agent at a sample and say “add a power-up to Boulder Dash” or “port the next ORX tutorial”, and it has the entire ORX API at hand plus a way to verify its own work. That is not theory - Pong, the tutorial and a fair chunk of the cleanup in the recent releases were made exactly that way.
So please clone it, run a sample, hack on it, and tell me what breaks. If you make a game with Norx I would love to hear about it, and pull requests are welcome.