C2. Decision Logic & Methods
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 asif
,for
, andvoid
.- 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 existingif-else-if
example and rewrite it as aswitch-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.
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
, anddo-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
orfalse
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()
andToLower()
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 simpleif
statement executes a block of code when a condition is met. if-else
Statement: Theif-else
structure allows execution of different code blocks depending on whether the condition istrue
orfalse
.if-else-if
Ladder: Anif-else-if
ladder helps evaluate multiple conditions in a sequence.- Nested
if
Statement: A nestedif
is anif
statement placed inside anotherif
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.
- Create the Script:
- In the
Project
window, right-click in theAssets
folder and chooseCreate > 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; } } }
- In the
- Create and Configure the GameObject:
- In the
Hierarchy
, right-click and chooseCreate Empty
. - Rename the GameObject to
ifStatements
. - With
ifStatements
selected, go to theInspector
. - Click
Add Component
, then search for and select theifStatement
script.
- In the
- Enter Play Mode and Test the Logic:
- Click the Play button at the top of the Unity editor.
- In the
Hierarchy
, select theifStatements
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 toWindow > General > Console
. - Only new or changed results are logged, avoiding spam every frame.
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 likereturn
) 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 aswitch-case
, you streamline logic that’s both easier to read and extend. Useswitch-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:
- Execute the code inside the
do
block. - Evaluate the condition following the
while
keyword. - If the condition is
true
, repeat the loop; iffalse
, 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
: Thecontinue
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 afor
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 ifi
starts at5
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 ado-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.
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:
-
Replace the content of the
CsharpFundamentals.cs
script attached toScriptDebugger
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."; } }
- In the
Hierarchy
, select the playButton
onDisplay GT
. - In the
Inspector
, scroll to theButton (Script)
component. - Under the
OnClick()
section, click the+
button to add a new event. -
Drag the
ScriptDebugger
GameObject into the object field. -
From the function dropdown, choose:
ScriptDebugger → RunDiagnostics()
. Once connected, whenever the player presses the Play Button in XR, Unity will invoke theRunDiagnostics()
method—and your display will update in real time.
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.