Overview: Performance Optimization
1. Use Static Typing
When using JavaScript the most important optimization is to use static typing instead of dynamic typing.
Unity uses a technique called type inference to automatically convert JavaScript constructs to statically typed code without you having to do any work.
using UnityEngine;
using System.Collections;
public class example :
MonoBehaviour {
public int foo = 5;
}
import UnityEngine
import System.Collections
class example(
MonoBehaviour):
public foo as
int = 5
In the above example
foo will automatically be inferred to be an integer value. Thus Unity can apply a lot of compile time optimizations, without costly dynamic name variable lookups etc.
This is one of the reasons why Unity's JavaScript is on average around 20 times faster than other JavaScript implementations.
The only problem is that sometimes not everything can be type inferred, thus Unity will fall back to dynamic typing for those variables.
By falling back to dynamic typing, writing JavaScript code is simpler. However it also makes the code run slower.
Let's see some examples.
function Start () {
var foo = GetComponent(MyScript);
foo.DoSomething();
}
using UnityEngine;
using System.Collections;
public class example :
MonoBehaviour {
void Start() {
System.
Object foo = GetComponent<MyScript>();
foo.DoSomething();
}
}
import UnityEngine
import System.Collections
class example(
MonoBehaviour):
def
Start():
foo as System.
Object = GetComponent[of MyScript]()
foo.DoSomething()
Here
foo will be dynamically typed, thus calling the function DoSomething takes longer than necessary - because the type of
foo is unknown,
it has to figure out whether it supports
DoSomething function, and if it does, invoke that function.
function Start () {
var foo : MyScript = GetComponent(MyScript);
foo.DoSomething();
}
using UnityEngine;
using System.Collections;
public class example :
MonoBehaviour {
void Start() {
MyScript foo = GetComponent<MyScript>();
foo.DoSomething();
}
}
import UnityEngine
import System.Collections
class example(
MonoBehaviour):
def
Start():
foo as MyScript = GetComponent[of MyScript]()
foo.DoSomething()
Here we're forcing
foo to be of specific type. You will get much better performance.
2. Use #pragma strict
Now the problem is of course, that you don't usually notice when you are using dynamic typing. #pragma strict to the rescue!
Simply add #pragma strict at the top of a script and Unity will disable dynamic typing in that script, forcing you to use static typing. Wherever a type is not known, Unity will report compile errors.
So in this case, foo will produce an error when compiling:
#pragma strict
function Start () {
var foo : MyScript = GetComponent(MyScript) as MyScript;
foo.DoSomething();
}
using UnityEngine;
using System.Collections;
public class example :
MonoBehaviour {
void Start() {
MyScript foo = GetComponent<MyScript>() as MyScript;
foo.DoSomething();
}
}
import UnityEngine
import System.Collections
class example(
MonoBehaviour):
def
Start():
foo as MyScript = GetComponent[of MyScript]()
foo.DoSomething()
3. Cache component lookups
Another optimization is caching of components. This optimization unfortunately requires a bit of coding effort and is not always worth it.
But if your script is really used a lot and you need to get the last bit of performance out of it, this can be a very good optimization.
Whenever you access a component through GetComponent or an accessor variable, Unity has to find the right component from the game object.
This time can easily be saved by caching a reference to the component in a private variable.
Simply turn this:
function Update () {
transform.Translate(0, 0, 5);
}
using UnityEngine;
using System.Collections;
public class example :
MonoBehaviour {
void Update() {
transform.Translate(0, 0, 5);
}
}
import UnityEngine
import System.Collections
class example(
MonoBehaviour):
def
Update():
transform.Translate(0, 0, 5)
Into this:
private var myTransform :
Transform;
function Awake () {
myTransform = transform;
}
function Update () {
myTransform.Translate(0, 0, 5);
}
using UnityEngine;
using System.Collections;
public class example :
MonoBehaviour {
private Transform myTransform;
void Awake() {
myTransform = transform;
}
void Update() {
myTransform.Translate(0, 0, 5);
}
}
import UnityEngine
import System.Collections
class example(
MonoBehaviour):
private myTransform as
Transform def
Awake():
myTransform = transform
def
Update():
myTransform.Translate(0, 0, 5)
The latter code will run a lot faster since Unity doesn't have to find the transform component in the game object each frame.
The same applies for scripted components, where you use GetComponent instead of the transform or other shorthand property.
4. Use Builtin arrays
Builtin arrays are fast, very fast, so use them.
While the ArrayList or Array classes are easier to use since you can easily add elements they don't have nearly the same speed.
Builtin arrays have a fixed size but most of the time you know the maximum size in advance and can just fill it out later.
The best thing about builtin arrays is that they directly embed struct data types in one tightly packed buffer, without any extra type information or other overhead.
Thus iterating through is very easy on the cache as everything is linear in memory.
private var positions :
Vector3[];
function Awake () {
positions =
new Vector3[100];
for (
var i = 0; i < 100; i++)
positions[i] =
Vector3.zero;
}
using UnityEngine;
using System.Collections;
public class example :
MonoBehaviour {
private Vector3[] positions;
void Awake() {
positions =
new Vector3[100];
int i = 0;
while (i < 100) {
positions[i] =
Vector3.zero;
i++;
}
}
}
import UnityEngine
import System.Collections
class example(
MonoBehaviour):
private positions as (
Vector3)
def
Awake():
positions = array[of
Vector3](100)
i as
int = 0
while i < 100:
positions[i] =
Vector3.zero i++
5. Don't call a function if you don't have to
The simplest and best of all optimizations is to perform less work.
For example , when an enemy is far away it is most of the time perfectly acceptable to have the enemy fall asleep. That is do nothing until the player comes close.
The slow way of handling this situation would be:
using UnityEngine;
using System.Collections;
public class example :
MonoBehaviour {
public Transform target;
void Update() {
if (
Vector3.Distance(transform.position, target.position) > 100)
return;
}
}
This is not a good idea since Unity has to invoke the update function and you are performing work every frame. A better solution is to disabling the behaviour until the player comes closer.
There are 3 ways to do this:
1. Use OnBecameVisible and OnBecameInvisible. These call backs are tied into the rendering system. As soon as any camera can see the object, OnBecameVisible will be called, when no camera sees it anymore OnBecameInvisible will be called. This is useful in some cases, but often for AI it is not useful because enemies would become disabled as soon as you turn the camera away from them.
function OnBecameVisible () {
enabled = true;
}
function OnBecameInvisible () {
enabled =
false;
}
using UnityEngine;
using System.Collections;
public class example :
MonoBehaviour {
void OnBecameVisible() {
enabled = true;
}
void OnBecameInvisible() {
enabled =
false;
}
}
import UnityEngine
import System.Collections
class example(
MonoBehaviour):
def
OnBecameVisible():
enabled = true
def
OnBecameInvisible():
enabled =
false
2. Use triggers. A simple sphere trigger can work wonders though. You get OnTriggerEnter/Exit calls when exiting the sphere of influence you want
function OnTriggerEnter (c :
Collider) {
if (c.CompareTag(
"Player"))
enabled = true;
}
function OnTriggerExit (c :
Collider) {
if (c.CompareTag(
"Player"))
enabled =
false;
}
using UnityEngine;
using System.Collections;
public class example :
MonoBehaviour {
void OnTriggerEnter(
Collider c) {
if (c.CompareTag(
"Player"))
enabled = true;
}
void OnTriggerExit(
Collider c) {
if (c.CompareTag(
"Player"))
enabled =
false;
}
}
import UnityEngine
import System.Collections
class example(
MonoBehaviour):
def
OnTriggerEnter(c as
Collider):
if c.CompareTag('Player'):
enabled = true
def
OnTriggerExit(c as
Collider):
if c.CompareTag('Player'):
enabled =
false
3. Use Coroutines. The problem with Update calls is that they happen every frame. Quite possibly checking the distance to the player could be performed only every 5 seconds. This would save a lot of processing power.