Unity or Unity3D Best Practices and Tips by Toptal Developers

This resource contains a collection of Unity or Unity3D best practices and tips provided by our Toptal network members, and will be updated regularly with additional information and emerging Unity techniques. This is a community-driven project, so we encourage you to contribute as well, and we are counting on your feedback.

Unity is a cross-platform game engine, and here we will embrace Unity and Unity3D best practices that will make you a better game developer.

Check out the Toptal resource pages for additional information on Unity or Unity3D interview questions.

Using a Material Pool to Avoid Excessive Instancing

In another tip here, Eduardo Dias da Costa showed us how to customize materials for different game objects. This is a great practice that can save a lot of work in creating the materials. However, in cases where the objects will frequently be generated and later destroyed, this technique could lead to leaking, as Unity generates a new instance of the material every time it’s modified and assigned to a Mesh Renderer. In this case, if you will reuse materials with the same parameters, it’s useful to use a Pool of Materials. I usually start with this generic class, and later customize if needed:

    /// <summary>
    /// Generic material pool
    /// </summary>
    /// <typeparam name="T">A class or struct that contains the parameters for identifying and constructing a new material</typeparam>
    public class MaterialPool<T> {

        // The definition of a function that given a T will return a new material.
        // This is used when the material is inserted in the pool for the first time.
        public delegate Material MaterialGenerator(T t);

        private Dictionary<T, Material> pool;
        private MaterialGenerator generator;

        public MaterialPool(MaterialGenerator generator) {
            this.pool = new Dictionary<T, Material>();
            this.generator = generator;
        }

        public Material GetMaterial(T t) {
            Material mat;
            if (!pool.TryGetValue(t, out mat)) {
                mat = generator(t);
                pool[t] = mat;
            }
            return mat;
        }
    }

It requires two additional, customized, elements: a material generator function, and a type that provides the parameters for customizing the material:

/// be careful when overriding both Equals and GetHashCode methods so that it will work correctly
/// as a key in the dictionary
struct MaterialDef {
...
}

/// generate a new material from the definition
private Material MaterialGenerator(MaterialDef matDef) {
...
}

/// in Start or Awake instance the material pool
    matPool = new MaterialPool<MaterialDef>(MaterialGenerator);
/// later in the code, when needing a new material, just request it from the pool
gameObject.GetComponent<MeshRenderer>().material = matPool.GetMaterial(new MaterialDef( ... ));

A big benefit of this approach is that it enables you to separate different concerns:

  1. The pooling.
  2. The identification of what makes one material different from the other.
  3. The material generation itself.

You can replace each one of these without worrying (much) about the others. Another advantage of this approach is that it allows you just to query for a material from the pool without caring whether it will generate a new one or reuse an existing one.

Contributors

Like what you're reading?
Get the latest updates first.
No spam. Just great engineering and design posts.
Like what you're reading?
Get the latest updates first.
Thank you for subscribing!
You can edit your subscription preferences here.

Efficient Ray Collision Detection Against a Single Object

Rays are very useful in Unity for implementing collisions and UI interaction, among other things. A typical way to use them is provided by the official documentation:

    RaycastHit hit;
    float distanceToGround = 0;

    if (Physics.Raycast(transform.position, -Vector3.up, out hit, 100.0F)) {
	    // Determine which object is using hit.collider and do something with it
    }

The equivalent for detecting all objects hit is:

    RaycastHit[] hits;
    hits = Physics.RaycastAll(transform.position, transform.forward, 100.0F);

However, what if we just want to see if a specific object is in the way of the ray, without going through a list of them, and we are not sure (or don’t care) if it’s a hit? We can check specifically for a single object by starting from its bounds instead of from the Ray, using the Bounds.IntersectsRay method:

        Ray ray = ... // our ray
	GameObject go = ... // our game object
	Collider collider = go.GetComponent();
	if (collider.bounds.IntersectsRay(ray)) {
		// object hit!
	}

This can also be used with the Bounds object provided by a MeshRenderer.

Contributors

Like what you're reading?
Get the latest updates first.
No spam. Just great engineering and design posts.
Like what you're reading?
Get the latest updates first.
Thank you for subscribing!
You can edit your subscription preferences here.

How can I access all Elements of a hierarchy using The “Depth-first search” algorithm?

Sometimes, developers need to find or test the elements that are inside of a complex structure made of intricate transforms relationships. To find or test the desired element, it is necessary to visit all the nodes of the mentioned structure.

Usually, transforms are organized as a complex tree data structure, and one of the most common algorithms for visiting all tree nodes is the Depth-first search. This algorithm recursively visits all nodes prioritizing the innermost ones, from left to right.

using System;

//Visits all nodes using the DepthFirstSearch algorithm calling ‘p_callback’ on each visit.
public bool TraverseDFS(Transform p_root,Predicate<Transform> p_callback)
{

  //’Predicate’ is a C# delegate that accepts one parameter and returns a ‘bool’
  //We can use this ‘bool’ it to check if the user wants to keep searching the tree.
  if(!p_callback(p_root))
  {
    //The desired query was found and we can stop searching.
    return false;
  }

  for(int i=0;i<p_root.childCount;i++) 
  { 
    if(!TraverseDFS(p_root.GetChild(i),p_callback))
    {
      //Stop searching
      return false; 
    }
  }

  //Keep searching
  return true;
}

Contributors

Like what you're reading?
Get the latest updates first.
No spam. Just great engineering and design posts.
Like what you're reading?
Get the latest updates first.
Thank you for subscribing!
You can edit your subscription preferences here.

How to move objects towards the desired position with constant and/or variable rates in a defined time frame?

Things in games must move. It is just a matter of speed, acceleration, and time.

The most common methods for moving things outside the physics loop of Unity is using MoveTowards and Lerp.

If you want to move things with constant speed, MoveTowards increments your position with a constant rate for each frame.

MoveTowards

//Constant Speed
Vector3 position;
Vector3 target;
float speed;
void Update()
{
  position = Vector3.MoveTowards(position,target,Time.deltaTime * speed);
}

To move things with the feel of acceleration one must use Lerp. The effect we get is caused because the next position is a percentage of the remaining distance. So, the first steps are bigger than the last ones because the remaining distance keeps getting shorter.

Lerp

//Variable Speed
Vector3 position;
Vector3 target;
float speed;
void Update()
{
  position = Vector3.Lerp(position,target,Time.deltaTime * speed);
}

The interesting part here is that these equations operate in numbers, and considering that quaternions (rotations), colors, rectangles and other Math structures have the same composition, it can be seen that everything can be interpolated using this technique. For instance, fading or sliding screens and rotating objects are other use cases of it.

Contributors

Like what you're reading?
Get the latest updates first.
No spam. Just great engineering and design posts.
Like what you're reading?
Get the latest updates first.
Thank you for subscribing!
You can edit your subscription preferences here.

How to correctly destroy an item from the scene?

Somewhere in the gameplay, your player eliminated a monster or picked an item. Now your code must remove those instances from the scene.

New developers usually mistake the gameObject’s components, such as the Transform and the attached MonoBehaviours as the main instance in the scene.

//Reference to the scripts
MonsterScript monster;
ItemScript item;

void OnPlayerWin()
{
    //Process score.
    Destroy(monster); //Will destroy the monster’s script only and the monster will be on scene. 
}

void OnPlayerGetItem()
{
    //Process item.
    Destroy(item); //Will destroy the item’s script only and the item will be on scene. 
}

Every component in the Unity API has a reference to its gameObject, which is the element containing all scripts and components related to a game element.

//Reference to the scripts
MonsterScript monster;
ItemScript item;

void OnPlayerWin()
{
    //Process score.
    Destroy(monster.gameObject); //Will destroy the monster’s entire instance.
}

void OnPlayerGetItem()
{
    //Process item.
    Destroy(item.gameObject); //Will destroy the item’s entire instance.
}

The knowledge to differentiate between a gameObject and its components is crucial to avoid unwanted behaviour in the key parts of your gameplay.

Yet, sometimes, the objective is to actually kill a given script to open a slot for another one. One example is changing between AI behaviours.

GameObject monster;

void OnPlayerClose()
{
    AIScriptIdle ai = monster.GetComponent<AIScriptIdle>(); //Gets the current AI instance
    if(ai) Destroy(ai); //If it exists, destroy.
    monster.AddComponent<AIScriptAttack>(); //Adds the Attack AI Script.
}

void OnPlayerFar()
{
    AIScriptAttack ai = monster.GetComponent<AIScriptAttack >(); //Gets the current AI instance
    if(ai) Destroy(ai);//If it exists, destroy.
    monster.AddComponent<AIScriptIdle>(); //Adds the Idle AI script.
}

Contributors

Like what you're reading?
Get the latest updates first.
No spam. Just great engineering and design posts.
Like what you're reading?
Get the latest updates first.
Thank you for subscribing!
You can edit your subscription preferences here.

How to customize a material on runtime exclusively for its GameObject?

Sometimes you have one material that is configured to render your character with the correct shader and parameters; but your game could have a great number of characters with different textures and parameters for each.

Usually, one would create one material for each. However, if at some point the base material needs its shader, textures or parameters changed, you would need to update all previously created ones.

One way to avoid that is to have one material for all characters and store the parameters and textures in the character script.

//Character.cs

Texture2D skin; //Reference to the character skin texture.
Color tint;     //Some tint parameter for the shader.

void Start()
{
    Material m = GetComponent<Renderer>().sharedMaterial; //Get the renderer material reference.
    m.color = tint;       //Change the shader color parameter to the character’s.
    m.mainTexture = skin; //Change the skin texture to the character’s.
}

Pretty easy? However, there is a catch. To simplify the workflow, we had only one material for all characters. So, if someone changes the material attributes, all characters would be affected.

To avoid this, you must duplicate the material instance as soon as the game starts and make it exclusive to that character.

//Character.cs

Texture2D skin; //Reference to the character skin texture.
Color tint;     //Some tint parameter for the shader.

void Start()
{
    Material m = GetComponent<Renderer>().sharedMaterial; //Get the renderer material reference.
    m = Instantiate<Material>(m);                         //Duplicate the original
    m.color = tint;       //Change the shader color parameter to the character’s.
    m.mainTexture = skin; //Change the skin texture to the character’s.
    GetComponent<Renderer>().sharedMaterial = m; //Assign the new material only for this character.
}

Contributors

Submit a tip

Fields marked with an asterisk (*) are required
Thanks for submitting your tip proposal
Our editorial staff will review it shortly. Please note that tips proposals are subject to review and editing, and may or may not be selected for posting, at the sole discretion of Toptal, LLC.