About advanced programming on Unity and game architecture

Do you have recommendations for getting Unity advanced coding information? Like assets or open source projects to study the code, youtube channels, articles, books, courses and etc.

I am a professional software developer and work with databases, frontend and backend for 6 years and recently decided to focus on game development. But after trying to go deep on Unity i found really difficult to find advanced content on game architecture and how to properly manage things for scallable games.

1 Like

With interactive software it’s all just iteration and refinement.

There’s almost always ten billion different ways to do things.

Usually the simplest approach is the best one to start with.

In other words, just do-do-do and pay attention to what you are doing.

2 Likes

If you want some code to read then maybe the unity source could be a starting point, though I am not sure if it’s a great one. I have picked up one or two tricks browsing it: GitHub - Unity-Technologies/UnityCsReference: Unity C# reference source code.

There are lots of youtube channels out there but I feel most are beginner targeted to maximize audience. The unite talks are a nice source though.

1 Like

Thanks for the recommendation, i saved some talks to watch later ^^

For me, beyond knowing the basics of the Unity api, it’s about knowing what your options are in C# and what common design patterns are. Then whenever you are selecting from those many approaches to a problem, you are at least familiar with the approaches and can make an informed decision on which way to go (first.)

(delegates vs interfaces is interesting, I do miss generics and linq here as options)

The unity specific code only goes so far, beyond that, it’s just C# and design patterns, nothing else. In fact, I’d recommend splitting your unity specific code (view) from the general code (model) for larger projects. It’s common practice.

1 Like

The first thing you will notice is how much slower the unity CLR (mono) is from what you are used to at work (.NET 6 and 7) :slight_smile:

Edit: and if you decompile the msil code you will see that mono is type casting floats to doubles before operating on it and than back again for no appernt reason

1 Like

I’ve got a 3 or 4 page intro to Unity for programmers at taxesforcatses-dot-com/codeNotes/UforP.shtml. One thing from there – everything in Unity wants to run all at once through their own Update()'s. That’s fine, but if you like a traditional top-down structure where one top-level chunk runs (or skips) everything, that works just fine, too.

About jvo3dc’s mentions of “design patterns”, that can be confusing. Some people say that to mean just any cool programming trick. But the official things called “Design Patterns” aren’t useful. Sure, you’ll find lots of old internet stuff about how great they are, but we finally have lots of newer internet stuff about what time-wasters they turned out to be.

1 Like

I will again point out, and this is the 3rd time this week alone, that the historical reason was that C# didn’t have a dedicated 32-bit floating point math library until .net Core 2.0 which was a) relatively recent, and b) unavailable in Unity. Unity made their own Mathf library (very early on) to support 32-bit math, however they’ve abandoned the idea of actually implementing their own low-level computations, because that’s just ludicrous for many reasons, and this kind of worked for 97% of games out there.

(Edit: Also I think it gets optimized by IL2CPP anyway.)

Because of this UnityEngine.Mathf is mostly just a 32-bit wrapper for the System.Math class which is 64-bit, this is why floats get cast to doubles and back, and this is especially painful for square roots and trigonometric functions. Some other methods such as System.Math.Abs do support 32-bit floats natively. And the rest of it are Unity’s own useful compounds or math functions, so not everything is bad about it.

Nowadays, we have an official 32-bit floating math library since .net 5 because .net core got reintegrated: MathF. According to my simple benchmarks they offer a significantly better performance and everyone and their grandma is encouraged to switch from UnityEngine.Mathf to System.MathF.

2 Likes

That’s true and untrue at the same time. There are “Design Patterns” and there are design patterns. Some of the patterns are so ubiquitous and evergreen, that they work in every environment and in any context, regardless of whether Unity is object- or composition- or data-oriented (as with DOTS), the language itself is vast and open to various approaches. Obviously all of the patterns are situational, but there is nothing black & white about them. Observer pattern, strategy pattern, factory pattern, singleton pattern, composition pattern, and many many more are perfectly valid or even beneficial for Unity projects.

That said, I never actually stress myself with remembering them, let alone applying them to the letter, but there is value in appreciating the reasons why they exist and what they solve.

1 Like

Write a Vector3 * float or worse Vector3 * Quaternion compile it with unity and look at the decompiled result.

1 Like

Design patterns are one thing, but more important is design principles like SOLID. Though, always approach principles with a bit of pragmatism

For the OP:

Try to get a good grasp on what Unity is – it’s a holistic engine, and your code is a mere guest to the party (quote by @Kurt-Dekker ). Pay special attention to how you can build things that are self-contained, but then you want to strategically connect them to the underlying mechanism, to make a cohesive hole. Where juniors would over-connect and lose all control, you can funnel your logic with complex solutions and intermittent machines that you write on top of it. You can also make everything as simple as possible, but the longevity of that approach depends on the scale of your particular project.

Learn about serialization in Unity, that’s a huge topic.

After you learn what makes Unity tick, learn C# as much as possible. This is easier to do than learning Unity. After almost 15 years I can’t manage to fully go through everything that exist, because it constantly evolves, yet I’m pretty much convinced I’ve seen nearly everything C# has to offer (at least on a fundamental level), because its design is much more deliberate and stable. And well-documented.

Spend some time at researching features you’d like to implement. Especially if you don’t have a graphics programming background. Learn the jargon, learn about the GPU and the techniques involved. Learn where the bottlenecks lie. What Unity does on its own, and what is beyond but must be maintained by Unity.

Spend some time in learning how to automate editors and make your own dev tools, this is a large part of being in control over your dev time in Unity. Nearly everything is programmable and extensible, but it’s shifty, sometimes not so well documented, and very different from actually working on a code that will be deployed.

These are the most important things I can think of.

1 Like

If you’re bothered with that, use mathematics package instead.

1 Like

I’ll bite…

Compiled this method:

  private void DoAThing()
  {
    Vector3 vector3 = Vector3.one * 5f;
  }

Well technically it was this, but it got trimmed down in compiling:

    private void DoAThing()
    {
        Vector3 v = Vector3.one;
        float s = 5f;
        var v2 = v * s;
    }

Resulted in exactly what I expected, a call to the Vector3.op_Multiply method passed in as a float32:

  .method private hidebysig instance void
    DoAThing() cil managed
  {
    .maxstack 2
    .locals init (
      [0] float32 V_0
    )

    // [36 5 - 36 39]
    IL_0000: call         valuetype [UnityEngine.CoreModule]UnityEngine.Vector3 [UnityEngine.CoreModule]UnityEngine.Vector3::get_one()
    IL_0005: ldc.r4       5
    IL_000a: stloc.0      // V_0
    IL_000b: ldloc.0      // V_0
    IL_000c: call         valuetype [UnityEngine.CoreModule]UnityEngine.Vector3 [UnityEngine.CoreModule]UnityEngine.Vector3::op_Multiply(valuetype [UnityEngine.CoreModule]UnityEngine.Vector3, float32)
    IL_0011: pop
    IL_0012: ret

  } // end of method zTest01::smile:oAThing

And going to that operator:

    [MethodImpl((MethodImplOptions) 256)]
    public static Vector3 operator *(Vector3 a, float d)
    {
      return new Vector3(a.x * d, a.y * d, a.z * d);
    }

The resulting IL is:

  .method public hidebysig static specialname valuetype UnityEngine.Vector3
    op_Multiply(
      valuetype UnityEngine.Vector3 a,
      float32 d
    ) cil managed
  {
    .maxstack 4
    .locals init (
      [0] valuetype UnityEngine.Vector3 V_0
    )

    IL_0000: nop

    // [543 7 - 543 53]
    IL_0001: ldarg.0      // a
    IL_0002: ldfld        float32 UnityEngine.Vector3::x
    IL_0007: ldarg.1      // d
    IL_0008: mul
    IL_0009: ldarg.0      // a
    IL_000a: ldfld        float32 UnityEngine.Vector3::y
    IL_000f: ldarg.1      // d
    IL_0010: mul
    IL_0011: ldarg.0      // a
    IL_0012: ldfld        float32 UnityEngine.Vector3::z
    IL_0017: ldarg.1      // d
    IL_0018: mul
    IL_0019: newobj       instance void UnityEngine.Vector3::.ctor(float32, float32, float32)
    IL_001e: stloc.0      // V_0
    IL_001f: br.s         IL_0021
    IL_0021: ldloc.0      // V_0
    IL_0022: ret

  } // end of method Vector3::op_Multiply

I’m not seeing anything overtly out of place here. You could make an argument about the Vector3 constructor adding an extra stack frame that could be avoided. But I’m not seeing anything about doubles here. Everything is a float32.

3 Likes

Now check the jited code

1 Like

You said:

That’s what I did.

The JIT’d code is very platform dependent.

3 Likes

Yeah wrong by me sorry. It’s the JITed code that will be typecasting.

1 Like

Question… how are you getting a peak at the JIT’d code?

The CLR will compile the code in memory… you’d have to either A) read the memory in place, or B) use the debugger api to observe the state of the application at the time. Not exactly a scenario that I have setup ready to just do on a whim. And I have the sneaking suspicion this is not what you’re referring to… and that just like you mistyped about the MSIL, you’re mistyping again here.

Do you mean the compiled code generated by IL2CPP?

1 Like

Cheat engine

The reason why System.MathF is faster than UnityEngine.MathF is that unity calls System.Math under the hood, an extra typrcast and method call.

Both libraries will typecast back and forth between floats and doubles because how mono JIT works

This is UnityEngine.MathF.Abs

public static float Abs(float f)
{
    float a = f;
    a = (float)(double)a;
    a = (float)(double)(float)(double)System.Math.Abs(a);
    return (float)(double)a;
}
1 Like