One of my first motivations to study programming was being able to make my own simple games, in Scratch and Python. Although I no longer regularly try to make games, I occasionally try to do something involving making games. It’s an enjoyable, challenging, and vast area of software development.
Given my recent explorations in programming languages, I thought I might try and describe a programming language for game development.
The status quo
Currently, some of the most used languages for gamedev are C++, C#, Python, and JavaScript, so shouldn’t we take a look at why people use them? Unfortunately, however, I’m not going to sift through the dozens of “top N programming languages for X” articles, so this will be my opinion.
- Performance (both execution speed and resource usage) matter. C++ and C# are the most widely used in general, and both have relatively strong performance characteristics.
- Engines make a (seemingly) huge difference: C# is used by Unity, while C++ is used by Unreal. Of course, the engine doesn’t account for all of the language’s users, but I would expect it to be quite a significant factor.
- All of the languages I mentioned are object-oriented in some way. I think this is partially because OOP is a great way to model game data and state, given that game objects and characters can be literally translated into objects, but also because OOP was (and probably still is today) the dominant paradigm when these languages were created.
- Portability and type systems are more polarizing characteristics.
- I personally think gamedev languages should lean towards more portable, given that distribution is extremely important for playtesting and gathering feedback.
- JavaScript doesn’t have portability problems at all: pretty much everybody has a web browser these days.
- Typing (in most comparisons I’ve seen) typically comes down to personal preference. It’s generally accepted that dynamic typing helps developers move faster, while static typing scales better in the long run — both of which are good for gamedev. I’m in the static camp myself.
- Other languages, like Lua, Zig, Jai, Lobster, etc. also make some very interesting design choices that are worth considering.
This information is basically what I’ll base design choices off of. Keep in mind that most of this is my opinion, coming from my own observations and experiences in gamedev (which isn’t that much), and that I’ve only assessed a few languages.
Let’s actually talk about the language then.
Language
- The language should be general enough to be used for simple software development outside games, and specialize in games, computer graphics, simulations, etc.
- The language should contain most features programmers expect from languages nowadays (and the same applies for the standard library).
- The language generally tends towards lower-level, but provides the constructs needed to write expressive, high-level code.
- The language should be designed for experienced programmers. New programmers can be guided through tutorials and documentation, but the language itself can’t be dumbed down.
- Find a way to avoid
null
.
Syntax
- I think syntax for any modern language should be simple, lightweight, and consistent.
- Explicit syntax can help avoid bugs.
- Syntactic sugar should generally be avoided, unless the relevant syntactic elements are used extremely often (for example, more than once in 10 lines of code).
- Braces, but no semicolons.
Typing
- As mentioned before, typing can be quite subjective.
- I’m going with static typing with type inference, which is common in modern languages like Go, Rust and Zig.
- Static typing generally scales a lot better (games can be huge projects), and type inference can raise productivity close to that of dynamic typing.
- Types vanish at runtime.
- Functions will mandate a type signature. This blog post makes a compelling argument.
- Algebraic data types are provided for easy data modeling.
- Composition is preferred over inheritance.
- A struct can delegate a method to one of its fields. This could replace full inheritance specifically for the purpose of code reuse.
- Should we let the programmer choose between dynamic dispatch and monomorphization, or should we make an automatic choice depending on situation and allow overrides?
- Does a variable’s type reflects its mutability?
- How should interfaces be defined?
- Through types, or type classes/traits?
- Should types and traits be nominal (Rust) or structural (Go)?
- How are traits derived?
Runtime
- AOT compilation seems like the way to go here — high performance, relatively simple, and easy distribution.
- JIT compilation could be useful for debugging or maybe even hot code loading but it comes at a very high engineering cost.
- Native code (via LLVM) and bytecode VM are both viable targets. C# runs on the CLR, and all but the most intensive triple-A games should run just fine on a decent VM.
- WASM support could definitely be considered, making it easy to share games on the Web.
- The language could provide a platform system, where I/O and graphics are implemented for a single platform like a package.
- Zero-cost abstractions should be preferred, especially if they don’t raise complexity significantly.
- Bounds checks and runtime assertions are worth the cost, and if not, they can be turned off.
- FFI should be supported.
Memory
- For gamedev, it’s often needed to have fine-grained control over memory and resources.
- Memory management must be made as efficient as possible while ensuring memory safety.
- Extremely intensive games would require full control and manual memory management, but I’m convinced that efficient automatic memory management should work fine for at least 80%.
- Multiple methods of memory allocation (regions) should be provided:
- Ownership analysis
- Ownership analysis with lifetime inference and RC
- Arena allocation
- Manual (e.g.
unsafe
mode)
- The programmer can allocate in different regions as desired, depending on which would be more suitable. How does the programmer choose? For each variable, reference, or are types parameterized over allocators?
- A sane default would be chosen (either RC+ownership or GC).
Concurrency & parallelism
- Parallel processing should be built into both the language and libraries.
- Threads can be directly accessed.
- Concurrency could be integrated into the language or libraries, possibly using actors, channels, coroutines, or
async/await
.
Metaprogramming
- Reflection is possible.
- Arbitrary code can be executed at compile time.
- Compile-time code execution is sandboxed through a capabilities system.
- Code can read/write other code through compile-time execution, but syntactic macros themselves aren’t provided.
Engine
- The engine should be distinct from the language (as in, those using the language aren’t tied to the engine).
- The engine should be developed for the language, not the other way around.
- There should be a low-level engine for rendering and simple 2D drawings and a high level engine for making full-featured games.
- There should be a portable graphics driver (using OpenGL, Vulkan, WebGL/WebGPU for example). It would probably be much easier to use someone else’s initially.
Tooling
- Compilation should be fast and incremental.
- The language provides a package manager, version manager, code formatter, and language server.
- A package registry could be built for distributing libraries or games.
- Documentation and testing solutions should be provided.
Conclusion
This concludes my description of what I think a language suitable for gamedev would include. I have no idea when I’ll ever be able to make this, but I suppose there’s no harm in jotting the ideas down.