Reflecting on using Unity for a large scale 2D project
February 16, 2022
Tagged: Unity Game Development C#
I was asked a while ago by Unity to send them some thoughts on what I think about Unity with regards to 2D development, with emphasis on sharing all the gory details. Nothing about any of the communication I had with them indicated that the information had to be private, and because I spent a lot of time writing it all out I thought it would be nice to put on my blog as well.
The asked me some specific questions, so I’m basically going to post my exact responses below, sparing no details (as the specifics from experience I think will really matter to people in a similar situation).
TLDR: The better you know C# programming, the more diminished the value of Unity is, ESPECIALLY in 2D games. Our main use case for Unity is for its UI system, and we would consider doing a custom engine in the future with some UI middleware instead.
This seems great, but came far too late into development for us to switch.
Additionally, our use case requires runtime tilemap generation, as we are providing a level editor to players. Unity’s story on runtime generation was unclear to me.
Rule tiles additionally (last I checked) seemed under documented and under considered, and would have been our main use case for the tool, as every tile in the game has rules.
Instead, we developed our own GPU-accelerated tilemap renderer, that allows us to render maps basically as large as we want, with every tile also have its own rules (250,000 tiles+).
This is TBD, but right now we’re just using basic Unity Input class. We may switch to the new Input system if need be, but our control needs are pretty simple, and coding our own simple keybinding system was pretty easy as well.
The “readiness” of this seems to be sort of floating. I’m wary to use it, but still interested. We may switch to this if we need to due to consoles.
This is a total mess. There are three different networking APIs all in various states of readiness and documentation, with no clear path on what is “the right way”.
Additionally, we don’t need the complexity.
We only need basic P2P networking, so are just using the P2P API of Steam via the Facepunch.Steamworks plugin.
I tried Unet and whatever the other ones are, but they just felt like too much to manage, with a moving API target, so we dropped them altogether.
I used to extoll the virtues of Scriptable Objects (slide from a talk I gave at a gamedev meetup):
A lot of my experience in Unity is informed by my specific use case: Building a Data-driven/Moddable Standalone Pixelart 2D Game.
Here’s my experience working with that:
I don’t need most of what Unity offers. I can do most everything I need via code. Most new features Unity seems excited about never seem like they are for me. RenderPipelines, Lighting Engines, Houdini, etc.
Similar to the above, APIs always feel like they are moving around. I’m incredibly reluctant to use anything new Unity puts out because I am now conditioned to think it will be deprecated or ignored for a few years.
Cantata is built from the ground up to moddable, and hence is very data driven. Unity doesn’t provide a good route towards doing that, as ScriptableObjects need to be imported via the asset system, Resources are deprecated, AssetBundles are deprecated, and Addressables are undocumented. I don’t need some massive CDN use case - just easy ways to load data from file. What I’ve done for Cantata is essentially circumvent Unity’s asset pipeline, and instead load most of my data in from disk at runtime. I use my tool Depot to author data outside of the game, and just load that at runtime, building the sprites programmatically from textures, etc. This would be insane to do for a large 3D project, but for a game where sprite sheets are rarely larger than 512x, it’s totally fine and the game runs great.
I also use Unity’s JSONUtility for this, but do wish it was better. Doing things like being able to deserialize arbitrary JSON, or incrementally parse based on keys, would be a godsend. Instead right now I use SimpleJSON to step down the tree a bit, and then pass those nodes into JSONUtility to be deserialized.
I made a thread on the forums about this, but having these in would be a godsend as well for 2D data-driven workflows (and many other things). I really want to lean on the fact that, in a perfect 2D world, Unity would be a best-in-class C# user and provide lots of ways for users to make games faster from C#. I see they are a stretch goal for an upcoming milestone as well, which is great. Generally, having up-to-date C# feature sets would be huge.
I hate not being able to leverage modern C# for Unity projects.
Additionally, I find that the more I learn about C# the less I care about using Unity for simple 2D things. Declaring static Actions in a single class is 10000% easier than trying to manage “EventObjects” to bind to event fields in the inspector (or using the UnityAction panel). C# events allow me to find references and easily follow data flows. Working through the inspector does not. I want to really drill on this as well, especially for a strategy game. The game is highly systemic, so understanding the how/why of something happening is of the utmost importance. This means that solutions that are opaque in their data flow (like inspector-driven workflows) or muddy about what is happening where (inspector + code workflows) are not viable. This means defaulting to code-centric development is effectively the only option for us, meaning I rarely touch Unity’s inspectors.
If we are to make a similar game again, we probably won’t use Unity. The only thing I really feel like we’d be giving up is the UI stuff, but that can be easily exchanged for something like Noesis or Coherent. I’m also very interested in Blazor. We will definitely stick in C#, and are already in the process of making our own 2D engine that solves a lot of our use-cases above. If we were doing a mobile game, I would strongly consider Unity, but also weigh it vs. our own engine or looking to a tool like Defold.
For 3D, we are considering switching to Unreal and leveraging Unreal CLR, though studio knowledge in 3D via prior work would make us definitely consider Unity here as well.
All that said, we are still 100% using Unity for Cantata and will not change engines before release. We’ve found a good balance and workflow for making Unity work with code, and will use that to deliver Cantata, as well as any future DLCs.
And there it is! For what it’s worth, I didn’t get some angry message in reply (at least publicly). I think Unity is well aware of their deficiencies around “traditional 2D development”, but also they don’t really seem to care about making it a lot easier, probably because 3D represents a larger market-share to target.
This is fine and I’d probably do the same thing if I was in their position, but it does also sort of function antithetically to the narrative of Unity being “easy to use”. I wouldn’t describe the total lack of a cohesive Input paradigm for a game engine “easy”. Or the fact you have to deal with the concept of a “renderer” just to get a sprite on the screen, etc.
It’s maybe “easy to use” in a context where you’re coming from bloatware AAA engines that need highly technical expertise to function, but for the average person, Unity puts a lot in your face to do very simple things. If you’re making a 2D game, you’re probably doing simple things a lot, so this stuff can be distracting to the point of total refusal to engage with it.
IDK - I know in the above I said we wouldn’t use Unity again for a similar use case, but we in actuality probably will because nothing has stepped up to the plate yet. Maybe my engine will be The Thing I’m really looking for. Maybe not.
For now, hope this was enlightening to other people in a similar position of evaluating Unity for projects of a similar vein. Hit me up @kkukshtel on Twitter if you have any questions.
Published on February 16, 2022.
Tagged: Unity Game Development C#