Easy Learn C#

Working with GameObjects

Introduction to GameObjects

GameObjects are the fundamental objects in Unity that represent characters, props, scenery, cameras, lights, and more. Every object in your game is a GameObject. Understanding how to work with GameObjects is essential for Unity development.

In this guide, you'll learn:

  • What GameObjects are and how they work
  • Creating and manipulating GameObjects in code
  • Working with parent-child relationships
  • Finding and referencing GameObjects
  • Enabling, disabling, and destroying GameObjects
  • Best practices for GameObject management

Understanding GameObjects

A GameObject is a container that can hold Components. By itself, a GameObject doesn't do anything—it needs Components to define its behavior and appearance.

Key Concepts:

  • Empty GameObject: A container with just a Transform component
  • Components: Add functionality to GameObjects (e.g., Renderer, Collider, Script)
  • Transform: Every GameObject has a Transform component for position, rotation, and scale
  • Hierarchy: GameObjects can have parent-child relationships
  • Prefabs: Reusable GameObject templates
  • Tags and Layers: Used to identify and categorize GameObjects

Creating GameObjects in Code

While you can create GameObjects in the Unity Editor, sometimes you need to create them programmatically during gameplay.

Creating GameObjects:


using UnityEngine;

public class GameObjectCreator : MonoBehaviour
{
    // Reference to a prefab that will be instantiated
    public GameObject prefabToSpawn;
    
    void Start()
    {
        // Method 1: Create an empty GameObject
        GameObject emptyObject = new GameObject("Empty Object");
        // Add a comment to explain what this code does
        // This creates a new, empty GameObject with just a Transform component
        
        // Method 2: Create a GameObject with components
        GameObject cubeObject = GameObject.CreatePrimitive(PrimitiveType.Cube);
        cubeObject.name = "Programmatic Cube";
        cubeObject.transform.position = new Vector3(0, 2, 0);
        // This creates a cube primitive at position (0,2,0) with mesh, collider, and renderer components
        
        // Method 3: Instantiate from a prefab
        if (prefabToSpawn != null)
        {
            // Instantiate at position (2,0,0) with default rotation
            GameObject newInstance = Instantiate(prefabToSpawn, new Vector3(2, 0, 0), Quaternion.identity);
            newInstance.name = "Prefab Instance";
            // This creates a copy of the prefab at position (2,0,0)
        }
        
        // Add a custom component to our empty object
        emptyObject.AddComponent();
        // This adds physics behavior to the empty object
        
        // Add a custom script component
        ExampleBehavior behavior = emptyObject.AddComponent();
        behavior.someValue = 42;
        // This adds our custom script and sets a value on it
    }
}

// Example custom component
public class ExampleBehavior : MonoBehaviour
{
    public int someValue;
    
    void Start()
    {
        Debug.Log($"ExampleBehavior started with value: {someValue}");
    }
}
                            

Ways to create GameObjects:

  • new GameObject() - Creates an empty GameObject with just a Transform
  • GameObject.CreatePrimitive() - Creates basic shapes with renderers and colliders
  • Instantiate() - Creates a copy of an existing GameObject or prefab
  • AddComponent<T>() - Adds components to GameObjects after creation

Working with GameObject Transform

The Transform component determines the position, rotation, and scale of a GameObject in the scene. Every GameObject has a Transform component.

Manipulating Transforms:


using UnityEngine;

public class TransformManipulator : MonoBehaviour
{
    // Reference to another GameObject
    public GameObject targetObject;
    
    void Start()
    {
        if (targetObject == null)
            return;
            
        // POSITION MANIPULATION
        
        // Set absolute position
        targetObject.transform.position = new Vector3(5, 0, 0);
        // This teleports the object to position (5,0,0) in world space
        
        // Change position relative to current position
        targetObject.transform.position += new Vector3(0, 1, 0);
        // This moves the object up 1 unit from its current position
        
        // Use Translate for relative movement
        targetObject.transform.Translate(new Vector3(0, 0, 2));
        // This moves the object forward 2 units in its local Z-axis
        
        // ROTATION MANIPULATION
        
        // Set absolute rotation (using Euler angles for readability)
        targetObject.transform.rotation = Quaternion.Euler(0, 90, 0);
        // This rotates the object to face 90 degrees on the Y-axis
        
        // Rotate relative to current rotation
        targetObject.transform.Rotate(0, 45, 0);
        // This rotates the object 45 more degrees around the Y-axis
        
        // Look at another position
        targetObject.transform.LookAt(transform.position);
        // This makes the target object face this script's GameObject
        
        // SCALE MANIPULATION
        
        // Set absolute scale
        targetObject.transform.localScale = new Vector3(2, 2, 2);
        // This makes the object twice as large in all dimensions
        
        // Scale relative to current scale
        targetObject.transform.localScale *= 1.5f;
        // This makes the object 1.5 times larger than its current scale
    }
    
    void Update()
    {
        if (targetObject == null)
            return;
            
        // Smoothly rotate the target over time
        targetObject.transform.Rotate(0, 30 * Time.deltaTime, 0);
        // This continuously rotates the object at 30 degrees per second
    }
}
                            

Transform operations:

  • transform.position - Sets/gets world position
  • transform.localPosition - Sets/gets position relative to parent
  • transform.Translate() - Moves relative to current orientation
  • transform.rotation - Sets/gets world rotation as Quaternion
  • transform.Rotate() - Rotates relative to current orientation
  • transform.LookAt() - Rotates to face a target
  • transform.localScale - Sets/gets scale relative to parent

Local vs. World Space

Unity uses two coordinate systems:

  • World Space: Global coordinates relative to the scene origin
  • Local Space: Coordinates relative to a GameObject's parent

Use transform.position for world space and transform.localPosition for local space.

Parent-Child Relationships

GameObjects can be organized in hierarchies with parent-child relationships. When a parent moves, rotates, or scales, all its children follow.

Working with Parent-Child Hierarchies:


using UnityEngine;

public class ParentChildDemo : MonoBehaviour
{
    public GameObject childPrefab;
    
    void Start()
    {
        // Create a parent object
        GameObject parent = new GameObject("Parent");
        // This creates an empty GameObject that will serve as a parent
        
        // Create three child objects
        for (int i = 0; i < 3; i++)
        {
            // Create a cube
            GameObject child = GameObject.CreatePrimitive(PrimitiveType.Cube);
            
            // Name the child
            child.name = $"Child_{i}";
            
            // Set the parent relationship
            child.transform.SetParent(parent.transform);
            // This makes the cube a child of the parent object
            
            // Position the child relative to the parent (using local coordinates)
            child.transform.localPosition = new Vector3(i * 2, 0, 0);
            // This positions the cubes in a row, each 2 units apart from each other
            
            // Scale the child
            child.transform.localScale = new Vector3(0.5f, 0.5f, 0.5f);
            // Makes each child cube half the default size
        }
        
        // Alternative way to set a parent
        if (childPrefab != null)
        {
            GameObject specialChild = Instantiate(childPrefab, parent.transform);
            // This instantiates the prefab and sets the parent in one step
            
            specialChild.transform.localPosition = new Vector3(0, 2, 0);
            // Position the special child above the other children
        }
        
        // Accessing children
        int childCount = parent.transform.childCount;
        Debug.Log($"Parent has {childCount} children");
        
        // Get a specific child by index
        if (childCount > 0)
        {
            Transform firstChild = parent.transform.GetChild(0);
            Debug.Log($"First child is {firstChild.name}");
            
            // Remove a child from the parent (without destroying the child)
            firstChild.SetParent(null);
            // This makes the first child independent (no parent)
            
            // Add it back with a different method
            firstChild.parent = parent.transform;
            // This reattaches the child to the parent
        }
    }
    
    void Update()
    {
        // Find the parent we created
        GameObject parent = GameObject.Find("Parent");
        if (parent != null)
        {
            // Rotate the parent - all children will rotate with it
            parent.transform.Rotate(0, 30 * Time.deltaTime, 0);
            // This rotates the parent and all its children together
        }
    }
}
                            

Parent-child relationships:

  • transform.SetParent() - Sets the parent of a GameObject
  • transform.parent - Gets/sets the parent Transform
  • transform.childCount - Number of children
  • transform.GetChild(index) - Gets a child at specific index
  • Instantiate(prefab, parent) - Creates with parent
  • localPosition/localRotation/localScale - Relative to parent

Finding and Referencing GameObjects

To interact with GameObjects, you first need to reference them. Unity provides several ways to find GameObjects in your scene:

Finding GameObjects:


using UnityEngine;

public class GameObjectFinder : MonoBehaviour
{
    // Inspector references - most efficient way to reference
    public GameObject directReference;
    
    void Start()
    {
        // METHOD 1: Find by name (expensive, avoid in Update)
        GameObject player = GameObject.Find("Player");
        // This searches the entire scene for a GameObject named "Player"
        
        if (player != null)
        {
            Debug.Log("Found player at position: " + player.transform.position);
        }
        
        // METHOD 2: Find by tag (more efficient than Find)
        GameObject mainCamera = GameObject.FindWithTag("MainCamera");
        // This finds the first GameObject with the "MainCamera" tag
        
        // METHOD 3: Find all GameObjects with a tag
        GameObject[] enemies = GameObject.FindGameObjectsWithTag("Enemy");
        Debug.Log($"Found {enemies.Length} enemies in the scene");
        // This returns an array of all GameObjects tagged "Enemy"
        
        // METHOD 4: Find object of a specific type
        Light mainLight = FindObjectOfType();
        // This finds the first active Light component in the scene
        
        // METHOD 5: Find all objects of a specific type
        Collider[] allColliders = FindObjectsOfType();
        Debug.Log($"Found {allColliders.Length} colliders in the scene");
        // This returns an array of all active Collider components
        
        // METHOD 6: Find a child by name
        Transform childTransform = transform.Find("ChildName");
        // This searches only the direct children of this GameObject
        
        if (childTransform != null)
        {
            GameObject child = childTransform.gameObject;
            Debug.Log("Found child: " + child.name);
        }
        
        // METHOD 7: Get component from GameObject
        if (player != null)
        {
            // Get a component attached to the player
            Rigidbody rb = player.GetComponent();
            
            if (rb != null)
            {
                // Modify the component
                rb.mass = 2.0f;
            }
            
            // Get all components of a type
            Collider[] playerColliders = player.GetComponents();
            Debug.Log($"Player has {playerColliders.Length} colliders");
        }
    }
    
    // BEST PRACTICE: Cache references rather than finding every frame
    private GameObject player;
    
    void Awake()
    {
        // Cache references during initialization
        player = GameObject.FindWithTag("Player");
    }
    
    void Update()
    {
        // Use the cached reference in Update
        if (player != null)
        {
            float distance = Vector3.Distance(transform.position, player.transform.position);
            // No need to search for the player every frame
        }
    }
}
                            

Finding methods by efficiency (best to worst):

  1. Direct inspector reference - Most efficient
  2. GetComponent<T>() - Fast, searches only the current GameObject
  3. transform.Find() - Fast, searches only direct children
  4. GameObject.FindWithTag() - Moderate, searches by tag
  5. FindObjectOfType<T>() - Slow, searches all GameObjects by component
  6. GameObject.Find() - Slowest, searches all GameObjects by name

Important: Cache references whenever possible rather than finding objects repeatedly.

Enabling, Disabling, and Destroying GameObjects

Managing the lifecycle of GameObjects is important for performance and gameplay.

GameObject Lifecycle Management:


using UnityEngine;

public class GameObjectLifecycle : MonoBehaviour
{
    public GameObject objectToManage;
    
    void Start()
    {
        if (objectToManage == null)
            return;
            
        // Check if a GameObject is active
        bool isActive = objectToManage.activeInHierarchy;
        Debug.Log($"Object active in hierarchy: {isActive}");
        
        // Check if the GameObject itself is active (ignoring parents)
        bool isSelfActive = objectToManage.activeSelf;
        Debug.Log($"Object active self: {isSelfActive}");
        
        // Disable a GameObject (makes it invisible and inactive)
        objectToManage.SetActive(false);
        // This deactivates the GameObject, stopping rendering and components
        
        // Wait 2 seconds, then enable it
        Invoke("EnableObject", 2.0f);
    }
    
    void EnableObject()
    {
        if (objectToManage != null)
        {
            // Enable a GameObject
            objectToManage.SetActive(true);
            // This activates the GameObject, resuming rendering and components
            
            // Schedule destruction after 5 seconds
            Destroy(objectToManage, 5.0f);
            // This will destroy the object after 5 seconds
        }
    }
    
    void Update()
    {
        // Example: Destroy the GameObject when a key is pressed
        if (Input.GetKeyDown(KeyCode.X) && objectToManage != null)
        {
            // Destroy immediately
            Destroy(objectToManage);
            // This removes the GameObject from the scene permanently
            
            // Clear the reference to avoid null reference exceptions
            objectToManage = null;
        }
        
        // Example: Instantiate a new object when a key is pressed
        if (Input.GetKeyDown(KeyCode.C) && objectToManage == null)
        {
            // Create a new sphere
            GameObject newSphere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
            newSphere.transform.position = new Vector3(0, 2, 0);
            
            // Assign it to our reference
            objectToManage = newSphere;
        }
    }
    
    // Destroy a component rather than the whole GameObject
    void RemoveComponent()
    {
        if (objectToManage != null)
        {
            // Get the component
            Renderer renderer = objectToManage.GetComponent();
            
            if (renderer != null)
            {
                // Destroy just the renderer component
                Destroy(renderer);
                // This removes just the Renderer component, making the object invisible
                // but the GameObject still exists with its other components
            }
        }
    }
    
    // DontDestroyOnLoad keeps a GameObject when changing scenes
    void KeepObjectBetweenScenes()
    {
        if (objectToManage != null)
        {
            // This prevents the object from being destroyed when loading a new scene
            DontDestroyOnLoad(objectToManage);
        }
    }
}
                            

GameObject lifecycle methods:

  • gameObject.SetActive(bool) - Enables/disables a GameObject
  • activeInHierarchy - Is the GameObject active in the scene
  • activeSelf - Is this GameObject itself active
  • Destroy(object) - Destroys a GameObject or component
  • Destroy(object, time) - Destroys after time delay
  • DontDestroyOnLoad() - Preserves GameObject between scenes

SetActive vs. Destroy

When deciding whether to disable or destroy GameObjects:

  • Use SetActive(false) for objects you'll reuse soon (better performance)
  • Use Destroy() for objects you won't need again (frees memory)
  • Consider Object Pooling for objects created and destroyed frequently (bullets, enemies)

GameObject Tags and Layers

Tags and layers help organize and filter GameObjects in your scene.

Working with Tags and Layers:


using UnityEngine;

public class TagsAndLayers : MonoBehaviour
{
    void Start()
    {
        // TAGS
        
        // Set a tag
        gameObject.tag = "Player";
        // This sets the tag of the current GameObject to "Player"
        
        // Check a GameObject's tag
        if (gameObject.CompareTag("Player"))
        {
            Debug.Log("This is the player!");
        }
        
        // Find all GameObjects with a tag
        GameObject[] enemies = GameObject.FindGameObjectsWithTag("Enemy");
        foreach (GameObject enemy in enemies)
        {
            Debug.Log($"Found enemy: {enemy.name}");
        }
        
        // LAYERS
        
        // Set a layer
        gameObject.layer = LayerMask.NameToLayer("Player");
        // This sets the layer of the current GameObject to the "Player" layer
        
        // Create a layer mask (for filtering in raycasts, etc.)
        int layerMask = LayerMask.GetMask("Enemy", "Obstacle");
        // This creates a mask that includes the "Enemy" and "Obstacle" layers
        
        // Example: Raycast that only hits certain layers
        RaycastHit hit;
        if (Physics.Raycast(transform.position, transform.forward, out hit, 100f, layerMask))
        {
            Debug.Log($"Hit {hit.collider.gameObject.name} on layer {LayerMask.LayerToName(hit.collider.gameObject.layer)}");
            // This raycast only detects objects on the Enemy or Obstacle layers
        }
        
        // Invert a layer mask (hit everything EXCEPT these layers)
        int invertedMask = ~LayerMask.GetMask("Player", "Ignore Raycast");
        // The ~ operator inverts the mask, so it includes all layers EXCEPT Player and Ignore Raycast
        
        // Set layer for all children too
        SetLayerRecursively(gameObject, LayerMask.NameToLayer("Player"));
    }
    
    // Helper method to set layer for GameObject and all its children
    void SetLayerRecursively(GameObject obj, int newLayer)
    {
        // Set layer of the current object
        obj.layer = newLayer;
        
        // Loop through all children and set their layers too
        foreach (Transform child in obj.transform)
        {
            SetLayerRecursively(child.gameObject, newLayer);
        }
    }
    
    void OnTriggerEnter(Collider other)
    {
        // Using tags in collision detection
        if (other.CompareTag("Enemy"))
        {
            Debug.Log("Collided with an enemy!");
        }
        
        // Check which layer the colliding object is on
        int objectLayer = other.gameObject.layer;
        if (objectLayer == LayerMask.NameToLayer("Pickup"))
        {
            Debug.Log("Collided with a pickup!");
        }
    }
}
                            

Tags:

  • gameObject.tag - Get/set the GameObject's tag
  • GameObject.FindGameObjectsWithTag() - Find objects with tag
  • gameObject.CompareTag() - More efficient than comparing string tags

Layers:

  • gameObject.layer - Get/set the GameObject's layer
  • LayerMask.GetMask() - Create a layer mask for filtering
  • Physics.Raycast(..., layerMask) - Only hit specific layers
  • ~layerMask - Invert a layer mask

When to Use Tags vs. Layers

  • Tags: For identifying the purpose/type of GameObjects
  • Layers: For controlling physics interactions, raycasting, and rendering

You can define tags and layers in the Unity Editor (Edit → Project Settings → Tags and Layers)

Best Practices for Working with GameObjects

  1. Use prefabs for reusable GameObjects to maintain consistency
  2. Cache references to GameObjects instead of finding them repeatedly
  3. Organize hierarchies with empty parent GameObjects for logical grouping
  4. Use tags and layers appropriately for identification and filtering
  5. Recycle GameObjects with object pooling for better performance
  6. Use SetActive() rather than destroying/recreating frequently used objects
  7. Keep hierarchies shallow when possible for better performance
  8. Use descriptive names for GameObjects in the hierarchy
  9. Favor direct references over Find methods when possible
  10. Consider scene organization when working with many GameObjects