I am an experienced Haskell programmer, although I only ever worked on one Haskell/PureScript project that was commercial. This year I had the weird idea to go into game development and I dived into the unreal engine which is all C++ for obvious historic reasons. Other game development engines (Unity and Godot come to mind) realized how C++ isn’t the best option and Unity chose C# quite sensibly, while Godot more boldly provides its own scripting language, which is really, really a good idea and makes working in Godot fun.
I didn’t chose unreal for it’s reliance on C++ but in spite of it.
Now I was wondering: Why is this Unreal/C++ bad, exactly? Is there a problem with object oriented programming, too? Are there advantages at all? Given my goal of finishing a game, how important is the programming language, really?
This goes to show what really lies at the core of my high productivity in Haskell. And I guess monads are part of it.
And these would be my answers/insights/claims after half a year of going back to C++:
-
Somewhat unsurprisingly, C++ and the unreal engine itself are old workhorses. Regarding programming paradigms they aren’t desirable tools and that hurts everywhere. The whole package that is the unreal engine still seems to be my best option. I.e. the programming language doesn’t matter as much as the whole toolbox.
-
In C++ good practices (often from newer versions) exist next to bad practices and the there is a lot of bad-practice-legacy-code, also game devs often are hobby programmer’s who don’t explore best practices as much as professional C++ devs might.
-
The C++ community often sabotages itself with misplaced discussion of optimal performance. Example:
setVisible(true);
supposedly ought to be replaced by
if(!bVisible)
{
setVisible(true);
}
to “avoid a potentiallly redudant function call”.
-
This sabotage becomes even more hurtfull when elegant abstraction (which are increasingly powerful in newer C++ versions) are dismissed for supposed suboptimal performance.
-
In C++, the programmer has full control over pass-by-value or pass-by-reference, and in practice that’s not a good thing, here’s why: Supposedly pass-by-reference performs better because, indeed, imagine a sufficiently large object and it’s true. Imagine small objects, pass-by-value regularly produced better code because copying might be worth it to avoid subsequent pointer indirections. So in the end, I don’t know exactly when to use what.
-
C++ can’t be fixed. This should be clear from the above. If I was to create a new C++ project from scratch, I could use C++ without object-orientation and I would have great tools, even some functional stuff. Given that I use C++ because of powerful old tooling, C++ really implies all the problems above, always.
-
The unreal engine has some design flaws that have nothing to do with C++. This is especially funny/annoying. When you get started in unreal, you have to make a distinction: Your code might currently be executed in the editor (e.g. a game designer drags an object into the scene which causes a call of your constructor) OR outside the editor in the final game. So far so good. Unreal offers a pragma
#IF WITH_EDITOR
to explicitly fork your code at compile-time and at runtime, there are objects like one calledGameInstance
which arenull
in the editor and properly initialized in the game. This means the notorious check for uninitalized objects in C++,if(!GameInstance) { return; }
isn’t avoided but embraced. Even funnier: if you don’t deal with it correctly, not only your game crashes but the whole editor. -
Object-orientation is often nothing more than modularity. So object-orientation in unreal/C++ forces you to divide your code into modules, which is a good thing. The resulting ambiguities not so much. E.g. why is it
GetWorld()->GetActorByClass(MyActorClass)
and not one ofMyActorClass::GetActors(GetWorld)
,GetActorByClass(MyActorClass, GetWorld())
? -
Object-orientation gets into the way of higher-level abstractions by unreal. This can be understood best by an example. In unreal there are actors to represent anything in the 3D-world, e.g. a bullet from a gun. Then there are components that add features to actors, e.g. the component for ballistic movement. In C++, this is done by inheriting from
AActor
to get an actor, then adding the component as property to your classMyBulletActor : public AActor
and then registering the component viaAActor::SetupAttachment(ballisticMovementComponent)
. So far so good. But how odd: what if I don’t register the component? Usually an undesirable state, but sometimes you want to share component data with other actors like this. E.g. theWindActor
influences the ballistic movement of your sniper bullet. The owning actor is identified only by the call toSetupAttachment
. In Haskell, without objects, there are no class properties and to set up an actor-component-relationship, you would obviously call some function, probably in the context of world building. Sharing your component data with other actors would be a different concern entirely (pass it around as argument or put it into a reader) and thus easily separated. -
The boilerplate, no-one likes it and it’s everywhere, sometimes small, sometimes big, sometimes it implies entire files (
public/myobject.h
andprivate/myobject.cpp
!) and in case of the header files it even affects compile-time and just putting all your code in .h-files isn’t advisable.
Sort of a conclusion:
I thought about how it would be nice to somehow write Haskell for my game and in Godot there exist Haskell bindings for the versions 3.x. I realized, however, that even if it would be possible, another language on top of unreal wouldn’t help a lot. Bad design includes language-specific bad practices but it protrudes interfaces and also the stuff behind those.
And despite all of this, I don’t complain. Unreal engine has a very generous pricing model for indie-devs and I basically get all the latest graphic features for free to write a game that, in principle, competes with AAA titles.