F01


Learning Outcomes

  • Explain the concept of custom GameObject behaviors in Unity. Before class, review how MonoBehaviour scripts define interactivity in XR experiences and skim common C# keywords such as if, for, and void.
  • Write and test Boolean logic using if statements. As prep work, create simple scripts with expressions using ==, !=, <, and logical operators, then log results to the Unity Console.
  • Implement branching logic with switch-case. In your pre-class practice, take an existing if-else-if example and rewrite it as a switch-case block to compare structure and usage.
  • Use for loops to iterate over objects in Unity. Ahead of the session, modify a loop to print the names or tags of multiple objects in a provided Unity scene, and try to predict the output before running it.
  • Apply do-while loops for guaranteed single execution. Your task before class is to create a script that attempts a connection retry at least once, even if conditions start out false.
  • Define and call basic methods in Unity C#. Prior to class, review the structure of a method and implement one that prints a debug message.
  • Pass parameters to methods to customize behavior. Update an existing method in your project to accept a parameter (e.g., int, float, or array) and call it with different values.
  • Use return values from methods in Unity. For preparation, run an example that returns a number or string, log the result, and think about how it might integrate into a larger system.

Custom GameObject Behaviors

As you start to build custom behaviors and interactive features in your XR projects, understanding decision logic and methods (i.e., functions) becomes absolutely critical. In Unity—especially in complex, real-time simulations like XFactory—these foundational programming constructs enable you to design smart, reactive systems that feel natural, purposeful, and immersive.

F02

Core Concepts

  • Boolean Logic (if): Enables decision-making based on conditions. Use it to make objects respond to inputs, sensor values, or environmental changes—e.g., opening a door when a user is near, or changing color based on temperature thresholds.
  • Branching Logic (switch-case): Handles multiple distinct outcomes cleanly. Ideal for scenarios like switching machine modes, selecting camera views, managing dialogue trees, or controlling device states based on object types.
  • Iterative Logic (for, while, and do-while Loops): Automates repetition and sequence handling. Use loops to scan multiple objects in a scene, perform timed events (like step-by-step assembly), or continuously monitor input from multiple sources.
  • Methods with Parameters: Encapsulate logic into reusable blocks that accept input. Use parameters like position vectors, object names, or force levels to perform dynamic actions—e.g., deploying different effects based on object size or category.
  • Methods with Return Values: Produce results for further use. Great for checking conditions (e.g., has the player reached a zone?), computing values (e.g., speed or angle), or querying systems (e.g., fetching sensor states or nearest interactable).
  • Coroutines: Enable asynchronous or delayed operations in Unity. Use them for tasks like waiting between animation steps, triggering effects over time, or sequencing events without freezing the main update loop.
  • Event-Driven Logic (using UnityEvents or C# delegates): Allows decoupling of triggers from behaviors. Use this to signal when a user presses a button, a timer completes, or an object enters a zone—enabling modular, reactive design patterns.

Key Features

  • Dynamic Interactivity: Build responsive systems that adapt to player presence, gaze, gesture, or controller input—ensuring immersive and context-sensitive feedback.
  • Automation & Repetition: Use loops and coroutines to streamline repeated actions like inventory updates, animation control, data polling, or batch interactions with multiple objects.
  • Modular Code Design: Break complex behaviors into clear, manageable functions—simplifying debugging, maintenance, and feature extension.
  • Customization & Reuse: Use parameterized methods and switch statements to reuse logic across multiple object types—keeping your scripts clean and adaptable.
  • Scalability: Structure your codebase to accommodate growth. Good use of methods, logic blocks, and events allows your XR environment to scale from prototypes to production.
  • Real-Time Feedback: Combine logic with sensor input or user tracking to adapt visuals, behaviors, or sounds in real time—boosting realism and user engagement.

Boolean Logic (if)

In C#, decision-making is often driven by Boolean expressions—expressions that evaluate to either true or false. Whether you are checking a robot’s status, validating sensor data, or controlling simulation flow, Boolean expressions form the backbone of your decision logic in XR development. Key concepts in Boolean logic include:

  • Expression vs. Statement: An expression combines values, variables, operators, and methods to produce a single value. A statement (such as an if statement) is a complete instruction that often contains one or more expressions.
  • Boolean Expression: When an expression returns a true or false value, it is called a Boolean expression. This value drives conditional statements (e.g., if, while, etc.) to branch execution.

Boolean Operators

Equality (==): Checks if two values are the same.

// Check if the selected wrench matches the required tool in the assembly station
string selectedTool = "double_open_end_wrench_small";
string requiredTool = "double_open_end_wrench_small";
Debug.Log(selectedTool == requiredTool); // Output: True

// Validate if the active material handling equipment ID matches a scanned ID
string scannedID = "Reach_Truck_01a_Prefab";
string activeID = "Pallet_Truck_01a_Prefab";
Debug.Log(scannedID == activeID); // Output: False

Inequality (!=): Checks if two values are not the same.

// Ensure the robot assigned to welding is not the same as the one with a claw
string weldingRobot = "prop_ind_robot_arm_arc_welder";
string handlingRobot = "prop_ind_robot_arm_claw";
Debug.Log(weldingRobot != handlingRobot); // Output: True

// Check if a drone's current task is different from its previous task
string currentTask = "Scanning";
string previousTask = "Scanning";
Debug.Log(currentTask != previousTask); // Output: False

Comparison Operators

Use these when comparing numerical values like a drone’s battery level or a machine’s cycle time:

  • Greater than (>)
  • Less than (<)
  • Greater than or equal to (>=) or
  • Less than or equal to (<=)
int batteryLevel = 85;          // Drone battery level
Debug.Log(batteryLevel > 50);   // Is battery sufficient? → True
Debug.Log(batteryLevel < 100);  // Below full charge? → True
Debug.Log(batteryLevel >= 85);  // Fully charged? → True
Debug.Log(batteryLevel <= 80);  // Needs charging? → False

Logical Negation Operator

The logical negation operator (!) inverts a Boolean value. Although the inequality operator (!=) uses !, it should not be confused with the standalone negation operator.

bool is3DPrinterIdle = false;
Debug.Log(!is3DPrinterIdle); // Output: True, indicating the 3D printer is running.

String Comparisons

When comparing strings, especially those from user input, it is important to handle cases and extra spaces:

  • Direct Comparison: A direct comparison may fail if there are unexpected spaces or case differences.
string scannedItem = " Box01";
Debug.Log(scannedItem == "Box01"); // Output: False (due to leading space)
  • Improved Comparison via String Methods: Use methods like Trim() and ToLower() to “massage” the data. This is especially useful in Unity UI systems where engineers interact with labels, control panels, or machine status displays.
string tag1 = " qr_code";
string tag2 = "QR_Code ";
Debug.Log(tag1.Trim().ToLower() == tag2.Trim().ToLower()); // Output: True

Conditional Operator

The conditional operator (?:) offers a compact syntax for decision logic. It evaluates a Boolean expression and returns one of two values. The basic syntax is <condition> ? <value if true> : <value if false>.

int scannedBoxes = 12;
int reward = scannedBoxes > 10 ? 100 : 50;
Debug.Log($"Reward Points: {reward}"); // Output: Reward Points: 100

if Statement

The if statement is one of the most fundamental decision-making tools in C#. It evaluates a Boolean expression and executes a block of code only if the expression is true. In XFactory, if statements can be used to control simulation logic, such as robot movement, safety checks, machine activation, alert handling, and post-processing readiness. In any if statement, if the condition evaluates to true, the code inside the { } executes; otherwise, it is ignored:

if (condition)
{
    // Code to execute if the condition is true
}

Types of if Statements:

  • Simple if Statement: A simple if statement executes a block of code when a condition is met.
  • if-else Statement: The if-else structure allows execution of different code blocks depending on whether the condition is true or false.
  • if-else-if Ladder: An if-else-if ladder helps evaluate multiple conditions in a sequence.
  • Nested if Statement: A nested if is an if statement placed inside another if block.
  • Multiple Conditions with Logical Operators: C# allows combining multiple conditions using logical operators, including && (AND: Ensures both conditions are true) and || (OR: Ensures at least one condition is true).

Example: Let’s create a simple script to showcase different types of if Statement. You will be able to modify variables in Play Mode and observe the logic results in the Console.

  1. Create the Script:
    • In the Project window, right-click in the Assets folder and choose Create > MonoBehaviour Script.
    • Name it: ifStatement
    • Double-click to open it in your code editor.
    • Replace the contents with the following:
     using UnityEngine;
    
     public class ifStatement : MonoBehaviour
     {
         public float droneSafeDistance = 0.5f;
         public int componentCount = 4;
         public int droneBattery = 30;
         public bool isRobotAvailable = true;
         public int partCount = 3;
         public bool isGateClosed = true;
         public bool isWeldingRobotReady = true;
    
         private string lastDroneDistanceMsg = "";
         private string lastComponentMsg = "";
         private string lastBatteryMsg = "";
         private string lastTendingMsg = "";
         private string lastWeldingMsg = "";
    
         void Update()
         {
             // 1. Simple if
             string droneDistanceMsg = "";
             if (droneSafeDistance <= 0.5f)
             {
                 droneDistanceMsg = "Drone is too close to shelves.";
             }
             if (droneDistanceMsg != "" && droneDistanceMsg != lastDroneDistanceMsg)
             {
                 Debug.Log(droneDistanceMsg);
                 lastDroneDistanceMsg = droneDistanceMsg;
             }
    
             // 2. if-else
             string componentMsg = componentCount >= 5
                 ? "Proceed to assembly station."
                 : "Insufficient components for assembly.";
             if (componentMsg != lastComponentMsg)
             {
                 Debug.Log(componentMsg);
                 lastComponentMsg = componentMsg;
             }
    
             // 3. if-else-if ladder
             string batteryMsg = "";
             if (droneBattery >= 75)
             {
                 batteryMsg = "Drone ready for long-distance delivery.";
             }
             else if (droneBattery >= 40)
             {
                 batteryMsg = "Drone ready for local delivery.";
             }
             else
             {
                 batteryMsg = "Drone needs charging.";
             }
             if (batteryMsg != lastBatteryMsg)
             {
                 Debug.Log(batteryMsg);
                 lastBatteryMsg = batteryMsg;
             }
    
             // 4. Nested if
             string tendingMsg = "";
             if (isRobotAvailable)
             {
                 tendingMsg = partCount >= 3
                     ? "Begin robotic machine tending."
                     : "Waiting for more parts to start machine tending.";
             }
             else
             {
                 tendingMsg = "Machine tending robot is offline.";
             }
             if (tendingMsg != lastTendingMsg)
             {
                 Debug.Log(tendingMsg);
                 lastTendingMsg = tendingMsg;
             }
    
             // 5. Logical AND
             string weldingMsg = "";
             if (isGateClosed && isWeldingRobotReady)
             {
                 weldingMsg = "Initiate welding operation.";
             }
             if (weldingMsg != "" && weldingMsg != lastWeldingMsg)
             {
                 Debug.Log(weldingMsg);
                 lastWeldingMsg = weldingMsg;
             }
         }
     }
    
  2. Create and Configure the GameObject:
    • In the Hierarchy, right-click and choose Create Empty.
    • Rename the GameObject to ifStatements.
    • With ifStatements selected, go to the Inspector.
    • Click Add Component, then search for and select the ifStatement script.
  3. Enter Play Mode and Test the Logic:
    • Click the Play button at the top of the Unity editor.
    • In the Hierarchy, select the ifStatements GameObject.
    • In the Inspector, you will see the public variables exposed by the script.
    • Modify these values while Play Mode is active. Each change will trigger a corresponding message in the Console only when the result changes.
    • If the Console window isn’t visible, go to Window > General > Console.
    • Only new or changed results are logged, avoiding spam every frame.

F03

You may add this to a UI in XFactory, including UI sliders or buttons to change variables interactively, or even use the results to trigger animations, sounds, or robot actions.


Branching Logic (switch-case)

C# provides several ways to implement branching logic. While if-else if-else constructs are versatile, switch-case statements can offer a more concise and readable alternative when evaluating a single variable against multiple known values. This makes them especially useful in contexts such as game state management, handling user inputs, or processing enumerated types in Unity.

switch-case Construct

The switch-case statement allows you to match a variable or expression against a set of potential values:

switch (expression)
{
    case value1:
        // Code executed if expression equals value1.
        break;
    case value2:
        // Code executed if expression equals value2.
        break;
    // Add more cases as needed.
    default:
        // Code executed if no cases match.
        break;
}
  • switch(expression): The variable or expression to be evaluated.
  • case Labels: Specific values compared against the switch expression.
  • break Statement: Terminates the execution of a case. Without it, C# prevents accidental “fall-through” from one case to the next.
  • default Case: An optional catch-all section that executes if no other cases match.

How switch-case Works

  • Evaluation: The switch expression is evaluated once.
  • Comparison: The value is compared sequentially against each case label.
  • Execution: When a match is found, the corresponding block of code executes until a break (or another terminating statement like return) is encountered.
  • No Fall-Through: Unlike some languages, C# does not allow implicit fall-through from one case to another, reducing potential bugs.
  • Default Handling: If no match is found and a default case exists, its code block is executed.

switch-case Use Cases

switch-case is ideal for tasks such as mapping game states, assigning roles or titles based on numerical values, or interpreting commands and user inputs. These statements are most effective when:

  • Single Value Comparison: You are evaluating one variable or expression against a list of discrete values.
  • Limited and Defined Cases: There are a finite number of known outcomes.
  • Clarity: Grouping related outcomes in a single construct can simplify code maintenance and readability.

if-else to switch-case

switch-case can simplify code that might otherwise be cluttered with lengthy if-else if-else constructs. Consider a scenario in XFactory simulation where you want to determine which station is currently active and what behavior or event should be triggered in Unity.

Original if-else if-else:

string currentStation = "welding";
string instruction = "";

if (currentStation == "logistics")
{
    instruction = "Initiate box scan and drone loading.";
}
else if (currentStation == "production")
{
    instruction = "Activate CNC machine and 3D printer.";
}
else if (currentStation == "assembly")
{
    instruction = "Deploy robotic arms for engine assembly.";
}
else if (currentStation == "welding")
{
    instruction = "Engage welding sequence for car frame.";
}
else if (currentStation == "exhibit")
{
    instruction = "Start AR/VR headset demo and robot showcase.";
}
else
{
    instruction = "Station not recognized. Standby mode activated.";
}

Debug.Log(instruction);

Converted to switch-case:

string currentStation = "welding";
string instruction = "";

switch (currentStation)
{
    case "logistics":
        instruction = "Initiate box scan and drone loading.";
        break;
    case "production":
        instruction = "Activate CNC machine and 3D printer.";
        break;
    case "assembly":
        instruction = "Deploy robotic arms for engine assembly.";
        break;
    case "welding":
        instruction = "Engage welding sequence for car frame.";
        break;
    case "exhibit":
        instruction = "Start AR/VR headset demo and robot showcase.";
        break;
    default:
        instruction = "Station not recognized. Standby mode activated.";
        break;
}

Debug.Log(instruction);

By switching from if-else chains to a switch-case, you streamline logic that’s both easier to read and extend. Use switch-case if you are evaluating one variable or expression against a list of discrete, known values and want cleaner, more readable branching logic for maintenance and clarity.


Iterative Logic (for)

In Unity, loops are essential for iterating through arrays, controlling simulation logic, and managing frame-based updates. The for loop is especially useful when you need precise control over how many times a block of code should execute—such as iterating through robots on an assembly line, scanning QR codes on boxes, or updating conveyor belts in the XFactory environment. A for loop executes a block of code a specific number of times. Its syntax consists of three main components:

  • Initializer: Sets up the iteration variable (e.g., int i = 0).
  • Condition: Determines how long the loop will run (e.g., i < 10).
  • Iterator: Updates the variable after each iteration (e.g., i++).
for (
    int i = 0;     // Initializer: Start at rack index 0
    i < 5;         // Condition: Loop through 5 logistics racks
    i++            // Iterator: Move to the next rack
)
{
    // Simulate scanning rack i in the logistics station
    Debug.Log($"Scanning Rack #{i} for inventory...");
}

Iterating in Reverse

For loops allow you to control the direction of iteration. This is especially useful in factory scenarios—like unloading scanned packages from a drone delivery log or reversing robot actions in a fault recovery system. For example, the following loop simulates unloading from 5 logistics racks in reverse order (4 to 0):

for (
    int i = 4;     // Start at the last scanned rack index
    i >= 0;        // Continue while index is valid
    i--            // Step backward
)
{
    Debug.Log($"Unloading package from Rack #{i}...");
}

Custom Increments

You can change how fast the loop progresses by modifying the increment step. This can be useful in XFactory to model actions that occur less frequently—like checking every third item on a machine tending table or sampling sensor data at longer intervals. For example, the following loop inspects every third slot: 0, 3, 6, 9:

for (
    int i = 0;     // Start at item 0
    i < 12;        // Continue while within table bounds
    i += 3         // Jump by 3 positions
)
{
    Debug.Log($"Inspecting item slot {i} on the machine tending table...");
}

Exiting a Loop Early

Sometimes, a loop needs to stop before reaching its natural end—like when a scanner detects a defective box or a robot exceeds its temperature threshold. You can use the break keyword to do this. For example, this loop stops as soon as the defective package (at index 6) is found:

for (int i = 0; i < 10; i++)
{
    Debug.Log($"Scanning package #{i}...");
    
    if (i == 6)
    {
        Debug.Log("Defective package detected — stopping scan.");
        break;
    }
}

Iterating Over Arrays

A common scenario in Unity is to iterate through arrays to update game states, process inputs, or control animations. The for loop is particularly useful when you need fine control over which elements to process. For example, the following reverse iteration pattern allows to deactivate systems from end to start during a staged factory shutdown:

string[] shutdownSequence =
{
    "Exhibit Station",
    "Welding Station",
    "Assembly Station",
    "Logistics Station"
};

// Iterate in reverse order for controlled station shutdown.
for (int i = shutdownSequence.Length - 1; i >= 0; i--)
{
    Debug.Log(
        $"Shutting down: {shutdownSequence[i]}"
    );
}

Modifying Array Elements

The for loop allows modification of array contents, which is useful when updating inventory, relabeling scanned packages, or marking items for inspection in the XFactory logistics station.

string[] packageLabels =
{
    "Box_Large_01a",
    "Shipping_Crate_01a",
    "Box_01b_Stack",
    "Box_Small_01a_Stack"
};

// Flag a specific package due to a damaged barcode during inspection.
for (int i = 0; i < packageLabels.Length; i++)
{
    if (packageLabels[i] == "Box_01b_Stack")
    {
        packageLabels[i] = "FLAGGED_Box_01b";
    }
}

// Confirm updated labels by printing each one.
foreach (var label in packageLabels)
{
    Debug.Log(
        $"Package label: {label}"
    );
}

Iterative Logic (do-while)

A do-while loop is an iteration statement in C# that executes its code block at least once before checking a condition. This behavior makes it ideal when the loop’s logic must run first, with the outcome determining whether to continue iterating. Unlike the standard while loop—where the condition is evaluated before entering the loop—the do-while loop evaluates the condition after executing the code block, ensuring that the code runs at least one time. The basic execution flow and structure of a do-while loop are as follows:

  1. Execute the code inside the do block.
  2. Evaluate the condition following the while keyword.
  3. If the condition is true, repeat the loop; if false, exit the loop.
do {
    // Code block: This code executes at least one time
} while (condition);

Key Concepts

  • Iteration Statement: A construct that repeatedly executes a block of code until a condition is met.
  • Post-Test Loop: Because the condition is evaluated after the loop’s code block, the do-while loop is also known as a post-test loop.
  • Guaranteed Execution: The loop’s code block always executes at least once—even if the condition is initially false.
  • Control Flow: The logic within the loop may affect the condition that determines whether the loop repeats. This is useful when the loop’s outcome must be computed first.
  • Using continue: The continue statement can be employed inside a do-while loop to skip the remainder of the current iteration and move directly to the condition check, allowing finer control over the loop execution without breaking out entirely.

Example: In XFactory, for example, a do-while loop can simulate retrying a connection to a machine before giving up, ensuring at least one connection attempt is always made.

int retryCount = 0;
bool isConnectionEstablished;

do
{
    // Try to connect to a robotic arm in the welding station.
    isConnectionEstablished = AttemptRobotConnection();
    retryCount++;
}
while (!isConnectionEstablished && retryCount < 3);

do-while vs. for

Both the do-while and for loops are used for iteration in Unity, but they differ in structure and usage.

  • for Loop: Use a for loop when the number of iterations is known in advance. It is compact and efficient for tasks like processing a fixed number of items or executing predefined steps. for loop combines initialization, condition check, and iteration in one line. The condition is checked before each iteration, so the loop may not run if the condition is false at the start. for loop is best suited for situations where the number of iterations is known. For example, the following loop executes only if the condition (i < 5) is true from the beginning. It won’t run at all if i starts at 5 or higher:
// Simulate inspecting the first 5 packages on the storage rack.
for (int i = 0; i < 5; i++)
{
    Debug.Log($"Inspecting package #{i}");
}
  • do-while Loop: Use a do-while loop when the task should run at least once, even if the condition is false initially. It is useful for operations like prompting for input, running startup checks, or retrying an action. do-while loop executes the loop body first and then checks the condition. It guarantees that the loop body is executed at least once. do-while loop is ideal for situations where the loop must execute at least one time, regardless of the condition. For example, the following loop executes the check before evaluating whether more attempts are needed—ensuring the body runs at least once:
// Simulate checking package alignment at least once before allowing drone movement.
int attempts = 0;
do
{
    Debug.Log($"Alignment check attempt #{attempts}");
    attempts++;
} while (attempts < 5);

Methods in C#

A method (also called a function) is a reusable block of code designed to perform a specific task. In C#, methods are the primary means of breaking down complex problems into smaller, manageable units. They help in:

  • Organizing Code: Dividing your code into logical sections makes large programs easier to read and maintain.
  • Improving Readability: A well-named method tells you what it does without needing to examine the implementation details.
  • Facilitating Reusability: Instead of writing the same code repeatedly, you write it once in a method and call it wherever needed.
  • Encapsulating Functionality: Methods allow you to hide the underlying implementation details, exposing only necessary information through parameters and return values.
  • Isolating Functionality: Methods promote modularity by isolating functionality, allowing reuse across the program, simplifying updates, and making testing and debugging easier.

F04

Method Signature

A method signature uniquely identifies the method within a class by declaring the method’s:

  • Return Type: Specifies the type of data the method will return to the caller. If no data is returned, the return type is void.
  • Name: Should be descriptive, using PascalCase (each word capitalized) to indicate the action the method performs.
  • Parameters: (Optional) Inputs to the method that allow it to work with external data. They are specified inside parentheses and separated by commas.

Method Body

The method body is enclosed in curly braces { } and contains the actual code to be executed when the method is called. This is where the logic of the method is defined.

// Complete method with a signature and a body.
void PlayIntroNarration() 
{
    Debug.Log("Welcome to the XFactory immersive experience!");
}

Parameters

Parameters allow methods to accept input values, making them versatile. They work as placeholders that are replaced with actual values (arguments) when the method is called.

  • Data Types: Each parameter must have a declared type (e.g., int, string, double).
  • Multiple Parameters: You can define multiple parameters, separated by commas.
  • Naming: Parameter names should be meaningful to clarify what type of data is expected.
// Method that moves a forklift a certain distance at a given speed.
void MoveForklift(float distance, float speed)
{
    // Simulated forklift movement logic here
}

Return Values

A method can return a value to its caller. The return type specified in the method signature must match the type of value returned.

  • Returning Data: The return statement is used to send a value back to the caller.
  • Void Methods: If a method does not return any data, its return type is declared as void.
// Method that calculates the dimensions of a CNC raw material.
float CalculateFloorArea(float length, float width, float height)
{
    return length * width * height;
}

Calling Methods

Calling (or invoking) a method transfers control from the calling code to the method, executes its body, and then returns control back to the caller. Methods can be invoked from other methods, loops, conditionals, or any context where they’re in scope.

Invoking Methods from Start(): In this case, PlayIntroNarration() is invoked from Start(), so it runs automatically when the GameObject is initialized.

void Start()
{
    Debug.Log("Initializing XFactory...");
    PlayIntroNarration();
    Debug.Log("Initialization complete.");
}

void PlayIntroNarration() 
{
    Debug.Log("Hello, Jackets! Welcome to XFactory!");
}

Invoking Methods from UI Events: In Unity, you can also call a method in response to events, such as clicking a button in the UI or interacting with objects in an XR scene. This allows your scripts to respond dynamically to player input. For example, you can call a method when the Play Button on Display GT is pressed to trigger a custom action like running diagnostics or starting an animation:

  1. Replace the content of the CsharpFundamentals.cs script attached to ScriptDebugger with the following script:

     using UnityEngine;
     using TMPro;
    
     public class CsharpFundamentals : MonoBehaviour
     {
         public TMP_Text myText; // Reference to the display text (TextMeshPro)
    
         // This method can be called from a UI button's OnClick event
         public void RunDiagnostics()
         {
             myText.text = "Diagnostics started...\nAll systems nominal.";
         }
     }
    
  2. In the Hierarchy, select the play Button on Display GT.
  3. In the Inspector, scroll to the Button (Script) component.
  4. Under the OnClick() section, click the + button to add a new event.
  5. Drag the ScriptDebugger GameObject into the object field.

    F05

  6. From the function dropdown, choose: ScriptDebugger → RunDiagnostics(). Once connected, whenever the player presses the Play Button in XR, Unity will invoke the RunDiagnostics() method—and your display will update in real time.

    F06

You can hook up multiple methods to the same button, or reuse the same method across different UI events to keep your scripts modular and reusable.

Overloading Methods

C# allows you to create multiple methods with the same name but different parameter lists. This is known as method overloading. It enables you to perform similar operations using different inputs, improving code flexibility and readability. These overloaded methods let you call TriggerAlert() with no context, a specific station, or a station with a detailed issue—supporting flexible alert handling in simulation scenarios.

// Overloaded methods to trigger an alert in the XFactory control system.

void TriggerAlert()
{
    Debug.Log("General alert issued across the factory.");
}

void TriggerAlert(string stationName)
{
    Debug.Log($"Alert issued at {stationName} station.");
}

void TriggerAlert(string stationName, string issue)
{
    Debug.Log($"Alert at {stationName} station: {issue}");
}

Recursive Methods

A recursive method is one that calls itself to solve a problem by breaking it down into smaller, similar subproblems. In XR-based factory simulations, recursion can be used for structured tasks such as traversing assembly steps, hierarchy trees, or performing countdowns. This method could represent a simplified recursive routine for walking through a multi-stage robotic assembly sequence in XFactory, with each call representing one step.

// Recursive method to simulate stepping through assembly stages for a product.
void RunAssemblyStage(int stage)
{
    if (stage <= 0)
    {
        Debug.Log("Assembly process complete.");
    }
    else
    {
        Debug.Log($"Running assembly stage {stage}...");
        RunAssemblyStage(stage - 1);
    }
}

Methods with Parameters

Think of a method as a “black box” that takes input (parameters), processes it, and optionally returns a result. When you add parameters to your methods, you enable them to accept input values-making your code more flexible, reusable, and easier to maintain. A parameter refers to a variable defined in the method’s signature; it acts as a placeholder for the input values. An argument is the actual value or reference passed to a method when it is called.

Declaring Parameters

When you create a method, you can declare one or more parameters. Each parameter is defined by its type and name.

public class ParameterDemo : MonoBehaviour
{
    // A method that simulates scanning boxes up to a given number
    void ScanBoxes(int max)
    {
        for (int i = 0; i < max; i++)
        {
            Debug.Log("Scanning Box #" + i);
        }
    }

    void Start()
    {
        // Scan 5 boxes at the logistics station
        ScanBoxes(5);
    }
}

Reuse Code Using Parameters

Imagine you need to display sensor data from multiple areas of the factory (like power usage, machine uptime, or item counts). Instead of duplicating code, you write a single method that accepts the data as a parameter.

public class DisplayArrayDemo : MonoBehaviour
{
    // Method that displays each value from a sensor array
    void DisplaySensorData(int[] data)
    {
        foreach (int reading in data)
        {
            Debug.Log("Sensor Reading: " + reading);
        }
    }

    void Start()
    {
        int[] assemblyData = { 10, 20, 15 }; // Data from the assembly station
        int[] weldingData = { 90, 85, 88 };  // Data from the welding station

        DisplaySensorData(assemblyData);
        DisplaySensorData(weldingData);
    }
}

Method Scope

Local variables are declared inside a method and are only accessible within that method. Class-level variables (sometimes called global) are declared outside any method and can be accessed by all methods in the same class. The following example shows how status is only visible inside ShowStatus(), while stationName can be accessed throughout the class:

public class ScopeExample : MonoBehaviour
{
    string stationName = "Welding Station"; // Class-level variable

    void ShowStatus()
    {
        string status = "Operational"; // Local variable
        Debug.Log(stationName + " is " + status);
    }

    void Start()
    {
        ShowStatus();

        // Debug.Log(status); // Error: 'status' is not accessible here
        Debug.Log("Monitoring: " + stationName); // OK: class-level variable
    }
}

Parameter Values

When value types like int, float, or bool are passed into a method, a copy of the value is made. Changes inside the method do not affect the original variable outside the method. In the following example, even though totalWeight is updated inside the method, the original total variable in Start() remains unchanged because it was passed by value:

public class ValueTypeExample : MonoBehaviour
{
    void CalculateTotalWeight(int boxWeight, int count, int totalWeight)
    {
        totalWeight = boxWeight * count;
        Debug.Log("Inside method: Total weight = " + totalWeight);
    }

    void Start()
    {
        int boxWeight = 10;
        int count = 3;
        int total = 0;

        CalculateTotalWeight(boxWeight, count, total);
        Debug.Log("After method call: Total weight = " + total); // Still 0
    }
}

Parameter References

When reference types like arrays or objects are passed into a method, the method receives a reference to the original data. Changes inside the method directly affect the original object—useful for updating factory data in real time. Since arrays are reference types, modifying them inside the method also changes the original array.

public class ReferenceTypeExample : MonoBehaviour
{
    void ResetPackageWeights(int[] weights)
    {
        for (int i = 0; i < weights.Length; i++)
        {
            weights[i] = 0; // Reset each package's weight
        }
    }

    void Start()
    {
        int[] packageWeights = { 10, 15, 20 };
        ResetPackageWeights(packageWeights);

        foreach (int weight in packageWeights)
        {
            Debug.Log("Weight after reset: " + weight); // All values will be 0
        }
    }
}

Optional Parameters

C# supports optional parameters by allowing default values in method definitions. This is helpful when a method has sensible defaults, like logging machine status. Optional parameters reduce the need for method overloads and make calls more concise when default values suffice.

public class OptionalParamsExample : MonoBehaviour
{
    void LogStatus(
        string stationName,
        string status = "Online",
        bool notify = true
    )
    {
        Debug.Log($"Station: {stationName}, Status: {status}, Notify: {notify}");
    }

    void Start()
    {
        LogStatus("Assembly");                        // Uses default status and notify
        LogStatus("Welding", "Offline", false);       // All parameters provided
    }
}

Name Parameters

Named parameters let you pass arguments in any order by specifying their names. This makes your code more readable, especially when dealing with multiple optional settings. Named parameters help clarify intent, especially when passing values out of order or skipping optional ones.

public class NamedParamsExample : MonoBehaviour
{
    void LogStatus(string stationName, string status = "Online", bool notify = true)
    {
        Debug.Log($"Station: {stationName}, Status: {status}, Notify: {notify}");
    }

    void Start()
    {
        LogStatus(status: "Maintenance", stationName: "3D Printer", notify: false);
        LogStatus("Robot Arm A", notify: true, status: "Idle");
    }
}

Methods with Return Values

When a method has a return value, it processes input (via parameters) and then produces an output. This design allows you to build modular, maintainable, and reusable code in your Unity projects. Methods with return values receive input, process them, and return output. They help isolate functionality and improve code readability.

// Adds two numbers and returns the sum.
int AddNumbers(int a, int b)
{
    return a + b;
}

Return Type Syntax

The return type is the first part of a method’s signature and specifies what kind of value the method will output. Use the return keyword to send a result back to the caller. In void methods, return is optional and is only used to exit early. A method can return:

  • A primitive type (e.g., int, float, bool)
  • A reference type (e.g., string, arrays, or objects)
  • Or void if it doesn’t return a value

Example: The LogStationStatus() method below performs an action without returning a result, while CalculatePowerUsage() returns a calculated value that can be reused in the simulation:

// Logs a simple message — returns nothing.
void LogStationStatus(string station)
{
    Debug.Log($"Status check at: {station} station.");
}

// Calculates total power used by a machine — returns a float.
float CalculatePowerUsage(float voltage, float current)
{
    return voltage * current;
}

Returning Numbers

Methods that return numeric values are commonly used for calculations, measurements, or conversions. The returned value must match the declared return type (int, float, double, etc.). Use explicit casting when converting between numeric types. Also, ensure operations are performed using the correct types to avoid precision loss. In XFactory, for example, this could be used to monitor or display real-time energy consumption for the welding station in a performance dashboard:

// Calculates the estimated energy cost for a welding robot based on time and rate.
float CalculateEnergyCost(float durationMinutes, float ratePerMinute)
{
    return durationMinutes * ratePerMinute;
}

Returning Strings

Methods that return strings are often used for tasks such as formatting labels, generating status messages, or transforming text data. Use built-in string methods (e.g., ToUpper, Substring, Replace) for efficient operations. Returned strings can be used for UI labels, debug logs, or metadata tags. In XFactory, for example, this could be used to dynamically generate labels like [WELDING] STATUS: ACTIVE for use on monitors or HUD elements:

// Generates a station status label in uppercase for display on the control panel.
string FormatStatusLabel(string stationName, string status)
{
    return $"[{stationName.ToUpper()}] STATUS: {status.ToUpper()}";
}

Returning Booleans

Boolean-returning methods are useful for decision-making, especially when evaluating safety checks, equipment states, or operational rules. Use conditional logic to evaluate system conditions. These methods integrate well with if statements, UI toggles, and safety triggers. In XFactory, for example, this method could be called before activating the welding station to ensure safety compliance:

// Checks whether a safety gate is closed before starting welding.
bool IsGateClosed(bool leftGate, bool rightGate)
{
    return leftGate && rightGate;
}

Returning Arrays

Methods can return arrays to provide a collection of values, which is useful when multiple related data points need to be sent back from a single method call. The return type defines the array element type and dimensions (e.g., int[], string[]). Additionally, ensure the returned array is properly initialized and populated before returning. In XFactory, for example, this method could be used to scan system statuses and list all machines that are currently offline for maintenance or diagnostics:

// Identifies all machine IDs currently marked as "offline" from a status array.
string[] GetOfflineMachines(string[] machineStatuses)
{
    List<string> offlineMachines = new List<string>();

    foreach (string status in machineStatuses)
    {
        if (status.Contains("Offline"))
        {
            offlineMachines.Add(status);
        }
    }

    return offlineMachines.ToArray();
}

Key Takeaways

Mastering decision logic and methods in Unity equips you to create intelligent, interactive, and scalable XR experiences. By combining Boolean checks, branching structures like switch-case, and iterative loops (for, while, do-while), you can control complex behaviors, automate repetitive tasks, and adapt to dynamic inputs in real time. Methods—especially those with parameters and return values—help you organize code into reusable, modular units, making projects easier to maintain and extend. Whether it’s reacting to player actions, managing simulation states, or processing sensor data, these programming tools form the backbone of responsive, maintainable, and immersive Unity applications.