Frank Moricz


Web Portfolio and Art Gallery

Magic made with caffeine and a keyboard.

Megabite #16 - Better Scripting Through Invoke-ation

When I first began with Unity, I noticed a great deal of things in tutorials were coded within the Update() section of a script.  As you may already know, this portion of a script will execute once per frame.  Depending on the framerate of a user, this could be a good thing or a bad thing.  If anything, it's unpredictable at best and taxing on a machine if you have a piece of complex code within.

The option also exists for FixedUpdate(), which is more predictable but can have a tendency to be just as taxing for complex code.  What do we do when we want to run something every second or so instead of every rendering frame or according to the physics timescale?  For that, we use the Invoke methods.

//This script will call our TestFunction() every 2 seconds
function Start() {
   InvokeRepeating("TestFunction", 2, 2);
} 

function TestFunction() {
print("Test function run at: " + Time.time);
}

In combination with CancelInvoke(), this method is far less taxing overall, especially if you're using complex code.  As opposed to Update, if we run the code once per second and the game is running at 60fps, we've just saved our machine from running the code 59 extra times.  If you have some type of loop in the function itself or anything that would have drained resources, this can be a godsend.

Invoke() itself has a great functionality as well, and can be used in conjunction with things like CancelInvoke() or isInvoking().  Invoke() allows you to also set a delay on a function without the hassle of using yield in the first line of code, and it gives you more versatility overall with the possibility of stopping the code from executing if needed.  For example:

//This script does something like a megaman-type cannon effect.  The user has to hold down the space key for 3 seconds, uninterrupted by enemy fire.  If interrupted, the fire functions will cancel out.
private var chargedUp : boolean = false;
var chargeAnimation : Transform;
var gunTip : Transform;
function Update() {
  If (Input.GetKey("space") && !isInvoking("FireCannon") && !chargedUp) {
     ChargeGun();
     Invoke("FireCannon", 3);
  }
} 
function ChargeGun() {
     Instantiate(chargeAnimation, gunTip.transform.position, Quaternion.identity);
     Invoke("ReadyCharge", 3);
}
function ReadyCharge () {
chargedUp = true;
}
function FireCannon() {
     if (Input.GetKey("space") && chargedUp == true) {
       Fire();
     }
}
function OnCollisionEnter (other : Collider) {
     if (other.transform.tag == "enemyBullet") {
       if (isInvoking("FireCannon")) {
         CancelInvoke("FireCannon");
       }
       if (GameObject.FindGameObjectWithTag("chargeAnimation")) {
         var theAni : Transform = GameObject.FindGameObjectWithTag("chargeAnimation");
         Destroy(theAni.gameObject);
         CancelInvoke("ReadyCharge");
       }
     }
}

Because of the isInvoking() directive, the above script is responsive immediately but will only execute once if the conditions are met.  Obviously the Fire() function would reset chargedUp to false as well as actually launch the projectile, but the innovative nature of isInvoking() allowed us to also set the rate of refire unless we decided on extra delay between shots.

For things such as general movement, the Update section of script is safe enough to use.  The same can be said regarding camera movement or a few other common features.  In truth, if your project is small you may not even see the difference in performance for a PC/Mac or Web build because of the powerful processors found in most gaming machines.  Where you may see the most benefit in something like this is in mobile development or in large projects with a lot going on at once.  Even so, it's best practice to try and write your code to not do anything unnecessary.  That way, additions to your game will have more available resources and you can do more at once.

With things available such as InvokeRepeating(), you might immediately be able to move code from Update() into its own function and set a delay of your choosing.  Even if you execute once every 0.1 seconds, you should immediately have a performance increase of that script section of at least 200% (at 30fps, which is low).  InvokeRepeating can be launched from the Start() of a script, or from another function if needed.

As a side note, simply running the command CancelInvoke() without adding the name of a function will actually cancel all invocations on the current script.  After learning these commands and really starting to work with them, I can safely say there is a lot of my old code that could be better revised.  Learning these as soon as possible will likely safe you a great deal of time and energy.  :)