1. 배열(Array)의 정의 및 특징
배열은 동일한 데이터 타입을 갖는 여러 개의 요소(Element)들을 고정된 크기의 연속적인 메모리 공간에 저장하는 데이터 구조입니다. C#에서 배열은 다음과 같은 주요 특징을 가집니다.
- 동일 타입 요소: 배열에 저장되는 모든 요소는 선언 시 지정된 단일 데이터 타입(예:
int
,float
,GameObject
,string
)이어야 합니다. - 고정 크기 (Fixed Size): 배열은 생성될 때 그 크기(저장할 수 있는 요소의 개수)가 결정되며, 일단 생성된 후에는 크기를 변경할 수 없습니다.
- 0 기반 인덱스 (Zero-based Indexing): 배열의 각 요소는 0부터 시작하는 고유한 정수 인덱스(Index)를 통해 접근합니다. 첫 번째 요소의 인덱스는 0이고, 마지막 요소의 인덱스는
배열 길이 - 1
입니다. - 참조 타입 (Reference Type): C#에서 배열은
System.Array
클래스로부터 파생된 참조 타입입니다. 즉, 배열 타입의 변수는 실제 데이터가 저장된 힙(Heap) 메모리상의 위치(주소)를 참조합니다. 배열 변수를 다른 변수에 할당하면 참조만 복사됩니다.
2. 1차원 배열 선언, 생성 및 초기화
1차원 배열은 가장 기본적인 형태의 배열로, 데이터를 선형적인 순서로 저장합니다.
[문법]
// 1. 선언 (Declaration)
데이터타입[] 배열이름;
// 2. 생성 (Instantiation) - 크기 지정
배열이름 = new 데이터타입[배열크기];
// 3. 선언과 동시에 생성
데이터타입[] 배열이름 = new 데이터타입[배열크기];
// 4. 생성과 동시에 초기화 (Initialization)
데이터타입[] 배열이름 = new 데이터타입[배열크기] { 값1, 값2, ..., 값N }; // 크기와 초기값 개수 일치 필요
// 5. 선언과 동시에 축약형 초기화 (크기 자동 추론)
데이터타입[] 배열이름 = { 값1, 값2, ..., 값N };
[예시 코드]
// 정수형 배열
int[] scores; // 선언
scores = new int[5]; // 크기가 5인 int 배열 생성 (요소들은 기본값 0으로 초기화됨)
// 실수형 배열 선언, 생성, 초기화
float[] positions = new float[3] { 1.0f, -0.5f, 2.3f };
// 문자열 배열 축약형 초기화
string[] playerNames = { "Alice", "Bob", "Charlie" }; // 크기는 3으로 자동 결정됨
// GameObject 배열 선언 및 생성 (Unity)
public GameObject[] enemyPrefabs = new GameObject[3]; // Inspector에서 3개의 프리팹을 할당받을 수 있음
3. 배열 요소 접근 및 수정
배열의 각 요소는 배열이름[인덱스]
형태의 인덱스 연산자([]
)를 사용하여 접근하거나 수정할 수 있습니다. 인덱스는 반드시 0 이상이고 배열 길이 - 1
이하여야 합니다. 유효하지 않은 인덱스를 사용하면 런타임에 IndexOutOfRangeException
예외가 발생합니다. 배열의 길이는 Length
프로퍼티를 통해 얻을 수 있습니다.
[예시 코드]
int[] scores = new int[5]; // { 0, 0, 0, 0, 0 }
// 요소 값 할당 (수정)
scores[0] = 95;
scores[1] = 88;
scores[4] = 72; // 마지막 요소 (인덱스 4)
// 요소 값 읽기 (접근)
int firstScore = scores[0]; // firstScore는 95
Debug.Log("두 번째 점수: " + scores[1]); // 출력: 두 번째 점수: 88
// 배열 길이 확인
int arrayLength = scores.Length; // arrayLength는 5
Debug.Log("배열의 크기: " + arrayLength);
// 유효하지 않은 인덱스 접근 시도 (예외 발생)
// int invalidScore = scores[5]; // IndexOutOfRangeException 발생
// scores[-1] = 100; // IndexOutOfRangeException 발생
4. 배열과 반복문 (for, foreach)
배열의 모든 요소를 순차적으로 처리하기 위해 반복문(Loop)이 필수적으로 사용됩니다.
for
루프: 인덱스를 사용하여 배열 요소를 순회할 때 유용합니다. 요소의 값을 수정하거나, 특정 위치의 요소에 접근해야 할 때 적합합니다.int[] numbers = { 10, 20, 30, 40, 50 }; for (int i = 0; i < numbers.Length; i++) // 인덱스 0부터 Length-1 까지 순회 { numbers[i] = numbers[i] * 2; // 각 요소 값을 2배로 수정 Debug.Log("Index " + i + ": " + numbers[i]); }
foreach
루프: 배열의 각 요소를 처음부터 끝까지 읽기 전용으로 간단하게 순회할 때 유용합니다. 인덱스가 필요 없을 때 코드가 더 간결해집니다.string[] fruits = { "Apple", "Banana", "Cherry" }; Debug.Log("과일 목록:"); foreach (string fruit in fruits) // fruits 배열의 각 요소를 fruit 변수에 담아 순회 { Debug.Log("- " + fruit); // foreach 루프 내에서 요소를 직접 수정하는 것은 권장되지 않음 (특히 참조 타입 요소의 내부 상태 변경은 가능하나, 요소 자체를 교체 불가) }
5. 다차원 배열 (Multidimensional Arrays)
다차원 배열은 행과 열(또는 그 이상의 차원)을 가지는 배열로, 테이블이나 그리드 형태의 데이터를 표현하는 데 유용합니다. 가장 흔한 형태는 2차원 배열입니다.
[문법 - 2차원 배열]
// 선언 및 생성
데이터타입[,] 배열이름 = new 데이터타입[행크기, 열크기];
// 초기화 예시
int[,] matrix = new int[2, 3] { { 1, 2, 3 }, { 4, 5, 6 } }; // 2행 3열
// 축약형 초기화
int[,] matrixB = { { 1, 2 }, { 3, 4 }, { 5, 6 } }; // 3행 2열
[요소 접근 및 순회]
// 요소 접근: 배열이름[행인덱스, 열인덱스]
int element = matrix[1, 2]; // 2행 3열 요소 접근 (값: 6)
// 순회 (Nested for loops 사용)
for (int i = 0; i < matrix.GetLength(0); i++) // GetLength(0): 행의 크기
{
for (int j = 0; j < matrix.GetLength(1); j++) // GetLength(1): 열의 크기
{
Debug.Log($"Matrix[{i},{j}] = {matrix[i, j]}");
}
}
[Unity 예시]
// 2차원 배열로 타일 맵 데이터 저장
public TileType[,] tileMap = new TileType[10, 10]; // 10x10 크기의 타일 맵 (TileType은 enum 또는 class/struct)
void InitializeMap()
{
for (int x = 0; x < tileMap.GetLength(0); x++)
{
for (int y = 0; y < tileMap.GetLength(1); y++)
{
// 특정 로직에 따라 타일 타입 설정
tileMap[x, y] = GetTileTypeForPosition(x, y);
}
}
}
6. 가변 배열 (Jagged Arrays)
가변 배열은 "배열들의 배열(Array of Arrays)"입니다. 각 내부 배열의 크기가 서로 다를 수 있다는 특징이 있습니다. 즉, 행마다 열의 개수가 가변적일 수 있습니다.
[문법]
// 선언 및 외부 배열 생성
데이터타입[][] 배열이름 = new 데이터타입[외부배열크기][];
// 내부 배열 각각 생성 및 초기화
배열이름[0] = new 데이터타입[내부배열크기0];
배열이름[1] = new 데이터타입[내부배열크기1];
// ...
// 초기화 예시
int[][] jaggedArray = new int[3][];
jaggedArray[0] = new int[] { 1, 2 }; // 첫 번째 행은 크기 2
jaggedArray[1] = new int[] { 3, 4, 5 }; // 두 번째 행은 크기 3
jaggedArray[2] = new int[] { 6 }; // 세 번째 행은 크기 1
[요소 접근 및 순회]
// 요소 접근: 배열이름[외부인덱스][내부인덱스]
int element = jaggedArray[1][2]; // 값: 5
// 순회 (Nested loops, 내부 루프는 각 내부 배열의 Length 사용)
for (int i = 0; i < jaggedArray.Length; i++) // 외부 배열 순회
{
for (int j = 0; j < jaggedArray[i].Length; j++) // 내부 배열 순회
{
Debug.Log($"Jagged[{i}][{j}] = {jaggedArray[i][j]}");
}
}
가변 배열은 각 행의 데이터 크기가 일정하지 않은 경우 메모리를 더 효율적으로 사용할 수 있습니다.
7. Unity에서의 배열 활용 사례
- 데이터 목록 관리: 게임 내 고정된 목록의 데이터를 저장하는 데 사용됩니다.
Vector3[] waypoints
: NPC나 카메라의 이동 경로 웨이포인트 목록.Transform[] spawnPoints
: 적이나 아이템이 생성될 위치 목록.Material[] characterSkins
: 캐릭터에 적용할 수 있는 스킨 재질 목록.AudioClip[] soundEffects
: 재생할 사운드 이펙트 목록.
- Inspector 노출:
public
배열이나[SerializeField]
어트리뷰트가 붙은private
배열은 Unity Inspector 창에 표시되어, 디자이너가 직접 게임 오브젝트, 프리팹, 값 등을 드래그 앤 드롭하거나 입력하여 배열을 채울 수 있습니다. 이는 개발자와 디자이너 간의 협업에 매우 유용합니다. - 버퍼: 특정 크기의 데이터를 임시 저장하는 버퍼로 사용됩니다. (예: 최근 입력 기록, 네트워크 패킷 데이터)
- 그리드 및 맵 데이터: 2D 배열은 턴제 게임의 보드, 타일 기반 맵, 인벤토리 슬롯 등 격자 구조 데이터를 표현하는 데 이상적입니다.
8. 배열의 한계 및 대안
배열은 효율적이고 기본적인 데이터 구조이지만, 명확한 한계점을 가지고 있습니다.
고정 크기: 가장 큰 제약 사항으로, 생성 후 크기를 동적으로 변경할 수 없습니다. 게임 실행 중 요소의 개수가 변동될 가능성이 있는 경우에는 배열이 적합하지 않습니다. 크기를 늘리려면 더 큰 새 배열을 만들고 기존 요소들을 복사해야 하는데, 이는 매우 비효율적인 작업입니다.
대안:
List<T>
: C# 제네릭 컬렉션인System.Collections.Generic.List<T>
는 배열의 고정 크기 한계를 극복하는 가장 일반적인 대안입니다.List<T>
는 동적으로 크기가 조절되며,Add()
,Remove()
,Insert()
등의 메서드를 통해 요소를 쉽게 추가, 삭제, 삽입할 수 있습니다. 내부적으로 배열을 사용하지만, 필요시 자동으로 배열 크기를 조정해줍니다. 게임 실행 중에 크기가 변하는 컬렉션에는List<T>
사용이 강력히 권장됩니다.using System.Collections.Generic; // 필요 List<GameObject> activeEnemies = new List<GameObject>(); // GameObject 타입의 리스트 생성 // 요소 추가 activeEnemies.Add(enemyInstance1); activeEnemies.Add(enemyInstance2); // 요소 삭제 activeEnemies.Remove(enemyToRemove); // 순회 (foreach 사용 용이) foreach (GameObject enemy in activeEnemies) { /* ... */ } // 개수 확인 int enemyCount = activeEnemies.Count; // List<T>는 Count 프로퍼티 사용
결론
배열은 C#에서 동일한 타입의 요소들을 고정된 크기로 관리하는 기본적인 데이터 구조입니다. 0 기반 인덱스를 통한 빠른 요소 접근이 가능하며, 1차원, 다차원, 가변 배열 등 다양한 형태로 활용될 수 있습니다. Unity 개발에서는 고정된 데이터 목록 관리, Inspector를 통한 데이터 설정, 그리드 데이터 표현 등 다양한 용도로 널리 사용됩니다. 하지만 생성 후 크기를 변경할 수 없는 명확한 한계 때문에, 요소의 개수가 동적으로 변하는 상황에서는 List<T>
와 같은 제네릭 컬렉션을 사용하는 것이 훨씬 효율적이고 유연합니다. 배열의 기본 개념과 사용법, 그리고 그 한계를 명확히 이해하는 것은 Unity에서 데이터를 효과적으로 구성하고 관리하기 위한 필수적인 기초 지식입니다.