C4. MonoBehaviour
Learning Outcomes
- Describe the role of
MonoBehaviour
as the foundation for Unity scripts. Before class, review its purpose and familiarize yourself with common lifecycle methods likeStart()
andUpdate()
.- Differentiate between initialization and setup methods in Unity. As preparation, study
Awake()
,Start()
,OnEnable()
,Reset()
, andOnValidate()
by creating a script that logs each method to observe their execution order.- Implement frame update loops for continuous behavior. Ahead of the session, write
Update()
,FixedUpdate()
, andLateUpdate()
methods to move or rotate a GameObject usingTime.deltaTime
and log when each method runs.- Respond to physics collisions and trigger events. For your pre-work, create GameObjects with colliders and write scripts that log messages when collisions or trigger entries occur.
- Detect visibility changes of GameObjects in the scene. In preparation, use
OnBecameVisible()
andOnBecameInvisible()
to log when an object enters or leaves the camera’s view.- Handle teardown events for cleanup in Unity. Before arriving, use
OnDisable()
andOnDestroy()
in a script to log when a GameObject is disabled or destroyed at runtime.
What Is MonoBehaviour
?
MonoBehaviour
is the foundational base class from which nearly all Unity scripts derive. It serves as the essential bridge between your custom C# code and Unity’s powerful engine, enabling your scripts to participate in the engine’s internal lifecycle, event system, and component-based architecture. MonoBehaviour
simplifies development by providing a set of built-in methods and message handlers—like Start()
, Update()
, and OnCollisionEnter()
—so you don’t have to build core behavior management from scratch.
The MonoBehaviour
Script
When you create a new script in Unity, it typically inherits from MonoBehaviour
by default. This inheritance grants access to Unity’s lifecycle messages—such as Start()
, Update()
, and many others—that are automatically called by the engine at specific times during the game’s execution. These hooks make it easy to define behavior that reacts to events like object initialization, frame updates, collisions, and visibility changes. You can attach a script to a GameObject by
dragging it onto a GameObject in the Hierarchy
, selecting the GameObject, then dragging the script into the Inspector
, or using the Component > Scripts
submenu. Key features of MonoBehaviour
script include:
-
Integration with Unity’s Lifecycle:
MonoBehaviour
provides built-in event methods, such asAwake()
,Start()
,Update()
, andFixedUpdate()
that Unity automatically invokes at specific times. Unity handles when these are called, so you don’t need to write manual polling logic. For example, in XFactory’s logistics station, you can useStart()
to initialize drone pathfinding logic, whileUpdate()
continuously monitors their battery level or navigational state:public class DroneController : MonoBehaviour { void Start() { Debug.Log("Drone initialized."); } void Update() { Debug.Log("Monitoring drone behavior..."); } }
-
Component-Based Architecture: Unity’s architecture is built around GameObjects and their attached components. A
MonoBehaviour
script is a custom component that defines behavior and can be attached to any GameObject in the scene. This modular design allows you to add, remove, or modify functionality without altering other parts of the system—perfect for building scalable simulations like XFactory. Once written, aMonoBehaviour
script can be reused across multiple GameObjects. This promotes code reuse and easier debugging. For example, attach aForkliftController
script to a forklift GameObject in the logistics station to give it autonomous movement behavior during package transport.public class ForkliftController : MonoBehaviour { public float moveSpeed = 5f; void Update() { // Move the forklift forward continuously transform.Translate(Vector3.forward * moveSpeed * Time.deltaTime); } }
-
Simplified Initialization and Event Handling: Instead of traditional constructors,
MonoBehaviour
uses lifecycle methods (e.g.,Awake()
for setup andStart()
for initialization) that are called at the appropriate time by Unity. This ensures that your code executes when the GameObject is fully set up and all components are loaded.
A script is just a blueprint for behavior. That behavior is not active until the script is attached to a GameObject in your scene. The script will appear in the
Inspector
, where its public fields can be viewed or edited directly, and Unity will begin calling its relevant methods automatically during runtime.
Properties
Properties (or serialized fields) represent variables that define the state or configuration of a MonoBehaviour
component. These values are often made public
or marked with [SerializeField]
so they can be set directly in the Unity Inspector
, without exposing them to other scripts. This approach allows designers and developers to tweak simulation behavior—like speed, capacity, or thresholds—without modifying code. For example, a forklift in the logistics station can move forward when triggered by user input or an automation script, while its speed is controllable through the Inspector
but not from other scripts:
using UnityEngine;
public class ForkliftMotion : MonoBehaviour
{
[SerializeField]
private float moveSpeed = 3f; // Editable in the Inspector but not directly from other scripts
void Update()
{
if (Input.GetKey(KeyCode.Space))
{
transform.Translate(Vector3.forward * moveSpeed * Time.deltaTime);
}
}
}
Public Methods
Public methods are functions declared within your MonoBehaviour
that can be called by other scripts or through Unity’s event messaging system. They define the actions that your component can perform and are essential for implementing game logic. For example, a welding robot can use a StartWelding()
method that begins its programmed sequence. Unity’s InvokeRepeating()
might repeatedly call it based on a timing condition.
using UnityEngine;
public class WeldingRobot : MonoBehaviour
{
// Method that logs when the welding sequence starts
public void StartWelding()
{
Debug.Log("Starting welding sequence...");
}
// Unity method called once when the GameObject is enabled
void Start()
{
// Calls StartWelding() after 2 seconds, then repeats every 4 seconds
InvokeRepeating("StartWelding", 2f, 4f);
}
}
Static Methods
Static methods belong to the class itself, not to an instance. This means you can call them without attaching the script to a GameObject or creating an object. They are ideal for utility functions, like logging, formatting, or shared calculations used across multiple systems. Static methods are especially useful for creating centralized, reusable functionality that doesn’t depend on GameObject instances. In XFactory, a static utility class could be used to log important actions from any machine, robot, or station—ensuring consistent logging format across the simulation.
using UnityEngine;
public static class FactoryLogger
{
// Logs a standardized message from any part of the simulation
public static void LogAction(string source, string message)
{
Debug.Log($"[XFactory - {source}] {message}");
}
}
// Usage examples:
FactoryLogger.LogAction("WeldingRobot", "Completed weld cycle.");
FactoryLogger.LogAction("Forklift", "Delivered package to Assembly Station.");
Inherited Members
The MonoBehaviour class inherits a wide range of useful properties and methods from its parent classes (Behaviour
, Component
, and Object
). These inherited members give your scripts direct access to the GameObject they are attached to, making it easier to interact with components, control object behavior, and respond to gameplay events. Key inherited members include:
gameObject
: Grants direct access to the GameObject this script is attached to. You can use it to enable/disable the object, access or modify components, or manage its hierarchy and state.transform
: Provides access to the GameObject’sposition
,rotation
, andscale
in the scene. Crucial for movement, orientation, and spatial logic.name
: Thename
of the GameObject. Useful for identification, debugging, or dynamic naming during runtime.tag
: A string identifier used to categorize or group GameObjects. Useful for filtering objects in logic or triggering behavior based on tags.CompareTag(string tag)
: Efficiently compares the GameObject’s tag to a givenstring
. Preferred over string comparison for performance.GetComponent<T>()
: Retrieves a component of typeT
attached to the same GameObject. Used to interact with other components (e.g.,Rigidbody
,Collider
, or scripts).GetComponents<T>()
: Retrieves all components of typeT
attached to the GameObject.TryGetComponent<T>(out T component)
: Safely attempts to retrieve a component of typeT
, returning a boolean indicating success or failure. Avoids exceptions.GetComponentInChildren<T>()
: Finds a component of typeT
in the GameObject or its child objects.GetComponentInParent<T>()
: Finds a component of typeT
in the GameObject or its parent objects.
Example
Now let’s utilize everything we have discussed so far, from the basics of MonoBehaviour
to inherited members, specifically GetComponent<T>()
, to control the Drone
GameObject in the XFactory logistics station.
- Create a
MonoBehaviour
Script:- Right-click on the
Scripts
folder in theProject
window and selectCreate > MonoBehaviour Script
. Name itDroneController
. - Paste the following script into it.
- It uses inherited members like
name
,transform
, andgameObject
directly fromMonoBehaviour
. - It uses
GetComponent<Animator>()
to access and control the drone’s main flight animation. - It uses
transform.Find()
to access child objects likeEye
and theFans
container. - It uses
GetComponent<Animator>()
on the childEye
to enable/disable its scanner’s pulse animation at runtime.
using UnityEngine; public class DroneController : MonoBehaviour { private Animator droneAnimator; // Controls drone flight animations private Animator eyeAnimator; // Controls Eye scanner animation private Transform[] fanTransforms; // References to the 4 fan objects private bool isFlying = false; // Tracks drone flight state private bool isEyeActive = true; // Tracks if Eye animator is active [SerializeField] private float idleFanSpeed = 1000f; // Fan speed while hovering [SerializeField] private float activeFanSpeed = 2000f; // Fan speed while flying void Start() { // Get the Animator on the main Drone droneAnimator = GetComponent<Animator>(); // Find Eye and get its Animator Transform eye = transform.Find("Eye"); if (eye != null) { eyeAnimator = eye.GetComponent<Animator>(); } // Get references to four fan objects under "Fans" Transform fansParent = transform.Find("Fans"); if (fansParent != null) { fanTransforms = new Transform[4]; for (int i = 0; i < 4; i++) { Transform fan = fansParent.Find($"fan.00{i + 1}"); if (fan != null) { fanTransforms[i] = fan; } } } Debug.Log($"{name} initialized with DroneController"); } void Update() { // Toggle flying state with Space key if (Input.GetKeyDown(KeyCode.Space) && droneAnimator != null) { if (isFlying) { droneAnimator.SetTrigger("ReturnToHover"); isFlying = false; } else { droneAnimator.SetTrigger("StartFlying"); isFlying = true; } } // Toggle Eye animator on/off with 'P' if (Input.GetKeyDown(KeyCode.P) && eyeAnimator != null) { isEyeActive = !isEyeActive; eyeAnimator.enabled = isEyeActive; // Enable/disable the Animator component Debug.Log("Eye animation is now " + (isEyeActive ? "ON" : "OFF")); } // Rotate fans based on drone state float currentSpeed = isFlying ? activeFanSpeed : idleFanSpeed; if (fanTransforms != null) { foreach (var fan in fanTransforms) { if (fan != null) { fan.Rotate(Vector3.forward * currentSpeed * Time.deltaTime); } } } } }
- Right-click on the
- Configure the Script:
- Attach this script to the
Drone
GameObject in theHierarchy
. - Adjust
Idle Fan Speed
andActive Fan Speed
as necessary.
- Attach this script to the
- Play and Observe the Behavior:
- Enter Play mode.
- Pressing
Space
toggles between flying and hovering by triggering animation transitions. - Pressing
P
enables or disables the Eye’s pulsing animation entirely (viaAnimator.enabled
). - Fan GameObjects are dynamically rotated around their
Z
-axis, with speed adjusted based on flight state.
Messages
Messages are special event functions in MonoBehaviour
that Unity calls automatically when specific events occur. Unlike methods you call manually, messages are triggered by Unity’s internal systems based on events in the game environment. This design makes it simple to respond to changes in game state, physics, input, rendering, and more without having to constantly check for those events yourself. Messages are crucial for three main reasons:
- Event-Driven Programming: Messages allow your scripts to follow an event-driven model. Instead of writing loops to monitor for conditions (like user input or collision events), you simply implement a message method (e.g.,
OnCollisionEnter()
) and Unity calls it when the event happens. - Separation of Concerns: Each message is responsible for handling a specific type of event. This modularity makes it easier to organize code—one method per event—improving readability and maintainability.
- Simplicity: Unity takes care of detecting events (such as collisions or input) and calling your methods. You only need to implement the logic, which accelerates development.
Message | Definition |
---|---|
Awake() |
Called when a script instance is loaded; used for early initialization. |
Start() |
Invoked just before the first frame update; ideal for initialization after Awake() . |
FixedUpdate() |
Called at fixed intervals; used for physics updates. |
Update() |
Called once per frame; used for general updates and handling real-time actions. |
LateUpdate() |
Called after Update() ; ideal for operations that depend on prior updates. |
OnValidate() |
Called in the Editor when a script is loaded or a value changes in the Inspector; useful for auto-updates. |
Reset() |
Called in the Editor when you click the Reset button in the Inspector; resets fields to default values. |
OnCollisionEnter() |
Called when a collider/rigidbody starts touching another collider/rigidbody. |
OnCollisionStay() |
Called every frame while another collider/rigidbody remains in contact. |
OnCollisionExit() |
Called when another collider/rigidbody stops touching this object. |
OnTriggerEnter() |
Invoked when another object enters a trigger collider on this object. |
OnTriggerStay() |
Called every frame while another object stays within the trigger collider. |
OnTriggerExit() |
Called when another object exits the trigger collider. |
OnBecameVisible() |
Called when the object becomes visible to any camera. |
OnBecameInvisible() |
Called when the object is no longer visible by any camera. |
OnDisable() |
Called when the script or its GameObject is disabled; used for cleanup or stopping actions. |
OnEnable() |
Called when the script or its GameObject becomes enabled and active. |
OnDestroy() |
Called just before the object is destroyed; used for final cleanup. |
For a full list of Unity event messages, see Unity’s MonoBehaviour documentation.
Initialization and Setup
These are methods that Unity calls during the lifecycle of a GameObject to set up and manage runtime behavior. While some occur only once, others enable real-time updates throughout gameplay. They are essential for driving dynamic behavior in XFactory—such as robotic movements, sensor monitoring, or user interaction with AR/VR panels. Key use cases include:
- Polling for input (e.g., sensor/user data).
- Moving objects (e.g., drone, quadruped).
- Checking conditions (e.g., machine state, energy level, assembly progress).
- Updating UI or animations frame-by-frame.
Reset()
The Reset()
method is automatically invoked in the Unity Editor. It is called when the user selects the Reset()
option from the component’s context menu in the Inspector
, or when a component is added to a GameObject for the first time. It is ideal for setting up default values or basic references to reduce manual configuration. Key features include:
- Editor-Only: The
Reset
method is only executed in editor mode, not in the final build. - Default Initialization: It provides a mechanism to set up default values for public fields or properties, enhancing usability.
- Ease of Use: Offers a quick way for developers to restore a component’s configuration without manually adjusting properties.
Example: In XFactory, you might use Reset()
to auto-link the logistics drone to its navigation target when first added. Attach the following script to the Drone
GameObject prefab. Ensure there is a GameObject in the scene tagged as “Waypoint” so that Reset()
can automatically assign it when the component is first added or reset in the Inspector
.
using UnityEngine;
public class DroneSetup : MonoBehaviour
{
public GameObject navigationTarget;
void Reset()
{
Debug.Log("Reset: Assigning default navigation target.");
// Try to auto-assign a default navigation waypoint
if (!navigationTarget)
{
navigationTarget = GameObject.FindWithTag("Waypoint");
}
}
}
OnValidate()
OnValidate()
is an editor-only callback that is invoked when a script is loaded or when a value is changed in the Inspector. It is commonly used to enforce constraints, correct mistakes, or auto-update related properties without waiting for runtime. Key features include:
- Editor-Only: Executes only within the Unity Editor and not in runtime builds.
- Data Validation: Ideal for checking and clamping values to keep data consistent (e.g., ensuring numerical values remain within a specified range).
- Frequent Calls: Can be triggered often during normal Editor operations such as loading scenes, building a player, or entering Play Mode.
Example: Use OnValidate()
to ensure the mobile robot remains within the defined X
-Z
boundaries of the assembly station area. Attach this script to the mobile robot GameObject in the assembly station (AGV_Omniwheel
). OnValidate()
runs in the Editor, so it helps enforce boundary limits immediately when the robot is moved or adjusted in the Inspector
—no need to run the game to see it applied.
using UnityEngine;
public class MobileRobotBounds : MonoBehaviour
{
// Define the boundary limits for the assembly station (in world space)
public Vector2 xBounds = new Vector2(-10f, 10f);
public Vector2 zBounds = new Vector2(-5f, 5f);
private void OnValidate()
{
Vector3 pos = transform.position;
// Clamp position within defined bounds
pos.x = Mathf.Clamp(pos.x, xBounds.x, xBounds.y);
pos.z = Mathf.Clamp(pos.z, zBounds.x, zBounds.y);
// Apply constrained position back to the transform
transform.position = pos;
}
}
Awake()
Awake(
) is automatically called when an enabled script instance is loaded. It is primarily used to initialize variables or states before the game starts running. This method is invoked in scenarios such as when a GameObject is active on Scene load, when an inactive GameObject is activated, or when a new GameObject is created via instantiation. Key features include:
- One-time Initialization: Called only once during the lifetime of a script instance. If the Scene is reloaded or loaded additively, each instance will have its own
Awake()
call. - Order of Execution: All active GameObjects have their
Awake()
called before anyStart()
methods. However, the order in whichAwake()
is called across different GameObjects is not deterministic. - Early Setup: Since
Awake()
is called before anyStart()
methods, it is ideal for establishing references or setting initial states. - Called on Disabled Scripts:
Awake()
is executed even if the script component is disabled, as long as its GameObject is active. - Exception Handling: If an exception occurs during
Awake()
, Unity will disable the component to prevent further errors.
Example: In XFactory’s logistics station, a drone can initialize its status and visual material when the scene loads. Attach this script to the drone GameObject (Drone
) that needs a visual status indicator. Make sure the drone has a Renderer
component (e.g., MeshRenderer
) on the same GameObject so GetComponent<Renderer>()
works correctly during Awake()
.
public class DroneStatusIndicator : MonoBehaviour
{
private Renderer droneRenderer;
private string status;
void Awake()
{
// Get the renderer to change the material later
droneRenderer = GetComponent<Renderer>();
// Initialize the default status
status = "Idle";
Debug.Log("Awake: Drone status set to Idle.");
}
}
OnEnable()
OnEnable()
is automatically called when a GameObject becomes enabled and active. This method is invoked every time the object enters Play Mode, provided it is enabled. Key features include:
- Automatic Invocation: Runs immediately when the GameObject is enabled, making it ideal for initialization tasks.
- Editor Support: When using the
[ExecuteInEditMode]
attribute,OnEnable()
is also called in the Unity Editor, not just during runtime. - Complementary Functionality: Works in tandem with
OnDisable()
(introduced later), which is called when the GameObject is disabled.
Example: In XFactory’s exhibit area, this could activate the quadruped robot’s Spot_Controller
animator when the GameObject becomes active. Attach this script to the quadruped robot GameObject (Spot
) in the exhibit station that has the Animator
component controlling the Spot_Controller
state. OnEnable()
ensures the animation starts whenever the robot is activated in the scene or re-enabled in play mode.
public class DemoRobotActivator : MonoBehaviour
{
private Animator spotAnimator;
void OnEnable()
{
spotAnimator = GetComponent<Animator>();
if (spotAnimator != null)
{
spotAnimator.Play("Spot_Controller");
Debug.Log("OnEnable: Quadruped animator activated.");
}
}
}
Start()
Start()
is invoked on the frame when a script is enabled, just before any of the frame and update loops are called for the first time. It is executed exactly once in the lifetime of the script, which makes it ideal for initialization that relies on other objects being set up first. Key features include:
- One-Time Initialization: Called only once per script instance, ensuring that initialization occurs only when needed.
- Execution Timing: Runs after the
Awake()
method of all objects, allowing it to safely interact with objects that have already initialized. - Coroutine Support: Can be implemented as a Coroutine, enabling the suspension of execution (using yield) for tasks like waiting.
- Conditional Execution: May not run on the same frame as
Awake()
if the script is not enabled during initial scene loading.
Example: You can use Start()
in the manufacturing station to launch a delayed CNC machine warm-up sequence followed by activating its operational sound. Attach this script to the CNC machine GameObject (CNC_Mill_Set
) in the manufacturing station. Ensure the GameObject has an AudioSource
component with the machine sound assigned—Start()
will trigger a warm-up delay before playback begins automatically during runtime.
using UnityEngine;
public class CNCMachine : MonoBehaviour
{
private AudioSource cncAudio;
void Start()
{
Debug.Log("Start: Initializing CNC warm-up sequence.");
// Get the AudioSource component on the CNC machine
cncAudio = GetComponent<AudioSource>();
// Begin warm-up coroutine
StartCoroutine(WarmUpRoutine());
}
private System.Collections.IEnumerator WarmUpRoutine()
{
yield return new WaitForSeconds(5f);
Debug.Log("CNC Machine ready. Starting operational sound.");
// Start playing the machine sound
if (cncAudio != null)
{
cncAudio.Play();
}
}
}
Tutorial
Let’s extend the simple SpotWalker.cs
script to guide the quadruped robot in the exhibit station of XFactory to a specified point in the XFactory scene, while enforcing editor-time setup and runtime behavior using Unity’s lifecycle callbacks:
- Editor Setup with
Reset()
: Auto-assigntargetPoint
when the component is added or reset. Key line:targetPoint = GameObject.FindWithTag("Waypoint")?.transform;
- Validation in
OnValidate()
: Clamp the waypoint’s position withinxBounds
andzBounds
whenever it’s edited in theInspector
. Key pattern:Mathf.Clamp
ontp.x
andtp.z
. - Early Initialization in
Awake()
: Reset thehasArrived
flag before any other logic runs. - Activation Hook with
OnEnable()
: Log and reinitialize state each time the component is enabled in Play Mode or Editor. - First-Frame Setup in
Start()
: Confirm the robot is initialized and ready to move. - Continuous Movement in
Update()
: UsesVector3.MoveTowards
to smoothly move along both X and Z axes. Arrival check: When distance < 0.01 units, stop and log arrival.
Implementation:
-
Attach the script below to your quadruped GameObject (
Spot
). Make sure the quadruped robot GameObject has aRenderer
using UnityEngine; public class SpotWalker : MonoBehaviour { [Header("Movement Settings")] public float speed = 1.0f; public float rotationSpeed = 180f; // Degrees per second public Transform targetPoint; public Vector2 xBounds = new Vector2(-10f, 10f); public Vector2 zBounds = new Vector2(-5f, 5f); private bool hasArrived; private Animator animator; // Animator to disable on arrival // ————— Editor Setup ————— void Reset() { // Auto-assign a waypoint tagged "Waypoint" targetPoint = GameObject.FindWithTag("Waypoint")?.transform; } void OnValidate() { if (targetPoint) { // Clamp the target position within bounds whenever edited Vector3 tp = targetPoint.position; tp.x = Mathf.Clamp(tp.x, xBounds.x, xBounds.y); tp.z = Mathf.Clamp(tp.z, zBounds.x, zBounds.y); targetPoint.position = tp; } } // ————— Runtime Initialization ————— void Awake() { hasArrived = false; animator = GetComponent<Animator>(); // Cache Animator reference } void OnEnable() { hasArrived = false; if (animator != null) animator.enabled = true; // Ensure animations are active on enable Debug.Log($"SpotWalker enabled, heading to: {(targetPoint ? targetPoint.name : "none")}."); } void Start() { Debug.Log("Quadruped robot initialized, will turn and move to target point."); } // ————— Movement, Turning & Arrival Handling ————— void Update() { // Only run movement and turning in Play Mode if (!Application.isPlaying || targetPoint == null || hasArrived) return; // Compute direction on the XZ plane Vector3 direction = targetPoint.position - transform.position; direction.y = 0; if (direction.sqrMagnitude > 0.0001f) { // Determine the rotation needed Quaternion targetRotation = Quaternion.LookRotation(direction); // Rotate smoothly toward the target transform.rotation = Quaternion.RotateTowards( transform.rotation, targetRotation, rotationSpeed * Time.deltaTime ); // Move forward in the newly faced direction transform.position = Vector3.MoveTowards( transform.position, targetPoint.position, speed * Time.deltaTime ); } // Arrival check if (Vector3.Distance(transform.position, targetPoint.position) < 0.01f) { hasArrived = true; Debug.Log("Arrived at target point."); if (animator != null) { animator.enabled = false; // Deactivate Animator on arrival Debug.Log("Animator disabled upon arrival."); } } } }
- Create an empty GameObject and tag it object as
"Waypoint"
. Alternatively, drag it or anyTransform
intoTarget Point
. -
Adjust
Speed
,Rotation Speed
,xBounds
, andzBounds
in theInspector
. -
Play the scene—watch your robot walk to the target and stop on arrival.
You have leveraged Unity’s lifecycle methods, especially Initialization and Setup messages, to create a robust, self-validating movement controller that works seamlessly in both the Editor and at runtime.
Frame and Update Loops
These methods are called once per frame or at a consistent time interval by Unity to handle ongoing logic during simulation. In XFactory, these loops are crucial for managing everything from real-time robotic motion to drone navigation, user interaction, UI feedback, and camera control. Key use cases include:
- Polling for user or machine input.
- Moving factory robots, drones, or conveyor belts.
- Monitoring operational conditions (e.g., battery level, tool wear).
- Updating UI panels, visual indicators, or animations.
Update()
Update()
is called every frame when the script’s component is enabled. It is primarily used to implement frame-dependent behaviors and game logic. Key features include:
- Frame-Based Execution: Runs every frame, making it ideal for tasks like checking for input or updating game states.
- Time Management: Uses
Time.deltaTime
to calculate the elapsed time since the last frame, ensuring frame rate independent movement and actions. - Conditional Use: Not every script requires an
Update()
function; it should only be implemented when continuous frame-based processing is necessary. - Enabled Dependency: The function is called only if the
MonoBehaviour
is enabled.
Example: In XFactory’s logistics station, the script below can use a timer to track elapsed time between drone scan cycles. It prints a status message every second. Attach this script to a drone GameObject (Drone
) in the logistics station. It should remain active during runtime, as Update()
depends on real-time frame updates to track and log scan cycle intervals every second.
using UnityEngine;
public class DroneScannerStatus : MonoBehaviour
{
private float timer = 0.0f;
void Awake()
{
Debug.Log("Awake: Drone scanner initialized.");
}
void Start()
{
Debug.Log("Start: Beginning scan cycle monitoring.");
}
void Update()
{
timer += Time.deltaTime;
if (timer > 1.0f)
{
timer = 0.0f;
Debug.Log("Update: Drone scanner cycle complete.");
}
}
}
LateUpdate()
LateUpdate()
is called every frame after all Update()
methods have run. It’s ideal for tasks that depend on updated GameObject positions, such as camera tracking. Key features include:
- Execution Order: Runs after all
Update()
calls, ensuring motion is complete before responding. - Frame-Based Call: Used for frame-dependent tasks like camera smoothing or bone-based animation alignment.
- Ideal for Follow-Cameras: Prevents laggy camera behavior that can occur if updates run out of sync.
Example: In XFactory’s exhibit station, a camera can follow a quadruped robot using LateUpdate()
to ensure smooth tracking after the robot moves. Attach this script to the camera GameObject (e.g., Main Camera
) intended to follow the quadruped robot (Spot
) in the exhibit area. Use LateUpdate()
so the camera updates after the robot has moved each frame, ensuring smooth and consistent tracking behavior.
using UnityEngine;
public class FollowQuadruped : MonoBehaviour
{
public Transform quadruped;
public Vector3 offset = new Vector3(0f, 2f, -6f);
void LateUpdate()
{
if (quadruped != null)
{
transform.position = quadruped.position + offset;
transform.LookAt(quadruped);
}
}
}
FixedUpdate()
FixedUpdate()
is Unity’s method for consistent, physics-based updates. It is called at regular, fixed intervals—ideal for any logic involving Rigidbody
motion, collisions, or real-world physical simulations. Key features include:
- Fixed Time Step: Runs every 0.02 seconds by default (configurable in project settings).
- Physics Engine Sync: Designed for
Rigidbody
movement, collision response, and force application. - Frame Rate Independence: Unlike
Update()
,FixedUpdate()
provides consistent physics behavior regardless of graphical frame rates.
Example: In XFactory’s logistics station, the heavy box (Box_Large_01a_Prefab_01
) on the scale (Industrial_Scale_01a
) can use FixedUpdate()
to continuously report weight (mass × gravity) to be projected on the scale’s UI display. Attach this script to the heavy box GameObject (Box_Large_01a_Prefab_01
) that sits on the industrial scale. Ensure the box has a Rigidbody
component, as FixedUpdate()
is used for accurate physics-based calculations and real-time weight reporting synced with the physics engine.
using UnityEngine;
public class BoxWeightReporter : MonoBehaviour
{
private Rigidbody rb;
void Start()
{
rb = GetComponent<Rigidbody>();
}
void FixedUpdate()
{
float simulatedWeight = rb.mass * Physics.gravity.magnitude;
Debug.Log($"[Logistics Scale] Weight reading: {simulatedWeight} N");
}
}
Tutorial
Let’s extend our previous SpotWalker.cs
example to demonstrate Unity’s special update loops:
-
Physics-Driven Movement with
FixedUpdate()
: Runs at a consistent timestep (default 0.02 s) in sync with the physics engine. UsesRigidbody.MoveRotation
andMovePosition
to turn and advance the robot toward its target. Checks for arrival and disables the Animator when the robot reaches the waypoint. -
Smooth Camera Follow with
LateUpdate()
: Executes every frame, after allUpdate()
calls. Repositions and orients the camera to trail the robot’s final pose without jitter.
Implementation:
-
Update your
SpotWalker.cs
so that all movement and turning runs inFixedUpdate()
using aRigidbody
. Replace the oldUpdate()
with this:using UnityEngine; [RequireComponent(typeof(Rigidbody))] public class SpotWalker : MonoBehaviour { [Header("Movement Settings")] public float speed = 1.0f; public float rotationSpeed = 180f; // Degrees per second public Transform targetPoint; public Vector2 xBounds = new Vector2(-10f, 10f); public Vector2 zBounds = new Vector2(-5f, 5f); private bool hasArrived; private Animator animator; // Animator to disable on arrival private Rigidbody rb; // Physics body // ———— Runtime Initialization ———— void Awake() { rb = GetComponent<Rigidbody>(); animator = GetComponent<Animator>(); // Freeze X/Z rotations so the robot stays upright rb.constraints = RigidbodyConstraints.FreezeRotationX | RigidbodyConstraints.FreezeRotationZ; hasArrived = false; } void OnEnable() { hasArrived = false; if (animator != null) animator.enabled = true; Debug.Log($"SpotWalker enabled, heading to: {(targetPoint ? targetPoint.name : "none")}."); } void Start() { Debug.Log("Quadruped robot initialized; using FixedUpdate for motion."); } // ———— Physics‐Timed Movement, Turning & Arrival ———— void FixedUpdate() { if (targetPoint == null || hasArrived) return; // 1. Compute horizontal direction Vector3 dir = targetPoint.position - rb.position; dir.y = 0; if (dir.sqrMagnitude > 0.0001f) { // 2. Smoothly rotate via Rigidbody Quaternion desired = Quaternion.LookRotation(dir); Quaternion nextRot = Quaternion.RotateTowards( rb.rotation, desired, rotationSpeed * Time.fixedDeltaTime ); rb.MoveRotation(nextRot); // 3. Move forward via Rigidbody Vector3 step = dir.normalized * speed * Time.fixedDeltaTime; rb.MovePosition(rb.position + step); } // 4. Arrival check & Animator disable if (Vector3.Distance(rb.position, targetPoint.position) < 0.01f) { hasArrived = true; Debug.Log("Arrived at target point (FixedUpdate)."); if (animator != null) { animator.enabled = false; Debug.Log("Animator disabled upon arrival."); } } } }
-
Add a
Rigidbody
component to your quadruped and freeze itsX
andZ
rotations. Ensure it also has anAnimator
. -
Create
FollowQuadrupedCamera.cs
and attach to yourMain Camera
:using UnityEngine; public class FollowQuadrupedCamera : MonoBehaviour { [Tooltip("Drag your robot’s Transform here")] public Transform quadruped; [Tooltip("Offset relative to robot position")] public Vector3 offset = new Vector3(0f, 2f, -6f); void LateUpdate() { if (quadruped == null) return; transform.position = quadruped.position + offset; transform.LookAt(quadruped); } }
-
In the
Inspector
, drag your robot’sTransform
intoQuadruped
, tweakOffset
andSmooth Speed
. -
Play the Scene. The robot will now turn and move under fixed‐timestep physics control, disable its Animator on arrival, and the camera will smoothly follow in
LateUpdate()
.
Physics Events
These are triggered automatically when the GameObject interacts with other objects through the Unity physics system (using Colliders
and Rigidbody
components). There are two types: Collision (solid object contact) and Trigger (non-physical overlap). Key use cases include:
- Detecting hits, landings, or bumps.
- Triggering effects or events when entering a zone.
- Managing physics-based reactions (e.g., bouncing, sliding).
OnCollisionEnter(Collision)
Called when a GameObject with a non-kinematic Rigidbody
starts colliding with another collider. It passes a Collision
object containing rich information about the contact. Key features include:
- Collision Data: Receives a Collision object, which includes contact points, impact velocity, and other collision details.
- Physics Interaction: Only triggers when one of the colliding objects has a non-kinematic rigidbody.
- Performance Optimization: If collision details are not needed, omitting the parameter can avoid unnecessary calculations.
- Event Propagation: Collision events are sent to disabled
MonoBehaviour
scripts, allowing for behavior activation in response to collisions.
Example: If the drone collides with storage boxes on the rack in the logistics station, an alert can be triggered. Attach this script to the drone GameObject (Drone
) in the logistics station that can collide with storage boxes. Make sure the storage boxes are tagged as StorageBox
and that both the drone and boxes have appropriate colliders and Rigidbody
components (at least one non-kinematic) to enable collision detection.
using UnityEngine;
public class DroneCollisionHandler : MonoBehaviour
{
void OnCollisionEnter(Collision collision)
{
// Check if the drone collided with a storage box
if (collision.gameObject.CompareTag("StorageBox"))
{
Debug.Log($"[Drone] Collided with: {collision.gameObject.name}");
// Visualize contact points in the Scene view
foreach (ContactPoint contact in collision.contacts)
{
Debug.DrawRay(contact.point, contact.normal, Color.red, 2f);
}
}
}
}
OnCollisionStay(Collision)
Invoked each frame while two GameObjects remain in contact. Useful for ongoing feedback or monitoring sustained pressure/contact. Key features include:
- Continuous Collision Monitoring: Called every frame while the collision persists, allowing continuous handling of collision events.
- Detailed Collision Data: Provides a Collision object with information such as contact points and relative velocity.
- Performance Consideration: If collision details are not needed, omitting the parameter avoids extra computations.
- Non-Kinematic Requirement: Collision events are sent only if one of the colliding objects has a non-kinematic rigidbody.
- Sleeping Rigidbodies: Collision stay events are not triggered for rigidbodies that are sleeping.
Example: If the mobile robot presses against a wall in the assembly station, an alarm can be generated. Attach this script to the mobile robot GameObject in the assembly station (AGV_Omniwheel
). To detect sustained wall contact, ensure the robot and wall objects have colliders, and at least one has a Rigidbody
. OnCollisionStay()
will continuously monitor the collision, with a velocity threshold to trigger alerts for pressing incidents.
void OnCollisionStay(Collision collision)
{
foreach (ContactPoint contact in collision.contacts)
{
Debug.DrawRay(contact.point, contact.normal, Color.yellow);
}
if (collision.relativeVelocity.magnitude > 1)
{
Debug.Log("Sustained contact with: " + collision.gameObject.name);
}
}
OnCollisionExit(Collision)
Called when two colliders stop touching. Commonly used for cleanup, state reset, or to log the end of a collision event. Key features include:
- Collision End Notification: Triggered when the contact between two colliding objects ceases.
- Detailed Collision Data: Provides a Collision object with information such as contact points and impact velocity, though it is often used simply to signal the end of a collision.
- Non-Kinematic Requirement: Collision events, including exit events, are only sent if one of the colliding objects has a non-kinematic rigidbody.
- Performance Consideration: As with other collision events, omit the collision parameter if detailed collision data is not needed to reduce extra computations.
Example: In XFactory’s logistics station, the drone can log when it has finished colliding with a storage box. Attach this script to the drone GameObject (Drone
) in the logistics station. Ensure storage boxes are tagged as StorageBox
, and that proper colliders and Rigidbody
setup are in place so Unity can invoke OnCollisionExit()
when the drone stops touching a box.
void OnCollisionExit(Collision collision)
{
if (collision.gameObject.CompareTag("StorageBox"))
{
Debug.Log($"[Drone] Cleared collision with: {collision.gameObject.name}");
}
}
OnTriggerEnter(Collider)
OnTriggerEnter(Collider)
is called when a GameObject with a trigger collider overlaps another collider. It’s commonly used for non-physical detection such as zone-based events, pickups, or proximity-based interactions. Key features include:
- Trigger-Based Interaction: One of the colliders must be marked as a trigger.
- Physics Setup Required: At least one object must have a
Rigidbody
component (typically the moving object), and both must haveCollider
s. - Timing: Executed during the
FixedUpdate()
cycle. - Collider Reference: The
other
parameter refers to the collider of the object that entered the trigger.
Example: In XFactory’s logistics station, the forklift (Reach_Truck_01a_Prefab
) detects when a large box has been loaded onto it. Attach this script to the forklift GameObject (Reach_Truck_01a_Prefab
) and ensure it has a trigger collider (set isTrigger = true
) to detect incoming boxes. The large box GameObjects must be tagged as LargeBox
and have Collider
(and optionally Rigidbody
) for the OnTriggerEnter()
event to register correctly.
void OnTriggerEnter(Collider other)
{
if (other.CompareTag("LargeBox"))
{
Debug.Log("[Forklift] Large box loaded: " + other.name);
// Optionally update UI, trigger animation, or start delivery logic
}
}
OnTriggerStay(Collider)
OnTriggerStay(Collider)
is called every physics update while another object remains inside a trigger collider. It is ideal for monitoring ongoing presence within a zone, such as charging stations, loading bays, or weight sensors. Key features include:
- Continuous Detection: Called repeatedly while the other collider stays inside the trigger.
- Rigidbody Required: At least one object must have a
Rigidbody
component. - Useful for State Monitoring: Can be used to check ongoing conditions like object stability, valid position, or contact duration.
Example: In XFactory’s logistics station, a forklift trigger zone can detect whether it is still carrying a large box. Attach this script to a trigger collider on the forklift (Reach_Truck_01a_Prefab
). Ensure the large boxes are tagged as LargeBox
and have Collider
(and optionally Rigidbody
). OnTriggerStay()
will continuously run while the box remains inside the trigger, allowing for persistent state checks or logic like box-locking or delivery countdowns.
void OnTriggerStay(Collider other)
{
if (other.CompareTag("LargeBox"))
{
Debug.Log("[Forklift] Still carrying box: " + other.name);
// Optional: Display status, lock box in position, or track duration
}
}
OnTriggerExit(Collider)
OnTriggerExit(Collider)
is called when another collider exits a trigger zone. It is commonly used to stop processes that began with OnTriggerEnter()
or OnTriggerStay()
. Key features include:
- Exit Detection: Signals that an object has left the trigger zone.
- Requires Rigidbody: At least one of the two colliders must have a
Rigidbody
. - Complements Entry Events: Pairs well with
OnTriggerEnter()
andOnTriggerStay()
for full lifecycle control. - Silent Exit: If the object is destroyed or deactivated inside the trigger, no exit event will occur.
Example: In XFactory’s logistics station, we can detect when the forklift offloads a box it was previously carrying. Attach this script to the forklift (Reach_Truck_01a_Prefab
). Make sure the large boxes are tagged as LargeBox
and have Collider
(and optionally Rigidbody
). OnTriggerExit()
will detect when a box leaves the cargo zone, useful for tracking offloads or triggering inventory updates.
void OnTriggerExit(Collider other)
{
if (other.CompareTag("LargeBox"))
{
Debug.Log("[Forklift] Box unloaded: " + other.name);
// Optional: Update inventory system or trigger unloading animation
}
}
Tutorial
Let’s use OnTriggerEnter(Collider)
to simulate a box scanning process in the logistic station using the drone. You will:
- Pilot a drone in Play Mode using W/A/S/D + Q/E keys.
- Scan storage boxes by bumping them with a trigger sphere and displaying their names on a UI text.
- Keep the fans spinning at idle or active speed based on movement input.
- Chase–style camera follows behind using
LateUpdate()
.
Implementation:
- Update the
DroneController.cs
Script:- Update the existing
DroneController.cs
script attached to yourDrone
(must have aRigidbody
) as follows. - This script handles movement in
FixedUpdate()
, fan rotation & scan‐UI inUpdate()
, and scanning viaOnTriggerEnter()
.
using UnityEngine; using TMPro; [RequireComponent(typeof(Rigidbody))] public class DroneController : MonoBehaviour { [Header("Movement Settings")] public float speed = 5f; public float rotationSpeed = 100f; [Header("Fan Settings")] public float idleFanSpeed = 1000f; public float activeFanSpeed = 2000f; [Header("Scan UI (TextMeshPro)")] public TextMeshProUGUI scanText; public float displayDuration = 2f; private Rigidbody rb; private Transform[] fanTransforms; private bool isFlying; private float scanTimer; void Awake() { rb = GetComponent<Rigidbody>(); rb.constraints = RigidbodyConstraints.FreezeRotationX | RigidbodyConstraints.FreezeRotationZ; Transform fansParent = transform.Find("Fans"); if (fansParent != null) { fanTransforms = new Transform[fansParent.childCount]; for (int i = 0; i < fansParent.childCount; i++) fanTransforms[i] = fansParent.GetChild(i); } } void Update() { // Clear scan text after the display duration if (!string.IsNullOrEmpty(scanText.text)) { scanTimer += Time.deltaTime; if (scanTimer >= displayDuration) { scanText.text = ""; scanTimer = 0f; } } // Rotate fans float fanSpeed = isFlying ? activeFanSpeed : idleFanSpeed; if (fanTransforms != null) { foreach (var fan in fanTransforms) fan.Rotate(Vector3.forward * fanSpeed * Time.deltaTime); } } void FixedUpdate() { // Read movement input float forward = Input.GetKey(KeyCode.W) ? 1f : Input.GetKey(KeyCode.S) ? -1f : 0f; float strafe = Input.GetKey(KeyCode.D) ? 1f : Input.GetKey(KeyCode.A) ? -1f : 0f; float turn = Input.GetKey(KeyCode.E) ? 1f : Input.GetKey(KeyCode.Q) ? -1f : 0f; isFlying = Mathf.Abs(forward) + Mathf.Abs(strafe) + Mathf.Abs(turn) > 0f; // Yaw rotation Quaternion deltaRot = Quaternion.Euler( 0f, turn * rotationSpeed * Time.fixedDeltaTime, 0f ); rb.MoveRotation(rb.rotation * deltaRot); // Translation Vector3 move = (transform.forward * forward + transform.right * strafe).normalized; rb.MovePosition(rb.position + move * speed * Time.fixedDeltaTime); } void OnTriggerEnter(Collider other) { if (other.CompareTag("StorageBox") && scanText != null) { string name = other.name; scanText.text = $"Scanned: {name}"; scanTimer = 0f; Debug.Log($"[Drone] Scanned: {name}"); } } }
- Update the existing
- Add a Front–Mount Trigger Collider:
- Under your
Drone
GameObject, create an empty child namedScanner
and position it at the drone’s nose (where you want it to “scan” forward). Also, make sure to disable the drone’sAnimator
. - On the
Scanner
child, add aSphereCollider
component. - In the
SphereCollider
settings, checkIs Trigger
. - Adjust the
Radius
so it just encapsulates the front of the drone without overlapping too far. This trigger will fireOnTriggerEnter()
in yourDroneController
when it overlaps anyStorageBox
. - In the
Inspector
, set each rack box’sTag
toStorageBox
.
- Under your
- Add a Scan UI Text:
- Create a
Screen Space - Overlay
UICanvas
, addUI > Text - TextMeshPro
. - Position the text element (e.g., top-left of the screen) and clear its default content.
- Optionally style the font, size, and color for readability.
- Drag this Text component into the
Scan Text
field on yourDroneController
script in theInspector
.
- Create a
- Create a
FollowDroneCamera.cs
Script:- Create a new script named
FollowDroneCamera.cs
. - Attach it to your
Main Camera
. Remove the other script (FollowQuadrupedCamera.cs
) to avoid conflict.
using UnityEngine; public class FollowDroneCamera : MonoBehaviour { [Tooltip("Assign your Drone’s Transform here")] public Transform drone; [Tooltip("Local offset: (X right, Y up, Z back)")] public Vector3 localOffset = new Vector3(0f, 3f, -8f); [Tooltip("Smooth follow speed")] public float smoothSpeed = 5f; void LateUpdate() { if (drone == null) return; // 1. Compute world position behind the drone Vector3 desiredPos = drone.TransformPoint(localOffset); // 2. Smoothly interpolate camera position transform.position = Vector3.Lerp( transform.position, desiredPos, smoothSpeed * Time.deltaTime ); // 3. Always look at the drone’s center transform.LookAt(drone); } }
- Create a new script named
- Configure the Script:
- Drag your drone’s Transform into the
Drone
field on theFollowDroneCamera
component. - Tweak
Local Offset
(X
: right/left,Y
: height,Z
: distance) to position the camera, and adjustSmooth Speed
for responsiveness.
- Drag your drone’s Transform into the
- Play the Scene:
- W/A/S/D: Move and strafe the drone.
- Q/E: Rotate (yaw) the drone.
- Fans: Spin faster when movement input is detected.
- Scanning: When the Scanner trigger overlaps a StorageBox, its name appears in the UI text for the configured
displayDuration
. - Camera: Trails behind the drone at your configured offset, smoothly following in
LateUpdate()
.
Visibility & Rendering
These methods are tied to what Unity’s cameras can see. Unity automatically calls them when a GameObject’s renderer enters or exits the field of view of any camera. In XFactory, these callbacks are essential for performance tuning, debugging, and triggering visibility-based logic for dynamic machines, sensors, or display panels. Key use cases include:
- Optimizing performance by disabling logic or effects when off-screen.
- Triggering animations, sounds, or loading behavior when something appears/disappears.
- Debugging or managing visibility-dependent logic.
OnBecameVisible()
OnBecameVisible()
is called when the renderer attached to a GameObject becomes visible to any camera. This message is broadcast to all scripts on the same GameObject with a renderer component. Key features include:
- Visibility Detection: Triggered when an object comes into the camera’s view, allowing the execution of code only when the object is visible.
- Performance Optimization: Helps reduce unnecessary computations by activating behaviors only when required.
- Coroutine Capability: Can be used as a coroutine by incorporating the yield statement within the function.
- Editor Support: In the Unity Editor, the Scene view cameras can also trigger this function, aiding in debugging and scene management.
Example: In XFactory’s assembly station, the large display screen (Display GT
) powers on 2 seconds after becoming visible to the camera. Attach this script to the Display GT
screen GameObject in the assembly station. Ensure the screen has a child Canvas
(for UI elements) and a Renderer
component so OnBecameVisible()
is triggered correctly when the camera sees it. The Canvas
will activate 2 seconds after the screen enters the camera’s view.
using UnityEngine;
using UnityEngine.UI;
public class DisplayScreenController : MonoBehaviour
{
private Canvas displayCanvas;
void Start()
{
// Find and disable the Canvas component at start
displayCanvas = GetComponentInChildren<Canvas>(true);
displayCanvas.enabled = false;
}
void OnBecameVisible()
{
Debug.Log("[DisplayScreen] Became visible — scheduling power-on.");
Invoke(nameof(ActivateDisplay), 2f); // Delay activation by 2 seconds
}
void ActivateDisplay()
{
displayCanvas.enabled = true;
Debug.Log("[DisplayScreen] Canvas activated.");
}
}
OnBecameInvisible()
OnBecameInvisible()
is invoked when the renderer attached to a GameObject is no longer visible by any camera. This function is called on all scripts associated with the renderer, allowing you to manage behaviors based on object visibility. Key features include:
- Visibility Monitoring: Triggered when an object is no longer seen by any camera, which can be used to halt unnecessary computations.
- Performance Optimization: Helps optimize performance by disabling behaviors when the object is not in view.
- Editor Support: In the Unity Editor, Scene view cameras can also trigger this function, aiding in debugging.
Example: In XFactory’s exhibit station, the quadruped robot uses a script called SpotWalker.cs
. Its walking animation can be paused when it leaves the camera’s view, and resumed when it becomes visible again. Attach this script to the quadruped robot GameObject (Spot
) in the exhibit station. It must have an Animator
component and a Renderer
(e.g., MeshRenderer
), as OnBecameVisible()
and OnBecameInvisible()
are only triggered when the GameObject’s renderer enters or exits the camera’s view.
using UnityEngine;
public class SpotWalker : MonoBehaviour
{
private Animator animator;
void Start()
{
animator = GetComponent<Animator>();
}
void OnBecameVisible()
{
Debug.Log("[SpotWalker] Visible — animation resumed.");
animator.enabled = true;
}
void OnBecameInvisible()
{
Debug.Log("[SpotWalker] Not visible — animation paused.");
animator.enabled = false;
}
}
Tutorial
Let’s use OnBecameVisible()
and OnBecameInvisible()
to monitor when a StorageBox enters or leaves any camera’s view in the logistics station. You will:
- Update a text field to show “Box Visible: [Name]” when it appears on-screen.
- Clear that text when it goes off-screen.
- Log corresponding messages to the Console for each event.
Implementation:
-
Create a new script named
BoxVisibilityMonitor.cs
and attach it to each StorageBox prefab/instance (make sure the box has aRenderer
component).OnBecameVisible()
fires once when the box’sRenderer
becomes visible to any camera.OnBecameInvisible()
fires once when it leaves all camera views.using UnityEngine; using TMPro; public class BoxVisibilityMonitor : MonoBehaviour { [Tooltip("Drag your UI TextMeshProUGUI here")] public TextMeshProUGUI visibilityText; void OnBecameVisible() { if (visibilityText != null) { visibilityText.text = $"Box Visible: {gameObject.name}"; Debug.Log($"[BoxVisibility] Box Visible: {gameObject.name}"); } } void OnBecameInvisible() { if (visibilityText != null) { visibilityText.text = ""; // Clear when no longer visible Debug.Log($"[BoxVisibility] Box Invisible: {gameObject.name}"); } } }
- Attach
BoxVisibilityMonitor.cs
to a box GameObject. -
Select the box and in the
BoxVisibilityMonitor
component, drag theText (TMP)
UI element into theVisibility Text
field. -
Play the scene. Move the drone so that the box enters the view frustum. When the box becomes visible, the UI text will update to Box Visible: [Name of the Box]. When the box leaves view, the UI text will clear. Check the Console for corresponding log messages from
OnBecameVisible()
andOnBecameInvisible()
.
Teardown
These Unity event methods are called when a script or GameObject is disabled or destroyed. They are essential for cleaning up resources, unsubscribing from events, or halting behaviors that should not continue once the object is no longer active. In XFactory, teardown logic can be used when, robots shut down or are removed from the scene, temporary diagnostic UI panels are closed, or machines are powered off between simulation phases. Key use cases include:
- Unsubscribing from events (e.g., PLC triggers or telemetry).
- Stopping coroutines or timers.
- Saving state (e.g., storing part progress before shutdown).
- Disabling effects or UI.
OnDisable()
OnDisable()
is automatically invoked when a script or its GameObject is disabled. This method is useful for performing cleanup operations such as unsubscribing from events, releasing resources, or other shutdown tasks. It is also called when a GameObject is destroyed or when scripts are reloaded after compilation. Key features include:
- Automatic Invocation: Called by Unity when a GameObject or its component is disabled.
- Cleanup Operations: Ideal for releasing resources or stopping ongoing processes.
- Script Reload Handling: Invoked during script reloads, ensuring proper state management.
- Editor Support: Works in both runtime and edit mode, which is especially useful for testing in the Unity Editor.
Example: In XFactory’s manufacturing station, pressing a key (e.g., L
) can toggle the CNC machine’s work lights (Spot Light_01
, …, Spot Light_04
). When lights are disabled, OnDisable()
ensures a message is logged and the system shuts down cleanly. Attach this script to the CNC machine GameObject (CNC_Mill_Set
) in the manufacturing station. In the Inspector
, assign all four spotlights (Spot Light_01
, …, Spot Light_04
) to the spotLights
array. Update()
handles the light toggling on key press (L
), while OnDisable()
ensures proper shutdown behavior when the script or GameObject is deactivated.
using UnityEngine;
public class CNCLightController : MonoBehaviour
{
public GameObject[] spotLights; // Assign Spot Light_01 to Spot Light_04 in Inspector
private bool lightsOn = false;
void Update()
{
if (Input.GetKeyDown(KeyCode.L))
{
lightsOn = !lightsOn;
foreach (GameObject light in spotLights)
{
light.SetActive(lightsOn);
}
}
}
void OnDisable()
{
Debug.Log("CNC light system disabled — all spotlights turned off.");
}
}
OnDestroy()
OnDestroy()
is automatically invoked when a GameObject or its attached behavior is destroyed. This event typically occurs when a scene ends, a new scene is loaded, or the application is closed. Key features include:
- Scene Transitions and Application Shutdown: Called when switching scenes or shutting down the game, ensuring cleanup actions occur.
- Active Object Requirement: It is only invoked for game objects that were active at some point in the scene.
- Debug and Logging: Often used to log cleanup events or release resources as the application exits or transitions between scenes.
Example: Imagine the mobile robot in the assembly station is dynamically instantiated at runtime. When the user switches to another scene (e.g., from assembly to exhibit), OnDestroy()
handles cleanup—saving progress or reporting its removal. Attach this script to the mobile robot GameObject in the assembly station. OnDestroy()
ensures cleanup actions (like saving state or logging removal) occur when the robot is destroyed during scene transitions. Use the Space key in Update()
to simulate scene changes for testing.
using UnityEngine;
using UnityEngine.SceneManagement;
public class RobotDestructionLogger : MonoBehaviour
{
void OnEnable()
{
Debug.Log("Mobile robot initialized and ready.");
}
void OnDestroy()
{
Debug.Log("Mobile robot instance is being destroyed. Logging final state.");
// Save robot progress, disconnect from navigation system, etc.
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
Debug.Log("Scene change triggered. Mobile robot will be destroyed.");
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
}
}
}
Tutorial
Let’s extend oue drone further by simulating a landing-shutdown-power on-takeoff cycle:
- Press L to descend gently to a landing height, then disable the
DroneController.cs
script. - Press T to enable
DroneController.cs
and ascend back to the original height. - Use
OnDisable()
inDroneController.cs
for cleanup/logging when controls are locked, andOnEnable()
to signal reactivation.
Implementation:
-
Add
OnDisable()
/OnEnable()
methods to your existingDroneController.cs
:public class DroneController : MonoBehaviour { // ... existing code ... void OnEnable() { Debug.Log("DroneController enabled - controls active."); } void OnDisable() { Debug.Log("DroneController disabled - controls locked."); } }
-
Create
DroneLifecycleManager.cs
and attach it to the sameDrone
GameObject:using UnityEngine; using System.Collections; [RequireComponent(typeof(DroneController))] public class DroneLifecycleManager : MonoBehaviour { [Tooltip("Reference to the DroneController component")] public DroneController droneController; [Header("Landing / Takeoff Settings")] public float landingHeight = 0.1f; // Y position to land at public float speed = 2f; // Ascent/Descent speed private float initialHeight; void Awake() { if (droneController == null) droneController = GetComponent<DroneController>(); initialHeight = transform.position.y; } void Update() { if (Input.GetKeyDown(KeyCode.L)) StartCoroutine(Land()); if (Input.GetKeyDown(KeyCode.T)) StartCoroutine(TakeOff()); } IEnumerator Land() { // Descend until landingHeight while (transform.position.y > landingHeight) { transform.position += Vector3.down * speed * Time.deltaTime; yield return null; } // Disable control if (droneController != null) droneController.enabled = false; Debug.Log("DroneLifecycleManager: Landed, DroneController disabled."); } IEnumerator TakeOff() { // Re-enable control if (droneController != null) droneController.enabled = true; // Ascend back to initialHeight while (transform.position.y < initialHeight) { transform.position += Vector3.up * speed * Time.deltaTime; yield return null; } Debug.Log("DroneLifecycleManager: Takeoff complete, DroneController enabled."); } }
- Attach the
DroneLifecycleManager
component to yourDrone
GameObject (alongsideDroneController
). - In the
DroneLifecycleManager
inspector, drag the existingDroneController
component into theDrone Controller
field (it should auto-detect if left blank). -
Adjust
Landing Height
andSpeed
to control how low/far and how fast your drone descends and ascends. - Press L in Play mode. The drone will smoothly descend to the configured
landingHeight
. Once reached,DroneController
is disabled, locking out movement controls. Check the Console for theOnDisable()
log fromDroneController
. -
Press T in Play mode.
DroneController
is re-enabled, restoring full hover controls. The drone will ascend back up to its original altitude (initialHeight
). Check the Console for theOnEnable()
log fromDroneController
.
Key Takeaways
MonoBehaviour
is the backbone of Unity scripting, connecting your custom C# code to the engine’s lifecycle, event system, and component model. By understanding its initialization hooks (Awake()
, Start()
, OnEnable()
), frame and physics loops (Update()
, LateUpdate()
, FixedUpdate()
), and event callbacks for collisions, triggers, visibility, and teardown, you can design responsive, efficient, and maintainable behaviors. Leveraging serialized fields and public methods lets you configure and reuse scripts without altering code, while inherited members like transform
and gameObject
simplify direct interaction with scene objects. In practice, mastering these patterns allows you to build modular, event-driven gameplay systems that react seamlessly to both the Unity engine’s timing and player or environment interactions.