- Unity game dev
- NuGet dependencies
- General optimization
- Procedural mesh generation
- Project set up
- Utilities repo
This is where I keep notes for things I keep revisiting throughout my work. Entries are sorted alphabetically.
Unity game dev
As of 2023-10-06, and since a new Unity extension for VSCode has been released, there is no official way to support adding Roslyn analyzers for C# code (perhaps other than reverting to using Omnisharp).
I may revisit this later, but at this point it might be wiser to wait for the VSCode extension to become more mature.
- “bake into pose” root transform changes that shouldn’t be applied to the gameobject on each animation.
Downloading animations “without skin” prevents wasting memory with unnecessary models. However, the avatar generated by Unity for these won’t work properly. That can be fixed by downloading the default Mixamo character (Y Bot) in T-pose and generating an avatar from it, which can then be used with “without skin” Mixamo animations.
Movement without foot sliding
- Disable saving
.blendfiles directly inside the Unity project’s “Assets” folder
- There doesn’t seem to be a way to store textures in
.blendfile, so keep them in “Assets” folder and use them in both Blender and Unity
It is often useful to generate textures for debugging code visually through the inspector.
To view a texture through the inspector, assign it to a public or
Then generate the texture with the following logic.
var tex = new Texture2D(width, width, TextureFormat.RGBA32, mipChain: false);
for (int i = 0; i < width; i++)
for (int j = 0; j < width; j++)
Color.Lerp(Color.black, Color.white, VoronoiNoise.Get(i, j, 30f, 1234L))
Apply() is required for the texture to appear right away, otherwise an update has to be triggered by changing something on the inspector (such as its anisotropic filtering level).
- Only the last LOD matters
- Impostors > LOD meshes
- Advancements like Nanite can change 3D lore fast
Avoid updating decimal numbers iteratively, which can accumulate errors. Instead, prefer to calculate values from the underlying constants each time.
This is one of the main concerns of the field of numerical analysis.
Frame rate independence
Everything that runs in
Update() (as opposed to
FixedUpdate()) should be carefully designed to ensure independence from variations in frame rate.
For example, there is a specific formula for a frame rate independent version of calling
lerp (linear interpolation) iteratively.
There is an extensive post about this here.
Ready to use code should be available in the Unity utilities repo.
Mathf vs MathF
UnityEngine.Mathf relies on
System.Math, which runs computations on the
System.MathF does computations on the
The difference may be small however since CPUs generally handle 64bit math better than GPUs.
The expensive square root operation can be avoided by comparing squared distances.
When raising a number to an integer exponent, direct multiplication (
x * x) is more efficient than calling a function such as
MathF.Pow() which accepts any real number as exponent.
Awaitable vs Task
Awaitable in version 2023.1, which essentially is a modernization of the Coroutine API (which handles things like waiting for the next frame) to make it compatible with async/await in C#.
Awaitable can also handle multithreading with explicit switching between main and background threads using
await Awaitable.MainThreadAsync() and
BackgroundThreadAsync appears to be a mere wrapper around
Task.Run(), thus with no advantages when it comes to allocating
In addition to this, a previously existing issue where pending tasks continue running even after exiting/entering play mode in the Unity editor remains:
To get around this, I had implemented a
SafeTask wrapper as a replacement for
Task.Run() (no other
Task API functionality is replaced), which still makes sense to continue using:
- Forum thread on multithreading
- “Overview of .NET in Unity” documentation page with “Async, tasks and Awaitable” section
- Await support documentation page
- Use Tasks over manually creating Threads except for long lived tasks, to avoid hoarding a thread from .NET’s thread pool.
- Always use concurrency management mechanisms when sharing data across threads. Even when there is only a single writer thread, memory caching mechanisms can interfere with synchronization.
Order of preference for concurrency management mechanisms:
- Task model
- Concurrent (thread safe) collections
- volatile (avoid this for being more obscure and harder to reason about than the Interlocked class)
- Lower level synchronization primitives (Mutex, Semaphore, …)
- Use static lambdas (introduced in C# 9) with
Task.Run()to avoid capturing scope, which requires heap allocations for the underlying class.
- Prefer long-running tasks to starting new tasks frequently, which avoids
Taskobject allocations and context switching overhead
With no official NuGet support in Unity at time of writing, one can set up NuGet dependencies through a separate C# project.
There are two other options worth mentioning:
- NuGet for Unity (too heavy handed/complex solution that runs the risk of going unmaintained)
- Downloading packages manually and copying
.dlls to the Unity project (troublesome not just for the extra manual work but also because one has to manually go through dependencies and download each, with no simple way of keeping track of everything over time)
These are the steps to set up dependencies through a standalone C# project:
dotnet new classlib --framework netstandard2.1 -o NuGetDependencies.
We target .NET Standard 2.1 according to Unity compatibility.
Also feel free to delete any
.csfiles created by default, as we won’t need to write any code.
dotnet new gitignore
- Add dependencies with
dotnet add package <package name> --version <version>(can be copied from NuGet website directly)
- Build with
dotnet publish(debug configuration is the default). Note we don’t use
dotnet build, which doesn’t include dependency
.dlls in build files.
- Copy the files in
./bin/Debug/netstandard2.1/publish/to somewhere in the Unity project’s
Assetsfolder, such as
The points below have more importance in the context of frequently run code, such as that in
- Avoid generating garbage (allocating heap memory for short-lived objects). One way to do this is to reuse objects, by storing them in class fields instead of instantiating locally. Object pooling is a similar and popular strategy.
- Use fixed size over dynamically sized collections (such as arrays over lists) whenever possible, for the reduced overhead.
- Use static lambda expressions (introduced in C# 9) over non-static to avoid capturing scope.
See Microsoft’s mixed reality performance recommendations for Unity.
Procedural mesh generation
Call MarkDynamic on meshes that are updated frequently at runtime.
Project set up
These are things to think about when starting a new project, which can also be worth revisiting once in a while. Generally high-leverage settings with poor defaults or quick fixes for common issues.
Fix color banding by checking “enable dithering” in the camera inspector. I only tested this in URP.
Mono vs IL2CPP
Decide between Mono or IL2CPP.
Generally, IL2CPP should be better as it can have better performance than Mono, although it may complicate mod creation (although hacking would also become more difficult accordingly).
Hiding properties from inspector
Before version 2023.3.0a11, Shader Graph properties not marked as “Exposed” simply don’t work unless initialized in code. The expected behavior would be to simply hide the property from the material inspector.
There is more info about this issue in this thread.
px vs rem
em should be used for font size related styles and
px for everything else, such as margins or padding.
There’s no consensus on whether to use
rem for everything or only for font size related styles (using mainly
Browsers zoom by increasing the size of
px, so this decision should only affect users who manually configure a different browser default font size.
For those, it may be better to scale fonts only and keep spacing the same, as otherwise they could simply use the zoom feature.
If unable to send email through a third party client for a Google Workspace account, try enabling “Allow per-user outbound gateways” in the admin panel.
Commit without a message
git add -A && git commit --allow-empty-message -m '' && git push
Git submodules are a nice way of setting up project dependencies.
You can add a dependency into a repo by running:
git submodule add --name steamworksnt https://github.com/marcospgp/steamworksnt.git <target-folder>
--name prevents the destination path from being used as the name by default, which can be confusing if the module is moved with
git mv later.
If submodules will be used in-editor as part of a Unity project, they should be placed in the
To update dependencies or download them after a fresh
git clone, use:
git submodule update --init --recursive --merge --remote
To remove a submodule, use
git rm <submodule path> (and not
git submodule deinit) in accordance with the docs.
However, also note that:
the Git directory is kept around as it to make it possible to checkout past commits without requiring fetching from another repository. To completely remove a submodule, manually delete
$GIT_DIR will usually be the