Thursday, November 15, 2012

Managing Game Objects with Unity

Dear Unity Developers,

I have more than 1000 views on my blog now! Thank you everyone. 

I've come across improper use Unity's GameObject.Find() so many times that I wanted to share the best ways to deal with this. Unity has ways to avoid this completely thus increasing performance in your code. I started typing this guide for my project teams, and I just thought it would be helpful for everyone to read. It uses code snippets from two projects.

Please, if you don't have time to read it now, do read it and make sure to fully understand it. E-mail any questions!! We're all studying game development, and I'm just trying to help. Also, I think that soon I'll be moving this blog over to my own personal site in order to offer code highlighting.

First, I'll show an example of a standard problem that I encounter in the file
//EnemyManagerClient.cs
public GameObject[] enemies = new GameObject[2];
void Update()
{
     Vector3 temp = GameObject.Find("Panda P1").transform.position 
              - enemies[0].transform.position;
}
You'll notice in this line that enemies[0] does not have to be "found" by Update(). This mean the object doesn't have to be found each time this statement is executed.

Every time your code exceutes GameObject.Find(), Unity has to look through all of your object. You should really store the reference to that object, and then your game object will act as a pointer. The least you could do is code it like this. Start is run one time before your first call to update,

public GameObject[] enemies = new GameObject[2];
private GameObject panda1;

void Start()
{
   panda1 = GameObject.Find("Panda P1")
}

void Update()
{
     Vector3 temp = panda1.transform.position 
            - enemies[0].transform.position;
}
That's way better!! You can also set game objects object in 3 additional ways.


1. Store the reference with the Unity Inspector. This works for objects in the hierarchy that want to reference a prefab or hierarchy object. It is the most effective way.

(extra caution, A script that is attached to a prefab will not be able to store a reference to an object in the hierarchy: use method 2 or 3)

Here's how. Add the line "public GameObject" panda1 to the top of the code. Compile. Look at the object that has the EnemyManagerClient component on it in the hierarchy (using Unity's Inspector). It now has a blank spot to which you can drag the object from the hierarchy. Doing so will give the component a permanent reference to the game object. Save the scene. Now, you never have to run find in the code. This is what was done with enemies[0]. You could drag the Panda 1 game object into enemies[1].

2. To maintain OOP, you need a way to refer to class scripts that are stored on other game objects as components.

//here is the wrong way to access variables in your script
void SendPacket()
{
     float xVelClient1 = 
            client1.GetComponent().velocity.x;
}

//Fritzie has already stored a reference to this 
//ProfMoveScript in this file. At the top we've added:
private ProfMoveScript profScript1;
void Start()
{
     //...
     profScript1 = client1.GetComponent();
}
This reference to client1 (Panda P1) was set by the same method as in example 1. (dragging and dropping in the unity editor )

3. Use this method if an object will be created dynamically and then referenced in an update or function. Prefab components can only store refs to things on the prefab. Attempts to get a component from a null object will throw a null reference exception.

This is a modified version of a way that I get a Networked animation script from a dynamically created player object in Club Consortya. This is similar to how we will want to get the references to the enemies in Panda so that we can then call their functions and edit their healths.

//There is a player script that has to be initialized 
//once the user is created
private bool initalized = false;
private GameObject user;

//Declare your script object
NetworkedAnimation networkedAnimation;

void Update ()
{
    if(!initialized)
    {
         Initialize();
    }
    else
    {
        networkedAnimation.Sync();
    }
}

void Initialize()
{
    //First you have to store a ref to the user
    if(user == null)
    {
      user = GameObject.FindGameObjectWithTag("Player");
    }

    //Only if you have the user can you get a component from it
    //otherwise, you will get a null reference exception    
    else if(user != null && networkedAnimation == null)
    {
        networkedAnimation = user.GetComponent("   NetworkedAnimation") 
              as NetworkedAnimation;
    }
    //Now see if it's all good to go, and then you won't have to 
    //run the initialize code in update anymore
    else if(user != null && networkedAnimation != null)
    {
        initialized = true;   
    }
}
And that does it. You can use this method to really make your code look beautiful. Next post will be about using a singleton reference. Let me know what you want to hear about in the comments below.