This is where I keep notes for future reference. Entries are sorted alphabetically.
CSS
px vs rem
Generally, rem
and 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 px
otherwise).
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.
When setting up email make sure to enable SPF, DKIM, and DMARC to properly authenticate messages. Use https://www.dmarctester.com to test this.
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.
Game dev
Gabe Newell on the concept of fun
https://www.youtube.com/watch?v=MGpFEv1-mAo
Unity
Analyzers
Unity has a guide on enabling analyzers. It works, as the .dll
s are added as analyzers to the .csproj
file generated by Unity:
<ItemGroup>
<Analyzer Include="/.../Assets/ErrorProne.Net.CoreAnalyzers.dll" />
<!-- ... -->
</ItemGroup>
However, while the warnings do show up in VSCode (with the C# dev kit extension), they do not appear in the Unity editor’s console.
Also note that the Unity analyzers are added by default, with the path varying based on the code editor selected under Unity -> preferences -> external tools
.
I tried setting up a separate C# project to include valuable analyzers as dependencies and make updating all of them at once easier, but the analyzer .dll
s aren’t included in the results of dotnet publish
and there’s no simple way to change that.
Animations
Importing
- “bake into pose” root transform changes that shouldn’t be applied to the gameobject on each animation.
Mixamo
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
See How to avoid foot sliding in Unity.
Blender
Settings
- Disable saving
.blend1
files
Unity interop
- Store
.blend
files directly inside the Unity project’s “Assets” folder - There doesn’t seem to be a way to store textures in
.blend
file, so keep them in “Assets” folder and use them in both Blender and Unity
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.
General optimization
The points below have more importance in the context of frequently run code, such as that in Update()
.
- 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.
Comparing distances
The expensive square root operation can be avoided by comparing squared distances.
Exponentiation
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.
Microsoft’s recommendations
See Microsoft’s mixed reality performance recommendations for Unity.
Inspector
Textures
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 [SerializeField]
field.
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++)
{
tex.SetPixel(
i,
j,
Color.Lerp(Color.black, Color.white, VoronoiNoise.Get(i, j, 30f, 1234L))
);
}
}
tex.Apply();
Calling 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).
LODs
- Only the last LOD matters
- Impostors > LOD meshes
- Advancements like Nanite can change 3D lore fast
Based on https://medium.com/@jasonbooth_86226/when-to-make-lods-c3109c35b802
Mathf vs MathF
UnityEngine.Mathf
relies on System.Math
, which runs computations on the double
type.
System.MathF
does computations on the single
/float32
type.
The difference may be small however since CPUs generally handle 64bit math better than GPUs.
Multithreading
Awaitable vs Task
Unity introduced 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#.
The new Awaitable
can also handle multithreading with explicit switching between main and background threads using await Awaitable.MainThreadAsync()
and await Awaitable.BackgroundThreadAsync()
.
However, BackgroundThreadAsync
appears to be a mere wrapper around Task.Run()
, thus with no advantages when it comes to allocating Task
objects.
In addition to this, a previously existing issue where pending tasks continue running even after exiting/entering play mode in the Unity editor remains:
Unity doesn’t automatically stop code running in the background when you exit Play mode.
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:
- https://github.com/marcospgp/unity-utilities/blob/main/Async/SafeTask.cs
- https://marcospereira.me/2022/05/06/safe-async-tasks-in-unity/
Relevant links:
- Forum thread on multithreading
- “Overview of .NET in Unity” documentation page with “Async, tasks and Awaitable” section
- Await support documentation page
General tips
- 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
- Interlocked
- lock
- volatile (avoid this for being more obscure and harder to reason about than the Interlocked class)
- Lower level synchronization primitives (Mutex, Semaphore, …)
Optimization tips:
- 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
Task
object allocations and context switching overhead
NuGet dependencies
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
.dll
s 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:
mkdir NuGetDependencies && cd NuGetDependencies
-
Run
dot
.Where “NuGetDependencies” is the destination folder. Feel free to modify this, or omit to create a project in the current directory
We target .NET Standard 2.1 according to Unity compatibility.
Feel free to delete any
.cs
files generated automatically, 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 usedotnet build
, which doesn’t include dependency.dll
s in build files. - Copy the files in
./bin/Debug/netstandard2.1/publish/
to somewhere in the Unity project’sAssets
folder, such asAssets/NuGetDependencies/
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.
Color banding
Fix color banding by checking “enable dithering” in the camera inspector. I only tested this in URP.
Editorconfig
Create a symlink from the project’s root to the .editorconfig
file in the Unity Utilities repo, which should be installed as a git submodule in the Assets
folder.
Git submodules
Set up dependencies on other repos by installing them as git submodules under the Assets
folder.
An example would be the Unity utilities repo: https://github.com/marcospgp/unity-utilities
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).
Shaders
Shader Graph
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.
General development
Floating point
Key Insights On IEEE 754 Floating Point Numbers
Error accumulation
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.
Git
Commit without a message
git add -A && git commit --allow-empty-message -m '' && git push
Submodules
Git submodules are a nice way of setting up project dependencies.
Adding
You can add a dependency into a repo by running:
git submodule add --name steamworksnt https://github.com/marcospgp/steamworksnt.git <target-folder>
Including the --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 Assets
folder.
Cloning & updating
To update dependencies or download them after a fresh git clone
, use:
git submodule update --init --recursive --merge --remote
Removing
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/modules/<name>/
.
$GIT_DIR
will usually be the .git
folder.
Makefile
MacOS
MacOS ships with an outdated version of make that does not support some functionality such as .ONESHELL
.
.ONESHELL
and .SHELLFLAGS
Context on using .ONESHELL
and .SHELLFLAGS
:
# Including the ".ONESHELL" target makes all commands within a target run in the
# same shell, instead of isolating each command into its own subshell.
# This allows us to make use of python virtual environments in a more readable
# way, and may also speed up execution.
# https://www.gnu.org/software/make/manual/html_node/One-Shell.html
.ONESHELL:
# ".ONESHELL" causes make to no longer fail immediately. We restore this
# behavior with the "-e" argument.
# We also set "-o pipefail" and "-u" for added strictness.
# Note that "-c" (the default argument when ".SHELLFLAGS" is not specified) must
# be included, otherwise make will error.
# https://www.gnu.org/software/make/manual/html_node/Choosing-the-Shell.html
.SHELLFLAGS := -c -e -o pipefail -u
NASA’s 10 rules for safe code
https://en.wikipedia.org/wiki/The_Power_of_10:_Rules_for_Developing_Safety-Critical_Code
Hardware
Keyboard
Ducky One 3
On MacOS, Cmd and Option keys are reversed for this keyboard. To fix, go to System Settings -> Keyboard -> Keyboard Shortcuts... -> Modifier Keys
and switch Command and Option keys with each other.
Monitor
Panel
OLED has best quality (true blacks) but pixel burn-in can be an issue. In comparison, IPS has good quality and best choice for price/quality ratio and PC usage (with static elements on screen which are pixel burn-in prone with OLED). VA has issues with ghosting/black smearing, and is cheaper but outdated. Never a good choice.
OS setup
General
- Install Fira Code font
MacOS
- Use AlDente to optimize battery longevity by limiting charging to 80%.
- Set shortcut for “copy picture of selected area to the clipboard” to
cmd + shift + S
insettings -> keyboard -> keyboard shortcuts -> screenshots
. - Enable keyboard navigation in
settings -> keyboard
to allow selecting buttons such as “cancel” in prompt dialogs with the keyboard.
Keyboard ISO to ANSI
To force MacOS to interpret a MacBook’s ISO keyboard as ANSI, install Karabiner-Elements, then under “virtual keyboard” switch “country code 0” from ANSI to ISO (I know, counter intuitive but it works).
After logging out and back in, the §
key will now be interpreted as the proper \
`.
This also frees up the < >
key to be used as a go back button (go forward with CMD as modifier) for apps like VSCode.
Shell
My MacOS shell setup:
- Oh My Zsh
- Switched to get case-insensitive path autocompletion, but has many more benefits over vanilla Z shell.
zsh-autosuggestions
for history-based command autocompletion.- Has more GitHub stars than
zsh-autocomplete
so picked it, but both seem nice.
- Has more GitHub stars than
- Enable “option key as meta key” in MacOS terminal app settings to delete entire words at once with
option + backspace
.
~/.zshrc
configuration:
-
Disabled
share_history
, which avoids live updating command history when managing multiple open shells across different projects:# Disable sharing command history across shells. unsetopt share_history
Windows
Emulate MacOS keyboard shortcuts
To emulate MacOS keyboard shortcuts on Windows, use the PowerToys Keyboard Manager with the following settings:
(note that replacing left alt and left ctrl should be good enough, but to keep things short “left” is omitted from these key names below. “left” on its own means “left arrow key”.)
Remap keys:
- alt -> ctrl
- ctrl -> alt
Remap shortcuts:
(note that the key remap above applies first, so alt below represents the ctrl key on the keyboard, and vice versa)
Restore switching between windows with alt + tab after key remap:
- ctrl + tab -> alt + tab
Restore switching tabs:
- alt + tab -> ctrl + tab
- alt + shift + tab -> ctrl + shift + tab
Move cursor to start/end of line (does not seem to require additional entries for selecting text with the addition of the shift key):
- ctrl + left -> Home
- ctrl + right -> End
Move cursor word by word, also selecting text if shift included:
- win + left -> ctrl + left
- win + right -> ctrl + right
- win + shift + left -> ctrl + shift + left
- win + shift + right -> ctrl + shift + right
Erase word by word:
- win + backspace -> ctrl + backspace
Drag text lines up and down:
- win + up -> alt + up
- win + down -> alt + down
Create new cursors up and down:
- win + ctrl + up -> ctrl + alt + up
- win + ctrl + down -> ctrl + alt + down
Note: for win + left/right remap to work, “override windows snap” in PowerToys FancyZones must be disabled, as it overrides that shortcut.
Because we remapped win + left/right/up/down, we restore that functionality (which basically moves windows around) on the real-world ctrl key (which has been remapped to alt, so we’ll use that below):
- alt + left -> win + left
- alt + right -> win + right
- alt + up -> win + up
- alt + down -> win + down
Restore PowerToys Run (the search box pop up that allows you to start apps faster) with:
- ctrl + space -> alt + space
Closing windows with alt + Q:
- ctrl + q -> alt + F4
Missing:
- Erase to start of line (cmd + backspace on MacOS)
-
Some VSCode keybinds are the same between Windows and MacOS, and this remap will break them if they use ctrl or alt, so they need to be restored - such as with “toggle terminal” (JSON generated by VSCode after modifying keybind):
{ "key": "alt+oem_3", "command": "workbench.action.terminal.toggleTerminal", "when": "terminal.active" }, { "key": "ctrl+oem_3", "command": "-workbench.action.terminal.toggleTerminal", "when": "terminal.active" }