안녕하세요! 유니티 개발 시 수많은 데이터를 다루다 보면 "이 많은 걸 언제 다 정렬하고, 필터링하지?" 막막할 때가 있으셨죠? 최근 진행된 유니티 스탠다드반 특강에서는 바로 이런 고민을 해결해 줄 강력한 도구, LINQ(Language Integrated Query)에 대해 배우는 시간을 가졌습니다. 이 글에서 그 핵심 내용들을 다시 한번 되짚어 볼게요!
LINQ는 C# 코드 내에서 마치 데이터베이스에 질의(Query)하듯 다양한 데이터 소스(리스트, 배열, XML, DB 등)의 데이터를 통합된 방식으로 다룰 수 있게 해주는 기능입니다. 특히 게임 개발에서는 수많은 아이템, 캐릭터, 몬스터 등의 정보를 조건에 맞게 검색하거나 정렬하는 데 매우 유용하게 사용됩니다.
List.Sort()
는 원본 자체를 변경)using System.Linq;
LINQ의 다양한 확장 메서드들을 사용하기 위해서는 스크립트 상단에 다음 네임스페이스를 추가해야 합니다.
using System.Linq;
Ⅲ. LINQ 작성 스타일: 메서드 구문 vs. 쿼리 구문
LINQ를 작성하는 방식에는 크게 두 가지가 있습니다.
메서드 구문 (Method Syntax): 람다식(Lambda Expression)을 활용하여 체인 형태로 메서드를 호출하는 방식입니다. (예: 데이터컬렉션.Where(조건).Select(항목).ToList();) 이번 강의에서는 주로 이 방식을 다뤘습니다.
쿼리 구문 (Query Syntax): SQL과 유사한 형태로 작성하는 방식입니다. (예: from 항목 in 데이터컬렉션 where 조건 select 항목;) SQL에 익숙한 분들에게는 더 직관적일 수 있습니다.
(어떤 방식을 사용하든 기능은 동일하며, 가독성이나 개인 선호에 따라 선택할 수 있습니다. 이번 강의에서는 메서드 구문에 집중했습니다.)
Ⅳ. 핵심 LINQ 메서드 파헤치기 🛠️
강의에서는 가상의 Weapon 클래스 리스트를 예제로 사용하여 다양한 LINQ 메서드들을 시연했습니다. Weapon 클래스는 Name(string), Type(enum), Grade(enum), AttackPower(int), RequiredLevel(int), Price(int) 등의 속성을 가지고 있다고 가정합니다.
// 예시 Weapon 클래스 및 데이터 (강의 내용 기반)
public enum WeaponType { Sword, Bow, Staff }
public enum WeaponGrade { Common, Rare, Unique, Legendary }
public class Weapon
{
public string Name { get; set; }
public WeaponType Type { get; set; }
public WeaponGrade Grade { get; set; }
public int AttackPower { get; set; }
public int RequiredLevel { get; set; }
public int Price { get; set; }
public override string ToString() // 디버그 출력을 위함
{
return $"이름: {Name}, 종류: {Type}, 등급: {Grade}, 공격력: {AttackPower}, 요구레벨: {RequiredLevel}, 가격: {Price}";
}
}
// 사용 예시 데이터 리스트 (실제 강의에서는 WeaponData.DB에 미리 정의된 static 리스트 사용)
List<Weapon> weaponDatabase = new List<Weapon>()
{
new Weapon { Name = "불의 검", Type = WeaponType.Sword, Grade = WeaponGrade.Rare, AttackPower = 15, RequiredLevel = 5, Price = 3000 },
new Weapon { Name = "얼음의 활", Type = WeaponType.Bow, Grade = WeaponGrade.Unique, AttackPower = 25, RequiredLevel = 10, Price = 5000 },
new Weapon { Name = "드래곤 스태프", Type = WeaponType.Staff, Grade = WeaponGrade.Legendary, AttackPower = 40, RequiredLevel = 20, Price = 10000 },
new Weapon { Name = "낡은 단검", Type = WeaponType.Sword, Grade = WeaponGrade.Common, AttackPower = 5, RequiredLevel = 1, Price = 100 },
new Weapon { Name = "사냥꾼의 장궁", Type = WeaponType.Bow, Grade = WeaponGrade.Rare, AttackPower = 18, RequiredLevel = 7, Price = 3500 },
new Weapon { Name = "초보 마법사의 지팡이", Type = WeaponType.Staff, Grade = WeaponGrade.Common, AttackPower = 10, RequiredLevel = 3, Price = 1200 }
};
// 예: 가격이 3000 이상인 무기들만 가져오기
List<Weapon> expensiveWeapons = weaponDatabase
.Where(weapon => weapon.Price >= 3000)
.ToList(); // 결과를 새 리스트로 만듦
foreach (Weapon w in expensiveWeapons)
{
Debug.Log(w.ToString());
}
// 람다식 weapon => weapon.Price >= 3000 은
// weaponDatabase의 각 Weapon 객체를 weapon이라는 이름으로 받아,
// 그 weapon의 Price가 3000 이상이면 true를 반환하는 조건식입니다.
// 예: 가격이 낮은 순으로 무기 정렬
List<Weapon> sortedByPriceAsc = weaponDatabase
.OrderBy(weapon => weapon.Price)
.ToList();
// 예: 공격력이 높은 순으로 무기 정렬
List<Weapon> sortedByAttackDesc = weaponDatabase
.OrderByDescending(weapon => weapon.AttackPower)
.ToList();
// 예: 이름 가나다순(ABC순)으로 정렬 (문자열도 가능)
List<Weapon> sortedByName = weaponDatabase
.OrderBy(weapon => weapon.Name)
.ToList();
// 한글은 가나다 순, 영어는 ABC 순으로 정렬됩니다.
// 한글과 영어가 섞여있을 경우, 일반적으로 영어가 먼저 정렬된 후 한글이 정렬됩니다.
// (OrderByDescending을 사용하면 역순)
// 예: 전체 무기 개수
int totalCount = weaponDatabase.Count();
Debug.Log(<span class="math-inline">"전체 무기 개수\: \{totalCount\}"\);
// 예\: 가격이 1000 이상인 무기 개수
int expensiveCount \= weaponDatabase\.Count\(weapon \=\> weapon\.Price \>\= 1000\);
Debug\.Log\(</span>"가격 1000 이상 무기 개수: {expensiveCount}");
// 예: 모든 무기의 총 가격
int totalPrice = weaponDatabase.Sum(weapon => weapon.Price);
Debug.Log(<span class="math-inline">"모든 무기 총 가격\: \{totalPrice\}"\);
// 예\: 모든 무기의 평균 가격
double averagePrice \= weaponDatabase\.Average\(weapon \=\> weapon\.Price\);
Debug\.Log\(</span>"모든 무기 평균 가격: {averagePrice:F1}"); // F1은 소수점 첫째 자리까지 표시
IGrouping<TKey, TElement>의 컬렉션입니다.
// 예: 무기 등급(Grade)별로 그룹화
var groupedByGrade = weaponDatabase.GroupBy(weapon => weapon.Grade);
// var를 사용하면 복잡한 타입을 컴파일러가 추론해줍니다.
// 실제 타입은 IEnumerable<IGrouping<WeaponGrade, Weapon>> 입니다.
foreach (IGrouping<WeaponGrade, Weapon> group in groupedByGrade)
{
Debug.Log($"--- {group.Key} 등급 무기 ---"); // group.Key는 그룹화 기준 값 (여기서는 WeaponGrade)
foreach (Weapon weaponInGroup in group) // group 자체도 Weapon 컬렉션처럼 사용 가능
{
Debug.Log(weaponInGroup.Name);
}
}
// 예: 가격이 가장 비싼 상위 3개 무기 가져오기
List<Weapon> top3ExpensiveWeapons = weaponDatabase
.OrderByDescending(w => w.Price)
.Take(3)
.ToList();
Debug.Log("--- 가장 비싼 무기 Top 3 ---");
foreach (Weapon w in top3ExpensiveWeapons)
{
Debug.Log(w.ToString());
}
// 예: 가격이 가장 비싼 상위 3개를 제외한 나머지 무기 가져오기
List<Weapon> exceptTop3ExpensiveWeapons = weaponDatabase
.OrderByDescending(w => w.Price)
.Skip(3)
.ToList();
Debug.Log("--- Top 3 제외 나머지 비싼 순 무기 ---");
foreach (Weapon w in exceptTop3ExpensiveWeapons)
{
Debug.Log(w.ToString());
}
Ⅴ. LINQ 메서드 체이닝: 강력함의 비결 🔗
LINQ의 가장 큰 장점 중 하나는 여러 메서드를 점(.)으로 연결하여 복잡한 쿼리를 간결하게 작성할 수 있다는 것입니다 (메서드 체이닝).
// 예: 가격이 1000 이상이고, 등급이 Rare 이상인 무기들을, 공격력 높은 순으로 정렬하여, 이름만 가져오기
List<string> filteredAndSortedWeaponNames = weaponDatabase
.Where(w => w.Price >= 1000 && w.Grade >= WeaponGrade.Rare) // 필터링
.OrderByDescending(w => w.AttackPower) // 정렬
.Select(w => w.Name) // 이름만 선택
.ToList(); // 리스트로 변환
Debug.Log("--- 1000원 이상, Rare 등급 이상, 공격력 높은 순, 무기 이름 ---");
foreach (string name in filteredAndSortedWeaponNames)
{
Debug.Log(name);
}
Ⅵ. 실전 활용 예시: UI에 필터링된 아이템 목록 표시하기
강의 후반부에는 ItemUIManager 스크립트의 UpdateWeaponList(List weapons) 함수를 호출하여, LINQ로 필터링하고 정렬된 무기 목록을 실제 UI에 표시하는 예시를 보여주셨습니다.
// 링크 테스트 스크립트 내에서 UI 매니저와 연결 후 사용
public ItemUIManager uiManager; // 인스펙터에서 연결
void ShowFilteredItems()
{
if (uiManager == null) return;
// 예: 가격이 2000 이상인 무기들만, 가격 낮은 순으로 정렬해서 UI에 표시
List<Weapon> weaponsToShow = weaponDatabase
.Where(w => w.Price >= 2000)
.OrderBy(w => w.Price)
.ToList();
uiManager.UpdateWeaponList(weaponsToShow); // UI 매니저의 함수로 전달
}
// 유니티 에디터에서 버튼 클릭 등으로 ShowFilteredItems() 함수를 실행하면
// 조건에 맞는 아이템들만 UI에 정렬되어 나타나는 것을 확인할 수 있습니다.
Ⅶ. 마치며: LINQ는 여러분의 코드를 우아하게 만들어 줄 거예요! ✨
LINQ는 처음에는 람다식과 다양한 메서드들 때문에 조금 어렵게 느껴질 수 있지만, 한번 익숙해지면 정말 강력하고 편리한 도구입니다. 반복적인 데이터 처리 코드를 줄여주고, 코드의 가독성을 높여주며, 복잡한 요구사항도 손쉽게 구현할 수 있도록 도와줍니다.
강의에서 배운 내용들을 바탕으로 직접 다양한 조건으로 데이터를 조회하고 조작하는 연습을 충분히 해보세요. 특히 아이템이 수백, 수천 개가 되는 실제 게임에서는 LINQ의 진가를 더욱 크게 느끼실 수 있을 거예요!
(강의 자료에는 First(), FirstOrDefault(), Join() 등 더 많은 LINQ 기능들이 언급되었으니, 추가 학습을 통해 더 깊이 파고들어 보세요!)