6.1 - Arrays
Arrays are one of the most fundamental data structures in programming. They allow you to store multiple values of the same type in a single variable, making it easier to manage collections of related data.
What Are Arrays?
An array is a fixed-size, ordered collection of elements of the same type. You can think of an array as a row of boxes, each containing a value, with each box numbered starting from zero.
Arrays are essential in game development for storing and managing:
- Player inventory items
- Enemy positions
- Waypoints for character movement
- Tile data for game boards
- Animation frames
- High scores
- And much more
Declaring and Initializing Arrays
Basic Array Declaration
To declare an array in C#, you specify the type of elements it will contain, followed by square brackets and a variable name:
// Declare an array of integers
int[] scores;
// Declare an array of strings
string[] playerNames;
// Declare an array of custom types
Enemy[] enemies;
Initializing Arrays
There are several ways to initialize an array:
1. Using the new
keyword with a size
// Create an array of 5 integers (all initialized to 0)
int[] scores = new int[5];
// Create an array of 10 strings (all initialized to null)
string[] playerNames = new string[10];
2. Using an array initializer
// Create and initialize an array of integers
int[] scores = new int[] { 100, 85, 92, 75, 96 };
// Shorthand syntax
int[] scores = { 100, 85, 92, 75, 96 };
// Create and initialize an array of strings
string[] weaponTypes = { "Sword", "Bow", "Axe", "Staff", "Dagger" };
3. Declaring first, initializing later
// Declare the array
float[] enemySpeeds;
// Initialize it later
enemySpeeds = new float[3];
enemySpeeds[0] = 2.5f;
enemySpeeds[1] = 3.0f;
enemySpeeds[2] = 1.8f;
Accessing Array Elements
Array elements are accessed using zero-based indexing, meaning the first element is at index 0, the second at index 1, and so on.
int[] scores = { 100, 85, 92, 75, 96 };
// Access individual elements
int firstScore = scores[0]; // 100
int thirdScore = scores[2]; // 92
// Modify an element
scores[1] = 88; // Changes 85 to 88
Attempting to access an index outside the array's bounds will throw an IndexOutOfRangeException
. Always ensure your index is valid before accessing an array element.
int[] scores = { 100, 85, 92 };
// This will throw an IndexOutOfRangeException
int invalidScore = scores[3]; // Error! The array only has indices 0, 1, and 2
Array Properties and Methods
Arrays in C# come with useful properties and methods:
The Length
Property
The Length
property returns the total number of elements in the array:
int[] scores = { 100, 85, 92, 75, 96 };
int numberOfScores = scores.Length; // 5
This is particularly useful when iterating through arrays:
// Print all scores
for (int i = 0; i < scores.Length; i++)
{
Console.WriteLine($"Score {i+1}: {scores[i]}");
}
Common Array Methods
The System.Array
class provides several useful methods for working with arrays:
int[] scores = { 75, 92, 85, 100, 96 };
// Sort the array in ascending order
Array.Sort(scores); // scores becomes { 75, 85, 92, 96, 100 }
// Reverse the array
Array.Reverse(scores); // scores becomes { 100, 96, 92, 85, 75 }
// Find the index of a value
int index = Array.IndexOf(scores, 92); // 2
// Fill the array with a specific value
Array.Fill(scores, 0); // scores becomes { 0, 0, 0, 0, 0 }
// Copy one array to another
int[] scoresCopy = new int[scores.Length];
Array.Copy(scores, scoresCopy, scores.Length);
Iterating Through Arrays
There are two main ways to iterate through arrays in C#:
Using a for
Loop
string[] weapons = { "Sword", "Bow", "Axe", "Staff", "Dagger" };
for (int i = 0; i < weapons.Length; i++)
{
Console.WriteLine($"Weapon {i+1}: {weapons[i]}");
}
The for
loop gives you access to the index, which can be useful in many scenarios.
Using a foreach
Loop
string[] weapons = { "Sword", "Bow", "Axe", "Staff", "Dagger" };
foreach (string weapon in weapons)
{
Console.WriteLine($"Available weapon: {weapon}");
}
The foreach
loop is more concise and readable when you only need the values and not their indices.
Multi-Dimensional Arrays
C# supports multi-dimensional arrays, which are arrays of arrays. These are useful for representing grid-based data like game boards, tile maps, or matrices.
Two-Dimensional Arrays
A two-dimensional array can be visualized as a table with rows and columns:
// Declare a 3x4 grid (3 rows, 4 columns)
int[,] grid = new int[3, 4];
// Initialize with values
int[,] grid = new int[,]
{
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8 },
{ 9, 10, 11, 12 }
};
// Access elements using two indices: [row, column]
int value = grid[1, 2]; // 7 (row 1, column 2)
// Set a value
grid[0, 3] = 42; // Sets the value at row 0, column 3 to 42
Iterating Through Multi-Dimensional Arrays
To iterate through a multi-dimensional array, you need nested loops:
int[,] grid = new int[,]
{
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8 },
{ 9, 10, 11, 12 }
};
// Get the dimensions
int rows = grid.GetLength(0); // 3
int columns = grid.GetLength(1); // 4
// Iterate through all elements
for (int row = 0; row < rows; row++)
{
for (int col = 0; col < columns; col++)
{
Console.WriteLine($"Value at [{row},{col}]: {grid[row, col]}");
}
}
Game Development Example: Tile Map
// 0 = empty, 1 = wall, 2 = player, 3 = goal
int[,] tileMap = new int[,]
{
{ 1, 1, 1, 1, 1, 1 },
{ 1, 0, 0, 0, 0, 1 },
{ 1, 0, 2, 0, 0, 1 },
{ 1, 0, 0, 0, 3, 1 },
{ 1, 1, 1, 1, 1, 1 }
};
// Render the tile map
for (int y = 0; y < tileMap.GetLength(0); y++)
{
for (int x = 0; x < tileMap.GetLength(1); x++)
{
switch (tileMap[y, x])
{
case 0:
Console.Write(" "); // Empty space
break;
case 1:
Console.Write("#"); // Wall
break;
case 2:
Console.Write("P"); // Player
break;
case 3:
Console.Write("G"); // Goal
break;
}
}
Console.WriteLine(); // New line after each row
}
Jagged Arrays
A jagged array is an array of arrays, where each sub-array can have a different length. This is useful when you need irregular data structures.
// Declare a jagged array with 3 rows
int[][] jaggedArray = new int[3][];
// Initialize each row with different lengths
jaggedArray[0] = new int[4] { 1, 2, 3, 4 };
jaggedArray[1] = new int[2] { 5, 6 };
jaggedArray[2] = new int[5] { 7, 8, 9, 10, 11 };
// Access elements
int value = jaggedArray[0][2]; // 3 (first row, third element)
// Iterate through a jagged array
for (int row = 0; row < jaggedArray.Length; row++)
{
for (int col = 0; col < jaggedArray[row].Length; col++)
{
Console.WriteLine($"Value at [{row}][{col}]: {jaggedArray[row][col]}");
}
}
Game Development Example: Level Waypoints
// A jagged array to store waypoints for different enemy paths
Vector2[][] enemyPaths = new Vector2[3][];
// Path 1: Patrol between 4 points
enemyPaths[0] = new Vector2[4]
{
new Vector2(0, 0),
new Vector2(10, 0),
new Vector2(10, 10),
new Vector2(0, 10)
};
// Path 2: Simple back-and-forth between 2 points
enemyPaths[1] = new Vector2[2]
{
new Vector2(5, 5),
new Vector2(15, 5)
};
// Path 3: Complex patrol route with 6 points
enemyPaths[2] = new Vector2[6]
{
new Vector2(2, 2),
new Vector2(8, 4),
new Vector2(12, 8),
new Vector2(10, 12),
new Vector2(6, 10),
new Vector2(4, 6)
};
// Assign paths to enemies
for (int i = 0; i < enemies.Length; i++)
{
int pathIndex = i % enemyPaths.Length;
enemies[i].SetWaypoints(enemyPaths[pathIndex]);
}
Arrays in Unity
Arrays are extensively used in Unity for various purposes:
// Common array usage in Unity scripts
public class EnemySpawner : MonoBehaviour
{
// Inspector-visible array of spawn points
public Transform[] spawnPoints;
// Array of enemy prefabs to spawn
public GameObject[] enemyPrefabs;
// Spawn rates for each enemy type
public float[] spawnRates;
void Start()
{
// Validate arrays
if (spawnPoints.Length == 0)
{
Debug.LogError("No spawn points assigned!");
return;
}
if (enemyPrefabs.Length == 0)
{
Debug.LogError("No enemy prefabs assigned!");
return;
}
// Ensure spawn rates match enemy prefabs
if (spawnRates.Length != enemyPrefabs.Length)
{
Debug.LogWarning("Spawn rates array doesn't match enemy prefabs array length!");
// Create a default array with equal rates
spawnRates = new float[enemyPrefabs.Length];
for (int i = 0; i < spawnRates.Length; i++)
{
spawnRates[i] = 1.0f / enemyPrefabs.Length;
}
}
// Start spawning enemies
StartCoroutine(SpawnEnemies());
}
IEnumerator SpawnEnemies()
{
while (true)
{
// Choose a random spawn point
int spawnIndex = Random.Range(0, spawnPoints.Length);
Transform spawnPoint = spawnPoints[spawnIndex];
// Choose an enemy type based on spawn rates
float randomValue = Random.value;
float cumulativeChance = 0f;
int enemyIndex = 0;
for (int i = 0; i < spawnRates.Length; i++)
{
cumulativeChance += spawnRates[i];
if (randomValue <= cumulativeChance)
{
enemyIndex = i;
break;
}
}
// Spawn the enemy
Instantiate(enemyPrefabs[enemyIndex], spawnPoint.position, spawnPoint.rotation);
// Wait before spawning the next enemy
yield return new WaitForSeconds(Random.Range(1f, 5f));
}
}
}
In Unity, arrays that are public or marked with [SerializeField]
will appear in the Inspector, allowing you to assign values directly in the editor. This is extremely useful for configuring game objects, prefabs, spawn points, and other game elements without changing code.
Array Limitations
While arrays are powerful, they have some limitations:
- Fixed Size: Once created, an array's size cannot be changed. If you need to add or remove elements, you must create a new array.
- Same Type: All elements in an array must be of the same type.
- Performance Considerations: Large arrays can be memory-intensive, and operations like insertion or deletion in the middle of an array require shifting elements.
For more flexible collections that can grow or shrink dynamically, you'll want to use the collections from the System.Collections.Generic
namespace, which we'll explore in the next sections.
Practical Example: Inventory System with Arrays
Let's implement a simple inventory system using arrays:
public class InventorySystem
{
// Array to store item IDs
private int[] itemIds;
// Array to store item quantities
private int[] itemQuantities;
// Current number of unique items in inventory
private int itemCount;
// Maximum inventory capacity
private readonly int maxCapacity;
public InventorySystem(int capacity)
{
maxCapacity = capacity;
itemIds = new int[capacity];
itemQuantities = new int[capacity];
itemCount = 0;
}
// Add an item to the inventory
public bool AddItem(int itemId, int quantity = 1)
{
// Check if the item already exists in inventory
for (int i = 0; i < itemCount; i++)
{
if (itemIds[i] == itemId)
{
// Item exists, increase quantity
itemQuantities[i] += quantity;
Console.WriteLine($"Added {quantity} of item {itemId}. New quantity: {itemQuantities[i]}");
return true;
}
}
// Item doesn't exist, add it if there's space
if (itemCount < maxCapacity)
{
itemIds[itemCount] = itemId;
itemQuantities[itemCount] = quantity;
itemCount++;
Console.WriteLine($"Added new item {itemId} with quantity {quantity}");
return true;
}
// Inventory is full
Console.WriteLine("Inventory is full! Cannot add item.");
return false;
}
// Remove an item from the inventory
public bool RemoveItem(int itemId, int quantity = 1)
{
for (int i = 0; i < itemCount; i++)
{
if (itemIds[i] == itemId)
{
// Found the item
if (itemQuantities[i] > quantity)
{
// Reduce quantity
itemQuantities[i] -= quantity;
Console.WriteLine($"Removed {quantity} of item {itemId}. Remaining: {itemQuantities[i]}");
return true;
}
else if (itemQuantities[i] == quantity)
{
// Remove the item completely by shifting all items after it
for (int j = i; j < itemCount - 1; j++)
{
itemIds[j] = itemIds[j + 1];
itemQuantities[j] = itemQuantities[j + 1];
}
itemCount--;
Console.WriteLine($"Removed item {itemId} completely");
return true;
}
else
{
// Not enough quantity
Console.WriteLine($"Not enough of item {itemId}. Have {itemQuantities[i]}, tried to remove {quantity}");
return false;
}
}
}
// Item not found
Console.WriteLine($"Item {itemId} not found in inventory");
return false;
}
// Display the inventory contents
public void DisplayInventory()
{
if (itemCount == 0)
{
Console.WriteLine("Inventory is empty");
return;
}
Console.WriteLine("Inventory Contents:");
for (int i = 0; i < itemCount; i++)
{
Console.WriteLine($"Item ID: {itemIds[i]}, Quantity: {itemQuantities[i]}");
}
}
}
// Usage example
public class Program
{
public static void Main()
{
InventorySystem inventory = new InventorySystem(10);
// Add some items
inventory.AddItem(101, 5); // Healing Potion
inventory.AddItem(202, 1); // Magic Sword
inventory.AddItem(303, 10); // Arrows
// Display inventory
inventory.DisplayInventory();
// Use some items
inventory.RemoveItem(101, 2); // Use 2 healing potions
inventory.RemoveItem(303, 5); // Use 5 arrows
// Display updated inventory
inventory.DisplayInventory();
}
}
While this array-based inventory system works, it has limitations:
- Adding/removing items in the middle requires shifting elements
- Fixed capacity
- No built-in sorting or searching capabilities
In the next sections, we'll see how collections like List<T>
and Dictionary<TKey, TValue>
can provide more flexible and powerful alternatives to arrays.
Conclusion
Arrays are a fundamental data structure in C# and are essential for many game development tasks. They provide an efficient way to store and access collections of related data.
Key points to remember:
- Arrays have a fixed size once created
- Array indices start at 0
- Use multi-dimensional arrays for grid-based data
- Use jagged arrays for collections of varying sizes
- Arrays in Unity can be exposed in the Inspector
In the next section, we'll explore the more flexible generic collections in the System.Collections.Generic
namespace, which build upon the foundation of arrays to provide more dynamic data structures.