This debate has been trodden a thousand times before. The best I can do is express my reasons for doing what I do. I do not claim to have the answer for you or your situation.
I will break this into two parts: my reasons for creating an engine from scratch and my reasons for choosing C# over C, C++, or Rust.
Let me start by pointing out that I've been programming a very long time. I grew up hacking away at code in QuickBASIC 4.5: the professional version of QBASIC that could compile stand-alone
.exe files! I spent my youth trying to make clones of console DOS games like ZZT and Kingdom of Kroz II on my dad's ZEOS 386SX. The moment I learned about the mythical beast that is C++ (what "real programmers" use), I changed course and never looked back. I have always been comfortable working on the entire tech stack of a game.
When it came time to go to college, it was a no-brainer for me: I went and got a Bachelors of Science in Computer Science. I loved every minute of it! But the best part was when the school introduced an elective course on 3D graphics. I knew nothing about coding 3D games, so I jumped on that opportunity. That's when I learned about the all powerful 4x4 matrix.
So, why do I want to make my own engine? The short answer is that I derive great personal satisfaction from doing so. I do not see the underlying technology as an "implementation detail" that I want to skip in order to focus on game design; the underlying technology is the best part for me. I want control over every aspect of the experience. I want to control over my storage formats. I want control over how and when my game chooses to load assets. I want control over how my game windows behave when resizing or moving.
I am making a 2D sidescroller sandbox ARPG for Windows, macOS, and Linux. That is well within my technical ability. I want as much ownership of the source code as possible; I do not want to be beholden to any engine makers both in terms of profits and technical longevity. I do not hold it against anyone who uses an engine like Unity or Unreal; most game studios are businesses first and artisans second. Those cost savings cannot be ignored. I like to think that (for now) my studio Obvious Piranha is the reverse: artisans first and business second.
To be clear, I am writing my own engine but leveraging existing platform middleware. I am using SDL for windowing and event handling. There was a time in my life where I was not afraid to write my own middleware; I learned win32, Cocoa, and Xlib to see how everything worked, but I've moved on. Libraries like SDL are more than adequate.
This is where things become interesting. :)
I absolutely swore by C++ for many many years. In particular, the release of C++11 was a game changer for me. The introduction of move semantics solved what I felt was the last remaining major C++ bottleneck. Allowing objects to move put an end to gross workarounds like reference-counting or copy-on-write mechanics. I could simply transfer the guts of one object to another with zero allocations.
C# wasn't really on my radar as a viable choice. I was writing C# at work, but it had too many issues blocking it from being a real game development language. I was uncomfortable with the idea of coding against .NET Framework in Windows and then trying to make everything behave in Mono on other platforms. Plus, with my extensive C++ background, I was all too aware of C# and its decision to allocate buffers all over the place for every little operation.
Microsoft and the .NET Foundation knew that the .NET ecosystem had no real future with its current trajectory, so they took drastic steps to bring .NET and C# into the modern age: they gutted all Windows-specific APIs and created .NET Core, a properly cross-platform ecosystem. Also, by breaking backward compatibility, the .NET Foundation was able to overhaul performance big time and address the issue of excess allocations.
However, the real magic arrived with .NET Core version 2.1:
Span<T>. This... is what I was waiting for. This new type addressed possibly my biggest gripe against C# and most of its APIs. It allows us to pass references to segments of arrays without copying the array! And you know what the other best part is? It can reference managed or unmanaged memory. So, if I create a method
void Sort(Span<int> items), it will work on both an
int that came from other C# code or an
int* that came from a call to a native C library. That's amazing.
Since the release of
Span<T>, my loyalty to C++ has burned down fast. I'm just so tired of dealing with
#include sequencing (forward declarations),
Makefile shenanigans, glitchy debuggers, and memory safety. I'm a big fan of running
dotnet build and having it succeed on any desktop platform out of the gate. As I mentioned above, my target hardware is desktop, so I do not need to squeeze every last cycle out of every inch of my code. I'll be optimizing where it matters. (Data-oriented design!)
I actually have a basic prototype of my engine in C++ that I threw away in favor of my new C# one.
Wow, thanks for reading all that! I want to go into detail on so many more aspects, and I will in future posts. I just wanted to describe my overall journey to right now without putting too many people to sleep.
Before anyone asks, Java was never an option. It has no value types, and the standard library is bad. Long live
Ask me anything.