Choosing a Programming Language for Multi-platform Game Development
You want to write a game that will run on desktop Linux, Windows, Mac OS X, Android, iOS, SteamOS, Xbox, PlayStation, Wii, and possibly the web?
I’m going to suggest that you go with native binary compilation. But let’s first look at some attractive alternatives:
It’s of course possible to use a ready-to-deploy game development environment (GDE?), such as Unity, ShiVa or Godot. They all have proven track records in making multi-platform games: performance might be an issue on some platforms, though, so you would need to compare them carefully. Most of them have licensing or royalty fees, so you have to ask yourself if it’s worth it. An important cost, too, is the loss of control: you’re putting so many of your eggs into one basket. In the case of Unity or ShiVa, you are dealing with proprietary baskets: your control over them is essentially nil. If you’re going this way, definitely prefer an open source solution, such as Godot. To me, even Godot isn’t liberating enough: if I need to make changes, I would have to delve into its monster codebase, patch and fork. I’m a developer who knows what he’s doing and what he wants, and so a ready-made solution would be too restrictive.
Java might look promising: it’s Android’s native language and available on many platforms. But ... not all. Even when it is available, it might not be built-in: requiring users to install a JRE is a significant deterrent, with no apparent rewards. And then, there are various performance and memory-use challenges. Minecraft might be the first truly successful game to be built on Java, but it’s easy to see that this success is in spite of Java, not because of it. Mojang might have gotten instant multi-platform reach at the get go, but in the long run Java was a drag. It’s ironic to note that choosing Java did not make porting Minecraft to Android trivial: Mojang ended up writing their Android version almost from scratch. Also, note that 3D Java game development would likely require a native extension such as LWJGL or jMonkey. Those do not work everywhere Java does. For gaming, it seems that Java costs more for users and developers than it is worth.
C# (or other CLR languages) offers similar but more limited benefits to Java. Mono can take you outside of Microsoft platforms, but it’s not as widely available as Java. It’s also missing features: for example, if you want XNA, you’ll need to use something like MonoGame to fill in that gap. Ironically, it might not even work out-of-the-box on Microsoft platforms: you often need to install a certain version of .NET, just like you need to install a JRE. This is again an annoying cost to the user.
The best language choices for creating binaries appear to be C or C++, and C++ is especially popular.
Despite a non-standard ABI, it’s usually easy to integrate C++ into any native platform. There are many ready-made C++ engines you can license or even use for free, however their multi-platform support may actually be limited. It’s only recently, for example, that Source, Unreal and CryENGINE announced support for Linux (Unigine had it for a long time), and none of them supports all mobile and console platforms. The free software and open source solutions, such as OGRE, are not much better. Again, the set of costs is essentially the same as wedding yourself to a GDE. If anything, it’s worse, because you get less help from the basic platform.
But what if you dislike C++ as a language? Though I was a C++ (and OOP) zealot in my misguided youth, I came to learn—the hard way—that C++ is a nightmare for debugging and quality control. Between constructors, destructors and operator overloading, there is so much opportunity for mischief. Simply put, you can never just look at some C++ code and know what hidden functions are being called. In anything but trivial class hierarchies the problem becomes totally unmanageable: the only way you can really know what’s going on is to go step-by-step with a debugger. It seems insane to choose a programming language that gets in the way of programming.
That said, C++ has a nice syntax for OOP, and OOP is useful (sometimes: not always, definitely not for everything, and definitely not within its strict orthodoxy). And so I put a lot of effort into exploring an attractive compromise: Vala (and Genie are nice high-level languages that behind-the-scenes turn into C code. They promise all the benefits of C with the benefits of Java/C#/Python-like linguistic features.
Does Vala deliver? Kinda, maybe. I have successfully written a simple game demo in Genie that runs on Linux, Windows, OS X, and I’m sure I could port it to Android, iOS, etc. But I’ve encountered three challenges. First, platform stability: the languages are production-ready, but still undergoing changes. I’ve found that I had to change code when a new version of the Vala compiler came out. Second, integrating existing libraries was not that easy. If they’re not GObject-based, you have to write your own translator (though note that it is not a wrapper, merely a kind of lexer used by the compiler to do syntax translation). I found myself spending a lot of time writing and tweaking these, and wondering if it was really worth the effort. Finally, there is the core GLib dependency. GLib is very portable, but it’s not so light: it’s a big and possibly demanding library. While its OOP system is great for creating reusable libraries, that’s a benefit that you don’t really need in game development. Is it worth losing so much because of some nice OOP?
There is another possible alternative: Objective-C. It’s actually quite portable, being supported by gcc, and is a much more natural extension of C than C++ is. But it’s just too tied to Apple, and too quirky for my tastes. If you’re thinking about Objective-C, you have to wonder if it isn’t best to just use plain-old C.
And, you know what? It is.
I’ve discovered and decided that C is the best way to write multi-platform games, offering so many benefits with minimal costs.
First, the benefits:
- Compiles everywhere, and with a standard ABI.
- Nothing beyond the most minimal dependencies: no GLib, not stdlib++, nothing beyond the basic libc that everything else requires, anyway.
- What-you-see-is-what-you-get coding: if a function is being called, you will see it in the source code.
- Lightning-fast, optimized compilation.
- The most crucial essentials for cross-platform binaries are all based on C APIs: SDL is in C, OpenGL is in C, the Android NDK uses C, etc. Sure, there are wrappers for all these for C++, but they’re nowhere as straightforward.
- No OOP! Nonsense: you can do OOP just fine in C, even without GLib or any other helper library (check out Cello, though, it’s pretty cool). Sure, it’s more verbose than C++, Objective-C, Vala, Java, etc., but it’s really not so bad. I find that I really prefer it, despite the verbosity: I know exactly when a constructor is being called, because I called it. There’s a crystal-clear straightforwardness to the code that is immensely refreshing.
- Primitive standard library! Nonsense: there is a large ecosystem of libraries you can use for just about any feature you require. A few examples: I like using the Better String Library for strings, SimCList for linked lists, and my own Exceptional C Exceptions (or exceptions4c) for error handling. There’s stuff out there for garbage collection, threads, parallel execution, etc.
- It’s not so easy to integrate C++ libraries into C. You have to create some sort of wrapper: it adds some performance overhead, which is really minimal and not a big deal, but mostly it’s just extra work for you. It’s unfortunate that many library developers decide to use C++ rather than C: it adds an unnecessary usability limitation. If you’re thinking of using one of those big C++ gaming engines (Source, Unreal, CryENGINE, etc.) then this cost is likely unbearable: choosing C is simply unrealistic, so bite the bullet and go with C++. Sigh.
With so many advantages, and no serious disadvantages (for my needs), I went with C. And let me tell you, so far, it’s been a wonderful experience. Having largely ported my code from my earlier Vala experiments, I don’t feel I miss the high-level syntactical features much. If anything, it’s nice not to have to fight compiler restrictions. It’s a breath of fresh air. But mostly, it’s a pleasure to see my game running on Linux, OS X, and Android (and iOS soon!) with only the most minimal external dependencies, feeling confident that gamers will get the best possible overall experience.
Paradise! Paradise? Well, yes and no. C may be the best language choice for games, but going binary involves serious challenges on some platforms. I’ll deal with those in future blog posts: in particular, you need to build a robust, flexible multi-platform build system, which will involve a lot of ongoing work to setup and maintain, and then there are the many quirks of various incompatible versions of the OpenGL APIs. More to come.