Control Flow: If Statements and Loops
Control flow is how your program decides what to do and when to do it. It's the foundation of all game logic—from checking if a player has enough health to attack, to looping through enemies to update their AI, to creating complex decision trees for game mechanics.
In this chapter, you'll learn how to make your code make decisions and repeat actions, which are essential skills for any game developer. By the end, you'll be able to create conditional logic, handle different game states, and write efficient loops that power your game's core mechanics.
What You'll Learn
- If statements - Making decisions based on conditions
- Switch statements - Handling multiple conditions elegantly
- For loops - Repeating code a specific number of times
- While loops - Repeating code while a condition is true
- Foreach loops - Iterating through collections
- Nested control structures - Combining control flow for complex logic
- Practical game examples - Real-world applications in Unity
Prerequisites
- Completed C# Basics: Variables, Data Types, and Operators
- Understanding of variables and data types
- Basic familiarity with Unity (optional, but helpful)
Understanding Control Flow
Control flow determines the order in which your code executes. Without control flow, your program would run from top to bottom, line by line, with no way to make decisions or repeat actions.
Why Control Flow Matters in Games:
- Decision Making: Should the player take damage? Is the enemy in range? Can the player afford this item?
- Repetition: Update all enemies every frame, check all collectibles, process all particles
- State Management: Handle different game states (menu, playing, paused, game over)
- Event Handling: Respond to player input, collisions, and game events
If Statements: Making Decisions
If statements let your code make decisions based on conditions. They're the most fundamental control structure in programming.
Basic If Statement
The simplest form checks a condition and executes code if it's true:
int playerHealth = 100;
if (playerHealth > 0)
{
Debug.Log("Player is alive!");
}
Syntax:
if (condition)
{
// Code to execute if condition is true
}
If-Else Statements
Add an alternative path when the condition is false:
int playerHealth = 50;
if (playerHealth > 0)
{
Debug.Log("Player is alive!");
}
else
{
Debug.Log("Player is dead!");
}
If-Else If-Else Chains
Handle multiple conditions:
int playerHealth = 75;
if (playerHealth >= 100)
{
Debug.Log("Player is at full health!");
}
else if (playerHealth >= 50)
{
Debug.Log("Player is healthy.");
}
else if (playerHealth >= 25)
{
Debug.Log("Player is wounded.");
}
else
{
Debug.Log("Player is critically injured!");
}
Comparison Operators
Use these operators to create conditions:
int score = 1000;
int highScore = 500;
// Equality
if (score == highScore) { } // Equal to
if (score != highScore) { } // Not equal to
// Comparison
if (score > highScore) { } // Greater than
if (score < highScore) { } // Less than
if (score >= highScore) { } // Greater than or equal
if (score <= highScore) { } // Less than or equal
Logical Operators
Combine multiple conditions:
int playerHealth = 75;
bool hasKey = true;
// AND operator - both conditions must be true
if (playerHealth > 0 && hasKey)
{
Debug.Log("Player can open the door!");
}
// OR operator - at least one condition must be true
if (playerHealth <= 0 || !hasKey)
{
Debug.Log("Player cannot proceed.");
}
// NOT operator - reverses the condition
if (!hasKey)
{
Debug.Log("Player needs a key!");
}
Game Example: Health System
Here's a practical example using if statements for a health system:
using UnityEngine;
public class PlayerHealth : MonoBehaviour
{
public int maxHealth = 100;
private int currentHealth;
void Start()
{
currentHealth = maxHealth;
}
public void TakeDamage(int damage)
{
currentHealth -= damage;
if (currentHealth <= 0)
{
currentHealth = 0;
Die();
}
else if (currentHealth < maxHealth * 0.3f)
{
Debug.Log("Low health warning!");
// Play low health sound effect
}
}
public void Heal(int amount)
{
if (currentHealth < maxHealth)
{
currentHealth += amount;
if (currentHealth > maxHealth)
{
currentHealth = maxHealth;
}
}
}
void Die()
{
Debug.Log("Player has died!");
// Handle death logic
}
}
Switch Statements: Multiple Conditions
Switch statements are perfect when you have many possible values to check. They're cleaner than long if-else chains.
Basic Switch Statement
int gameState = 1;
switch (gameState)
{
case 0:
Debug.Log("Main Menu");
break;
case 1:
Debug.Log("Playing");
break;
case 2:
Debug.Log("Paused");
break;
case 3:
Debug.Log("Game Over");
break;
default:
Debug.Log("Unknown state");
break;
}
Syntax:
switch (variable)
{
case value1:
// Code for value1
break;
case value2:
// Code for value2
break;
default:
// Code if no case matches
break;
}
Switch with Strings
Switch works with strings too:
string weaponType = "sword";
switch (weaponType)
{
case "sword":
Debug.Log("Slashing attack!");
break;
case "bow":
Debug.Log("Ranged attack!");
break;
case "staff":
Debug.Log("Magic attack!");
break;
default:
Debug.Log("Unknown weapon!");
break;
}
Switch Expressions (C# 8.0+)
Modern C# allows switch expressions for cleaner code:
string weaponType = "sword";
int damage = weaponType switch
{
"sword" => 10,
"bow" => 8,
"staff" => 12,
_ => 5 // Default case
};
Game Example: Game State Manager
Here's a practical game state manager using switch:
using UnityEngine;
public enum GameState
{
MainMenu,
Playing,
Paused,
GameOver,
Victory
}
public class GameStateManager : MonoBehaviour
{
private GameState currentState = GameState.MainMenu;
public void ChangeState(GameState newState)
{
// Exit current state
ExitState(currentState);
// Enter new state
currentState = newState;
EnterState(currentState);
}
void EnterState(GameState state)
{
switch (state)
{
case GameState.MainMenu:
Debug.Log("Entering Main Menu");
// Show menu UI
break;
case GameState.Playing:
Debug.Log("Starting Game");
// Hide menu, start gameplay
Time.timeScale = 1f;
break;
case GameState.Paused:
Debug.Log("Game Paused");
// Show pause menu
Time.timeScale = 0f;
break;
case GameState.GameOver:
Debug.Log("Game Over");
// Show game over screen
break;
case GameState.Victory:
Debug.Log("Victory!");
// Show victory screen
break;
}
}
void ExitState(GameState state)
{
// Handle state exit logic if needed
}
}
For Loops: Repeating Code
For loops repeat code a specific number of times. They're perfect when you know exactly how many iterations you need.
Basic For Loop
// Count from 0 to 9
for (int i = 0; i < 10; i++)
{
Debug.Log("Count: " + i);
}
Syntax:
for (initialization; condition; increment)
{
// Code to repeat
}
Parts:
- Initialization: Runs once at the start (e.g.,
int i = 0) - Condition: Checked before each iteration (e.g.,
i < 10) - Increment: Runs after each iteration (e.g.,
i++)
Common For Loop Patterns
// Count up
for (int i = 0; i < 10; i++)
{
Debug.Log(i);
}
// Count down
for (int i = 10; i > 0; i--)
{
Debug.Log(i);
}
// Count by 2s
for (int i = 0; i < 10; i += 2)
{
Debug.Log(i); // 0, 2, 4, 6, 8
}
// Count with custom step
for (int i = 100; i >= 0; i -= 10)
{
Debug.Log(i); // 100, 90, 80, ..., 0
}
Game Example: Spawning Enemies
Here's how to use a for loop to spawn multiple enemies:
using UnityEngine;
public class EnemySpawner : MonoBehaviour
{
public GameObject enemyPrefab;
public int enemyCount = 5;
public float spawnRadius = 10f;
void Start()
{
SpawnEnemies();
}
void SpawnEnemies()
{
for (int i = 0; i < enemyCount; i++)
{
// Calculate random position
Vector2 randomPosition = Random.insideUnitCircle * spawnRadius;
Vector3 spawnPosition = new Vector3(randomPosition.x, 0, randomPosition.y);
// Instantiate enemy
Instantiate(enemyPrefab, spawnPosition, Quaternion.identity);
Debug.Log("Spawned enemy " + (i + 1));
}
}
}
Nested For Loops
You can put loops inside loops for 2D operations:
// Create a grid of objects
for (int x = 0; x < 10; x++)
{
for (int y = 0; y < 10; y++)
{
Vector3 position = new Vector3(x, 0, y);
// Create object at position
Debug.Log($"Grid position: ({x}, {y})");
}
}
While Loops: Conditional Repetition
While loops repeat code as long as a condition is true. Use them when you don't know how many iterations you'll need.
Basic While Loop
int count = 0;
while (count < 5)
{
Debug.Log("Count: " + count);
count++; // Important: Update the condition!
}
Syntax:
while (condition)
{
// Code to repeat
// Must update condition to avoid infinite loop!
}
Do-While Loops
Do-while loops always execute at least once:
int count = 0;
do
{
Debug.Log("Count: " + count);
count++;
} while (count < 5);
Difference: The condition is checked after the first execution.
Game Example: Health Regeneration
Here's a practical example using a while loop for health regeneration:
using UnityEngine;
using System.Collections;
public class HealthRegeneration : MonoBehaviour
{
public int maxHealth = 100;
private int currentHealth = 50;
public int regenRate = 1;
public float regenInterval = 1f;
void Start()
{
StartCoroutine(RegenerateHealth());
}
IEnumerator RegenerateHealth()
{
while (currentHealth < maxHealth)
{
currentHealth += regenRate;
if (currentHealth > maxHealth)
{
currentHealth = maxHealth;
}
Debug.Log($"Health: {currentHealth}/{maxHealth}");
yield return new WaitForSeconds(regenInterval);
}
Debug.Log("Health fully regenerated!");
}
}
Infinite Loops Warning
Never create infinite loops accidentally:
// BAD - This will freeze your game!
while (true)
{
Debug.Log("This runs forever!");
}
// GOOD - Always have an exit condition
bool isRunning = true;
while (isRunning)
{
// Do something
if (someCondition)
{
isRunning = false; // Exit the loop
}
}
Foreach Loops: Iterating Collections
Foreach loops iterate through collections like arrays and lists. They're simpler than for loops when you just need to process each item.
Basic Foreach Loop
string[] weapons = { "sword", "bow", "staff", "dagger" };
foreach (string weapon in weapons)
{
Debug.Log("Weapon: " + weapon);
}
Syntax:
foreach (type variableName in collection)
{
// Code to execute for each item
}
Game Example: Processing Enemies
Here's how to use foreach to update all enemies:
using UnityEngine;
using System.Collections.Generic;
public class EnemyManager : MonoBehaviour
{
public List<GameObject> enemies = new List<GameObject>();
void Update()
{
// Update all enemies
foreach (GameObject enemy in enemies)
{
if (enemy != null)
{
// Update enemy AI, health, etc.
EnemyAI enemyAI = enemy.GetComponent<EnemyAI>();
if (enemyAI != null)
{
enemyAI.UpdateAI();
}
}
}
}
public void RemoveDeadEnemies()
{
// Remove null or destroyed enemies
enemies.RemoveAll(enemy => enemy == null);
}
}
Foreach Limitations
You can't modify the collection while iterating:
// BAD - This will cause an error!
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
foreach (int number in numbers)
{
if (number % 2 == 0)
{
numbers.Remove(number); // ERROR!
}
}
// GOOD - Create a new list or use a for loop backwards
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
for (int i = numbers.Count - 1; i >= 0; i--)
{
if (numbers[i] % 2 == 0)
{
numbers.RemoveAt(i); // Safe!
}
}
Combining Control Structures
You can combine control structures to create complex logic:
Nested If Statements
int playerLevel = 15;
int playerGold = 500;
if (playerLevel >= 10)
{
if (playerGold >= 1000)
{
Debug.Log("Player can buy premium item!");
}
else
{
Debug.Log("Player needs more gold.");
}
}
else
{
Debug.Log("Player level too low.");
}
Loops with Conditions
// Find first enemy with low health
GameObject[] enemies = GameObject.FindGameObjectsWithTag("Enemy");
for (int i = 0; i < enemies.Length; i++)
{
EnemyHealth enemyHealth = enemies[i].GetComponent<EnemyHealth>();
if (enemyHealth != null && enemyHealth.currentHealth < 20)
{
Debug.Log("Found weak enemy!");
// Attack this enemy
break; // Exit loop early
}
}
Game Example: Inventory System
Here's a complete example combining multiple control structures:
using UnityEngine;
using System.Collections.Generic;
public class Inventory : MonoBehaviour
{
public List<Item> items = new List<Item>();
public int maxInventorySize = 20;
public bool AddItem(Item newItem)
{
// Check if inventory is full
if (items.Count >= maxInventorySize)
{
Debug.Log("Inventory is full!");
return false;
}
// Check if item already exists (stackable items)
foreach (Item item in items)
{
if (item.itemName == newItem.itemName && item.isStackable)
{
item.quantity += newItem.quantity;
Debug.Log($"Added {newItem.quantity} {newItem.itemName}");
return true;
}
}
// Add new item
items.Add(newItem);
Debug.Log($"Added {newItem.itemName} to inventory");
return true;
}
public void UseItem(string itemName)
{
foreach (Item item in items)
{
if (item.itemName == itemName)
{
if (item.quantity > 0)
{
item.quantity--;
Debug.Log($"Used {itemName}");
// Remove item if quantity is 0
if (item.quantity <= 0)
{
items.Remove(item);
}
}
return;
}
}
Debug.Log($"Item {itemName} not found in inventory");
}
public void DisplayInventory()
{
Debug.Log("=== INVENTORY ===");
if (items.Count == 0)
{
Debug.Log("Inventory is empty");
}
else
{
for (int i = 0; i < items.Count; i++)
{
Item item = items[i];
if (item.isStackable)
{
Debug.Log($"{i + 1}. {item.itemName} x{item.quantity}");
}
else
{
Debug.Log($"{i + 1}. {item.itemName}");
}
}
}
}
}
[System.Serializable]
public class Item
{
public string itemName;
public int quantity = 1;
public bool isStackable = false;
}
Control Flow Best Practices
Follow these best practices to write clean, efficient control flow code.
1. Use Meaningful Conditions
// BAD - Unclear what this checks
if (x) { }
// GOOD - Clear and descriptive
if (playerHealth > 0) { }
if (isGamePaused) { }
if (enemyCount > 0) { }
2. Avoid Deep Nesting
// BAD - Too nested, hard to read
if (condition1)
{
if (condition2)
{
if (condition3)
{
// Code
}
}
}
// GOOD - Use early returns or combine conditions
if (!condition1) return;
if (!condition2) return;
if (!condition3) return;
// Code
3. Use Switch for Multiple Values
// BAD - Long if-else chain
if (weaponType == "sword") { }
else if (weaponType == "bow") { }
else if (weaponType == "staff") { }
// ... many more
// GOOD - Clean switch statement
switch (weaponType)
{
case "sword": break;
case "bow": break;
case "staff": break;
}
4. Avoid Magic Numbers
// BAD - What does 10 mean?
if (playerHealth < 10) { }
// GOOD - Use named constants
const int LOW_HEALTH_THRESHOLD = 10;
if (playerHealth < LOW_HEALTH_THRESHOLD) { }
5. Break Out of Loops Early
// GOOD - Exit loop when condition is met
for (int i = 0; i < enemies.Length; i++)
{
if (enemies[i].IsDead())
{
AttackEnemy(enemies[i]);
break; // Found target, exit loop
}
}
Common Mistakes and How to Avoid Them
Mistake 1: Forgetting Break in Switch
// BAD - Falls through to next case
switch (state)
{
case 0:
DoSomething();
// Missing break!
case 1:
DoSomethingElse();
break;
}
// GOOD - Always include break
switch (state)
{
case 0:
DoSomething();
break;
case 1:
DoSomethingElse();
break;
}
Mistake 2: Infinite Loops
// BAD - Condition never changes
int count = 0;
while (count < 10)
{
Debug.Log(count);
// Forgot to increment count!
}
// GOOD - Always update loop condition
int count = 0;
while (count < 10)
{
Debug.Log(count);
count++; // Update condition
}
Mistake 3: Off-by-One Errors
// BAD - Goes one too far
for (int i = 0; i <= array.Length; i++) // Should be <
{
Debug.Log(array[i]); // Will crash on last iteration
}
// GOOD - Correct bounds
for (int i = 0; i < array.Length; i++)
{
Debug.Log(array[i]);
}
Mistake 4: Modifying Collection During Iteration
// BAD - Modifies collection while iterating
foreach (GameObject enemy in enemies)
{
if (enemy.IsDead())
{
enemies.Remove(enemy); // ERROR!
}
}
// GOOD - Iterate backwards or create new list
for (int i = enemies.Count - 1; i >= 0; i--)
{
if (enemies[i].IsDead())
{
enemies.RemoveAt(i); // Safe!
}
}
Practice Exercises
Try these exercises to reinforce your understanding:
Exercise 1: Health Check System
Create a system that checks player health and displays appropriate messages:
- Full health (100%): "Perfect condition!"
- Healthy (50-99%): "Feeling good"
- Wounded (25-49%): "Take care"
- Critical (1-24%): "Danger!"
- Dead (0%): "Game Over"
Exercise 2: Score Multiplier
Create a score system that applies multipliers based on combo count:
- Combo 0-4: 1x multiplier
- Combo 5-9: 2x multiplier
- Combo 10-19: 3x multiplier
- Combo 20+: 5x multiplier
Exercise 3: Enemy Spawner
Create a system that spawns enemies in a grid pattern:
- Use nested for loops
- Spawn 5x5 grid of enemies
- Each enemy should have a unique position
Exercise 4: Item Finder
Create a function that searches through an inventory:
- Use foreach to iterate items
- Find items with quantity less than 5
- Return list of items that need restocking
Troubleshooting
Issue: Loop Runs Forever
Problem: Your while loop never exits.
Solution: Ensure you update the condition variable inside the loop.
// Make sure to update the condition
int count = 0;
while (count < 10)
{
count++; // Don't forget this!
}
Issue: Switch Statement Executes Multiple Cases
Problem: Multiple cases execute when only one should.
Solution: Add break; after each case.
switch (value)
{
case 1:
DoSomething();
break; // Don't forget break!
case 2:
DoSomethingElse();
break;
}
Issue: Array Index Out of Bounds
Problem: Trying to access array element that doesn't exist.
Solution: Always check bounds before accessing.
// Check bounds first
if (index >= 0 && index < array.Length)
{
Debug.Log(array[index]);
}
Summary
Control flow is essential for creating interactive, dynamic games. You've learned:
- If statements - Make decisions based on conditions
- Switch statements - Handle multiple conditions elegantly
- For loops - Repeat code a specific number of times
- While loops - Repeat code while condition is true
- Foreach loops - Iterate through collections easily
- Nested structures - Combine control flow for complex logic
Key Takeaways:
- Use if statements for simple decisions
- Use switch for multiple related values
- Use for loops when you know the iteration count
- Use while loops when iteration count is unknown
- Use foreach for iterating collections
- Always update loop conditions to avoid infinite loops
- Break out of loops early when possible
Next Steps: Now that you understand control flow, you're ready to learn about functions and methods, which will help you organize your code and make it reusable. Functions are the building blocks of clean, maintainable game code.
Next Chapter
Ready to organize your code better? In the next chapter, you'll learn about Functions and Methods - how to create reusable code blocks that make your game development more efficient and your code easier to maintain.
Continue to Chapter 3: Functions and Methods →
Additional Resources
- Microsoft C# Documentation - Control Flow - Official C# control flow reference
- Unity Learn - C# Scripting - Unity-specific C# tutorials
- C# Language Reference - Complete C# language documentation
Related Guides:
- Unity Game Development - Apply C# in Unity
- Game Programming Fundamentals - Core programming concepts
Practice:
- Try modifying the examples in this chapter
- Create your own game systems using control flow
- Experiment with different loop types
- Build a simple game mechanic using if statements and loops
Keep practicing, and soon control flow will become second nature!