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):
- Direct inspector reference - Most efficient
- GetComponent<T>() - Fast, searches only the current GameObject
- transform.Find() - Fast, searches only direct children
- GameObject.FindWithTag() - Moderate, searches by tag
- FindObjectOfType<T>() - Slow, searches all GameObjects by component
- 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
- Use prefabs for reusable GameObjects to maintain consistency
- Cache references to GameObjects instead of finding them repeatedly
- Organize hierarchies with empty parent GameObjects for logical grouping
- Use tags and layers appropriately for identification and filtering
- Recycle GameObjects with object pooling for better performance
- Use SetActive() rather than destroying/recreating frequently used objects
- Keep hierarchies shallow when possible for better performance
- Use descriptive names for GameObjects in the hierarchy
- Favor direct references over Find methods when possible
- Consider scene organization when working with many GameObjects