Unity protip šŸ§µ~ How do u improve performance *effectively* without advanced programming skills?
First of all, before I touch on gfx optimization, most spikes in Unity are not from the gpu, they are from the garbage collector. Check ur profiler to see.
Most ppl blame slowdown on the graphics, but the GC is often much worse in this regard
Often times it's unity's own built-in functionality that is throwing a lot into the GC. The Instantiate and Destroy functions are the most obvious culprits. You can tell an experienced Unity dev bc they will use pools for **literally** everything lol
Im not gonna explain object pooling here bc theres SO many tutorials and such. Just wanted to note that yes, it's really as important for performance as people say.
I recommend Advanced Pooling System on the asset store to people who dont want to write their own.
GC spikes come from some unexpected places too.
For example, Text rendering is obscenely costly (when using dynamic font size, which is the default)
This meant that *every time you change the Text.text variable of a UI element it makes a ton of garbage*.
Doesn't matter if you only changed 1 letter, every letter get re-rendered.
The solution (although it carries some limitations) is to set a constant font size in your font's import settings.
Another big spender is the physics system, especially if static tags and rigidbodies aren't being used correctly.
Static objects will run MUCH faster if they have their 'static' flag checked. But there are a couple esoteric rules which are not well explained.
static object rule #1: your object is not actually static unless *every parent* object down to the scene root is also static.
You still wont get the full performance benefit, regardless of whether you move the parent object or not.

Also ur setting urself up for some gnarly bugs by having static objects inside nonstatic objects.

imo Unity shouldn't even allow u to do this, it's never a good idea.
static object rule #2: just bc a collider isn't static doesn't mean ur allowed to move it (why is unity like this...?)
If ur going to do any transformations on a collider, it *needs* a kinematic rigidbody attached to it (or to one of its parents)
If even one single collider with no RigidBody moves, the physics system has to a costly thing where it rebuilds some of its internal modeling.
Why does Unity allow you to have colliders which are neither static nor moveable? idrk
Just going to add that Character Controllers are exempt from the Rigidbody requirement ("Im not like other colliders uwu")
I could go on about other random common things, but lets move on to everyone's favorite part: graphics optimization.
So contrary to popular belief, poly count is NOT a major factor in game performance anymore. That's part of why you see unoptimized film/ tv props in AAA games a lot now; yes its bad for performance, but not as bad as other things
Fwiw, the biggest impact of these ultra hi-poly unoptimized meshes is actually usually on the load time rather than the gpu.
ofc there are poly limits to modern systems too but they're not the first bottleneck most hit. If you're using game-ready models, then poly count is probably not relevant to ur performance tbqh
Whats *really* effective at improving gpu performance is reducing the number of draw calls. Typically, there's 1 draw call per material, per Mesh Renderer.
So you can cut an object's draw calls to a fraction by combining multiple meshes together ("batching") or combining materials+textures together ("atlasing")
Batching (combining meshes that share a material into a single mesh) is very effective, but Unity's Dynamic and Static Batching implementations are both.... uh.... not my fav. Ill get into why and then mention my alternative
Dynamic Batching is harmless but doesn't reduce the number of draw calls very much, so it doesn't really fix the issue.
Feel free to turn it on, just keep expectations low.
Static Batching has a real performance boost, but it's DANGEROUS!! and I recommend keeping it off.

Static Batching is destructive and runs in the background so u dont have direct control... it can make objects irreversibly uneditable... I have horror stories lol
The *workable* solution is "Manual Batching": using the 'FBX Export' package to combine sets of meshes into a single mesh. It takes some manual labor, but the results are undeniably better
ā€¢ select the objects in Unity which you want to batch
ā€¢ export as fbx
ā€¢ open fbx in ur modeling program
ā€¢ select all meshes and hit 'combine'
ā€¢ overwrite fbx and import it into ur scene
ā€¢ if u didn't alter the origin point, it should line up perfectly with the original.
This has a major performance boost bc it reduces not only the number of renderers, but also the number of active Game Objects and Mesh Colliders in the scene as well.
Another fairly easy way to get a big drop in draw calls is to turn on Occlusion Culling. This can have BIG benefits, but only if your scene has been properly optimized first.
Occlusion Culling is deeply tied in with the 'static' tag, and wont do much if static objects aren't tagged correctly (see above).
You also need to use an opaque shader on objects that can block the player's view.
Doesn't matter if the texture is opaque; the shader must not support transparency or it wont work
When you bake occlusion culling, it builds its model only from objects which are static and opaque. So implementing occ culling in a scene which hasnt been optimized in that way won't yield the expected benefit
Occlusion Culling is more effective in games with a lot of line-sight obstructions, so ymmv. But if your scene is optimized in this way already, it's highly recommended.
The last one Im going to mention is atlasing textures. Basically just putting a bunch of textures into a single image to reduce the number of materials.
Seems boring, but the benefits are no joke.
For example, say u have a model which uses 4 materials.
If you combining all 4 textures into 1 big texture (and 1 material) it will reduce the draw calls for that renderer from 4 to 1
Now imagine you have 100 of these in the scene; u just saved 300 draw calls/ frame
Anyhow, Thanks for reading. I hope all this is useful to someone.

If u know a good, relatively-accessible optimization that I didn't mention, feel free to drop it in the comments ā™„
Adding an addendum about object references bc its important. Id avoided it to keep coding-talk to a minimum, but lets go:
Getting a GameObject by tag is slow, and getting it by name is very very slow. I never ever use these and you shouldnt either. There are better coding patterns to get references~
On the other hand, GetComponent is very fast, and GetComponentInChildren isnt terrible. But if you are doing any of these functions in your Update function, you NEED to cache the references to the greatest extent possible
since some ppl dont know what that looks like:

OtherClass myRef;

void Awake( )
{
myRef=GetComponent<OtherClass >();
myRef.DoSomething();
}
So the most crucial and basic reference optimization is to move as much reference-getting as u can out of Update and into the Awake event.

Especially when it comes to tag lookups and name lookups, which are vastly more expensive than GetComponent
also worth noting:
MyScript.transform is just a shorthand! what you're really doing is MyScript.GetComponent<Transform>()

Same the with MyScript.gameObject

GetComponent is FAST, but if you're doing it many times per update across many objects, it can add up
So for objects which access transform many times every update (e.g. character movement) I add:
---------------
private Transform tr;

void Awake{
tr=transform;
}
---------------
And then replace the word 'transform' with 'tr' throughout the code
Anyways, caching stuff on awake is good, but if you use name / tag lookups you will get a spike on object creation.

This can be a big contributor first-frame spikes, and also big problem for framerate stability if u not pooling objects

There's other ways to get those refs: ~
So generally there are 2 times u look up objects by name/tag/component:
[1] bc it is some kind of single-instance object (e.g. a manager class or the player character)
[2] to get all instances of a certain kind of object that exist right now (or to check if any exist)
in case [1], it's a bit easier.

As before, you make a reference, but this time its a public static reference instead.

A static variable is one which belongs to the class as a whole, rather than any instance of that class.

They're extremely useful in Unity for many things
Here's what it looks like:
------------
class MyClass : MonoBehaviour{
public static MyClass instance;

void Awake() {
instance=this;
}
}
------------
now if you type MyClass.instance in another script it will give you the reference
This only works when there is precisely 1 instance of the object, so it's a good idea to enforce this limit.
Easiest way is to destroy the old instance when a new one is made:
----------
void Awake()
{
if (instance)
Destroy (instance.gameObject);

instance=this;
}
On to the [2] situation (when u want to access all instances of a certain component, for example)

There are many ways to do this, but Im going to show you a very easy and versatile solution~
You can do the same basic strategy as for [1], but instead of a static reference, you have a static *list of references*.

On object creation, add a ref to the list, or destruction, remove it. That's literally all.

example below:
class MyClass: MonoBehaviour
{
public static List<MyClass> instances=new List<MyClass>();

public OnEnable()
{
instances.Add(this);
}

public OnDisable()
{
instances.Remove(this);
}

}
Now any other script can do something like:

---------
if (MyClass.instances.Count>0)
Debug.Log("MyClass is in the scene");
---------

~or~

---------
for (int i =0; i< MyClass.instances.Count; i++)
{
MyClass.instances[i].CommitMurder();
}
So to tie it all back, the cleanest way to avoid using name/type/tag lookups is with static references.
There are other ways to handle this stuff, such as having manager objects which deal with the references.

Knowing your own use-case, you may be able to do something better suited. But these are generic best-practices, and tbqh you rarely need more.
Anyways, Ill close with one last short, unrelated tip:

code thats only intended for the editor will still run in the standalone unless you put
"
#if UNITY_EDITOR
#endif
"
around it.

common editor functions like Debug.Log can easily impact standalone performance.
this also includes things like custom editor visualization (gizmos)

Although Unity will not *draw* the gizmos in standalone, it will still do the code leading up to that, which usually involves a lot of vectors and stuff.

so those should also be cordoned off w #if UNITY_EDITOR
That's all, Thanks for making it this far.
I really hope this stuff is helpful.
Ź• ^ā€¢^Ź”šŸ’•
P.S. trans liberation now

ā€¢ ā€¢ ā€¢

Missing some Tweet in this thread? You can try to force a refresh
怀

Keep Current with Aaron Taecker-Wyss šŸ‘½

Aaron Taecker-Wyss šŸ‘½ Profile picture

Stay in touch and get notified when new unrolls are available from this author!

Read all threads

This Thread may be Removed Anytime!

PDF

Twitter may remove this content at anytime! Save it as PDF for later use!

Try unrolling a thread yourself!

how to unroll video
  1. Follow @ThreadReaderApp to mention us!

  2. From a Twitter thread mention us with a keyword "unroll"
@threadreaderapp unroll

Practice here first or read more on our help page!

Did Thread Reader help you today?

Support us! We are indie developers!


This site is made by just two indie developers on a laptop doing marketing, support and development! Read more about the story.

Become a Premium Member ($3/month or $30/year) and get exclusive features!

Become Premium

Don't want to be a Premium member but still want to support us?

Make a small donation by buying us coffee ($5) or help with server cost ($10)

Donate via Paypal

Or Donate anonymously using crypto!

Ethereum

0xfe58350B80634f60Fa6Dc149a72b4DFbc17D341E copy

Bitcoin

3ATGMxNzCUFzxpMCHL5sWSt4DVtS8UqXpi copy

Thank you for your support!

Follow Us on Twitter!

:(