유니티에서 다른 부분은 어느정도 이해하고 넘어갈 수 있다. 대부분의 함수가 직접 실행해보면서 확인할 수 있고 그러다보면 어느샌가 이해가 되기 마련이다. 하지만 필자는 델리게이트, 람다식에서 막히기 시작했다. 이들은 특이하게도 함수를 직접 사용하고 솔직히 없어도 큰 불편함을 느끼지 못할 때가 생긴다. 하지만 있으면 굉장히 편리하다. 오늘 막 이해했기에 이 글을 읽는 이들이 한번에 이해가 쉽게끔 설명하고자 노력하겠다.
1. 람다식이란?
유래라던가 필자는 자세히 모른다. 그렇기에 코드의 구조와 내용만을 설명하겠다.
(int x, int y) => x + y
private int gold;
int Gold { get => gold; } {set => gold = value;}
아마 람다식을 공부하다가 검색을 하는 중이신 분들에게는 저런 내용을 굉장히 많이 보셨을 것이다. 그럼 =>는 무슨 뜻을 가지는가? 그 내용은 아래에 예제를 보면 확인할 수 있다. 익명 메서드라고 불리는 람다식은 그냥 어렵게 생각하지 않고 그냥 이름이 없으니 또 사용할 수 없고 본래 메서드 처럼 값을 받아 =>는 return으로 번역해서 보면 된다. 이렇게까지는 다들 이해했을 것이다. 그럼?
int Add(int x, int y) { return x + y; }
private int gold;
int Gold {get return gold; } {set return (gold = value);}
다음 예제는 내가 람다식이 헷갈린 이유이자 이것이 해결되면 델리게이트에 대한 이해도 쉬워질 것이다.
inventory.SetStatChangedCallback((ItemAtk, ItemHp, ItemDef) =>
{
MaxHp += ItemHp;
MaxAtk += ItemAtk;
MaxDef += ItemDef;
});
강의에서 나왔던 한 람다식에 대한 이야기다. 이 때문에 =>이 return 이라는 것을 아는데 시간이 걸렸고 이걸 서술하는 사람도 몇 없다. (내가 못 찾았던 건가..?) 저기서 => 이후에 나오는 값 대입이 보이는데 자세히 보면 람다식의 매개변수와 함께 뒷 내용이 묶여있는 것을 볼 수 있다. 그럼 좀 더 보기 쉽게 내용을 고쳐보겠다.
inventory.SetStatChangedCallback((ItemAtk, ItemHp, ItemDef)
return
{
MaxHp += ItemHp;
MaxAtk += ItemAtk;
MaxDef += ItemDef;
});
이렇게 보면 이해가 되는 사람도 있을 것으로 보인다. 그냥 아까와 똑같다. 람다식으로 준 값을 안에다가 넣는다는 식이다. return에 들어간 내용은 메서드와 굉장히 유사하니 그냥 저 문구는 하나의 메서드에 들어가는 내용을 람다로 구현했다고 생각하면 편하다. 짧은 내용의 메서드의 경우 람다식을 사용하면 굉장히 직관적인 느낌이 들어 사용하기 좋고 => 이 return보다는 짧으니 적응만 하면 더 쓰기 좋고 가독성 좋은 코드를 만드는데 도움이 될 것이다.
그리고 델리게이트의 이야기가 나왔으니 추가 설명을 이어가보겠다.
Action<float, float, float> statChangedCallback;
public void SetStatChangedCallback(Action<float, float, float> callback)
{
statChangedCallback = callback; // 이게 델리게이트를 저장하는 부분
}
/// 위 아래는 서로 다른 클래스
inventory.SetStatChangedCallback((ItemAtk, ItemHp, ItemDef) =>
{
MaxHp += ItemHp;
MaxAtk += ItemAtk;
MaxDef += ItemDef;
});
아까 예문을 긁어온 것이다. 델리게이트의 경우 같은 클래스가 아닌 타 클래스에서도 주고 받을 수 있다. 물론 inventory.SetStatChangedCallback에서 눈치채신 분들도 계시겠지만 결국 선언해야 값을 넣어준다는 사실은 똑같다. 다른 점이 있다면 메서드를 저장하여 활용할 수 있다는 것이다. 값을 한 번 제대로 지정해놓으면 필요한 메서드를 델리게이트를 통해 여러번 구현하기가 편하다는 것이다. 델리게이트도 예문을 바꾸면 다중 구독이 가능하다.
Action<float, float, float> statChangedCallback;
public void SetStatChangedCallback(Action<float, float, float> callback)
{
statChangedCallback += callback; // 이게 델리게이트를 저장하는 부분
}
//다른 내용
public class Inventory
{
// 이벤트 선언: EventHandler<T>는 표준 델리게이트 타입
public event EventHandler<StatChangeEventArgs> OnStatChanged;
}
inventory.OnStatChanged += HandleStatChange;
델리게이트를 사용한 두 예문을 비교하면 하나는 =을 하나는 +=을 사용하였다. 정말 간단하게 내용을 바꿀 수 있는 대신에 Delegate 설계 철학이 무너지기에 가능하면 event를 사용하는 편이 좋다는 것이다. 이러한 다중구독을 활용하면 Item을 장착했을 시, 플레이어에게 스텟올려주기, 장착여부 판단하기 등의 소요 값을 하나의 event에 저장하여 다룰 수 있다는 것이다. 사실 한번씩 다 적을 거라면 굳이 event에 줘야하는가? 당연히 의문이 들만하다. 허나 이점이라고 한다면 event의 값은 코드 중간에 넣는 것이 아닌 초기에 한 번 넣어주고 event를 코드 중간에 넣는다면 한눈에 기능을 확인할 수 있다. 각 클래스의 값을 하나씩 불러와서 다른 메서드에 값을 매번 불러와서 사용하느니 초기에 event 함수를 가져와 그곳에 다 넣는 것이 훨씬 편할 것이다.
항목 | delegate 필드 (Action 등) | event |
외부에서 = 가능? | ✅ 가능 (callback = null 등) | ❌ 불가능 |
외부에서 Invoke() 가능? | ✅ 가능 | ❌ 불가능 |
다중 구독 (+=) 가능? | ❌ 기본적으로 안됨 (직접 구현해야 함) | ✅ 자동 지원 |
외부에서 전체 덮어쓰기 위험 | ✅ 있음 | ❌ 없음 |
안전성 / 캡슐화 | 낮음 | 높음 |
설계 철학 | 유연한 코드 중심 | 안정성 중심의 이벤트 시스템 |
'유니티 스파르타 캠프 4주차' 카테고리의 다른 글
내배캠 개인프로젝트 트러블슈팅 내용 (0) | 2025.05.07 |
---|---|
25-05-02 Vector.Lerp와 깃허브 커밋 (0) | 2025.05.02 |
2025-05-01 유니티 Collision과 Trigger의 관계 (0) | 2025.05.01 |
25-04-29 유니티 애니메이터 속도와 깜빡깜빡 문제 (1) | 2025.04.29 |
25-04-28 팀 프로젝트 발표회 및 튜터님 피드백 (0) | 2025.04.28 |