버튼을 누르면 무엇을 할지는 버튼이 정하지 않습니다. 버튼은 "눌렸을 때 부를 함수" 를 받아 두었다가 부를 뿐입니다. 함수를 변수처럼 담아 넘기는 문법이 델리게이트입니다. 이번 편에서는 Unity 버튼의 onClick 에 함수를 붙이는 여섯 가지 방법을 차례로 봅니다.
2024년 부산 콘텐츠 아카데미 퍼스널 트레이닝 수업에서 MyLesson Unity 실습 프로젝트의 Strategy_05_Delegate.cs 로 가르친 내용입니다. 수업은 여섯 단계로 나뉘고, 단계마다 코드가 조금씩 바뀝니다.
버튼은 무엇을 할지 모른다
Unity 의 Button 은 게임마다 쓰입니다. 시작 버튼, 상점 버튼, 닫기 버튼. 버튼 코드 안에 "게임을 시작한다" 를 적어 둘 수는 없습니다. 그래서 버튼은 눌렸을 때 부를 함수 목록(onClick)만 갖고, 그 목록에 함수를 넣는 일은 버튼을 쓰는 쪽이 합니다.
- FindObject버튼 오브젝트 찾기
- GetComponent<Button>버튼 컴포넌트
- onClick.AddListener(함수)부를 함수 등록
- 클릭등록한 함수가 불림
버튼은 씬의 /Canvas/Game/Start/Button (Play) 경로에 있습니다. 경로로 오브젝트를 찾는 Program.FindObject 도우미가 이 수업에서 추가됐습니다.
이름 있는 함수를 넘기기
GameObject go = Program.FindObject("/Canvas/Game/Start/Button (Play)");
Button btn = go.GetComponent<Button>();
btn.onClick.AddListener(OnClickFunction);
private void OnClickFunction()
{
Program.WriteLine("Called OnClickFunction");
}AddListener(OnClickFunction) 에서 함수 이름 뒤에 괄호가 없습니다. 함수를 부른 것이 아니라 함수 자체를 넘긴 것입니다. AddListener 가 받는 타입은 UnityAction 이라는 델리게이트이고, "매개변수 없고 반환값 없는 함수" 를 담는 그릇입니다. OnClickFunction 은 그 모양과 맞으니 넘길 수 있습니다.
이름 없는 함수: 무명 메서드와 람다
btn.onClick.AddListener(delegate
{
Program.WriteLine("Called Delegate Function");
});
btn.onClick.AddListener(() =>
{
Program.WriteLine("Called Lambda Function");
});한 번만 쓸 함수에 이름을 짓는 것은 번거롭습니다. 그래서 그 자리에서 함수를 만들어 넘깁니다. delegate { … } 는 C# 2.0 의 무명 메서드, () => { … } 는 C# 3.0 의 람다 식입니다. 지금은 람다를 주로 씁니다.
Called Delegate Function
Called Lambda FunctionAddListener 를 두 번 했으니 onClick 목록에는 함수가 둘 들어 있고, 클릭 한 번에 둘 다 차례로 불립니다.
내 델리게이트 타입 선언하기
private delegate void OnClickSomething(Button instigator);
private OnClickSomething>
public void Run()
{
GameObject go = Program.FindObject("/Canvas/Game/Start/Button(Play)");
Button btn = go.GetComponent<Button>();
>
btn.onClick.AddListener(delegate
{
onClickSomethingFunction(btn);
});
}
private void OnClickFunctionStep3(Button instigator)
{
Program.WriteLine("Called OnClickFunctionStep3");
Program.WriteLine("Instigator's name is {0}", instigator.gameObject.name);
}맨 위의 delegate void OnClickSomething(Button instigator) 는 함수가 아니라 타입 선언입니다. "Button 하나를 받고 반환값이 없는 함수" 를 담는 그릇에 OnClickSomething 이라는 이름을 붙였습니다. 그 타입의 변수 onClickSomethingFunction 에 OnClickFunctionStep3 을 담고, 버튼이 눌리면 그 변수를 함수처럼 부릅니다.
| 코드 | 무엇인가 |
|---|---|
delegate void OnClickSomething(Button) | 타입 선언 (함수의 모양) |
OnClickSomething onClickSomethingFunction | 그 타입의 변수 |
onClickSomethingFunction = OnClickFunctionStep3 | 변수에 함수를 담음 |
onClickSomethingFunction(btn) | 담긴 함수를 부름 |
Called OnClickFunctionStep3
Instigator's name is Button (Play)눌린 버튼을 넘겨받으니, 같은 함수를 여러 버튼에 붙여도 어느 버튼이 눌렸는지 알 수 있습니다.
실행 중에 함수를 만들어 담기
onClickSomethingFunction = (instigator) =>
{
Program.WriteLine("Called runtime lambda");
Program.WriteLine("Instigator's name is {0}", instigator.gameObject.name);
};
OnClickSomething localDelegate = delegate(Button instigator)
{
Program.WriteLine("Called runtime(local) delegate");
Program.WriteLine("Instigator's name is {0}", instigator.gameObject.name);
};
btn.onClick.AddListener(delegate
{
onClickSomethingFunction(btn);
localDelegate(btn);
});델리게이트 변수에는 이름 있는 함수뿐 아니라 람다와 무명 메서드도 담을 수 있습니다. 필드(onClickSomethingFunction)에도, Run 안의 지역 변수(localDelegate)에도 담을 수 있습니다. 람다의 매개변수 (instigator) 에는 타입을 적지 않았는데, 담을 변수의 타입(OnClickSomething)을 보고 컴파일러가 Button 이라고 압니다.
직접 선언하지 않아도 되는 델리게이트
UnityAction<Button> unityActionWithParam = new UnityAction<Button>(
delegate (Button instigator)
{
Program.WriteLine("Called unityActionWithParam");
Program.WriteLine("Instigator's name is {0}", instigator.gameObject.name);
}
);
btn.onClick.AddListener(() => unityActionWithParam(btn));매번 델리게이트 타입을 선언하기는 번거롭습니다. Unity 는 UnityAction<T>, .NET 은 Action<T> 를 미리 만들어 두었습니다. Action<Button> 은 우리가 3단계에서 선언한 OnClickSomething 과 모양이 같습니다.
onClickSomthingAction = delegate(Button instigator)
//onClickSomthingAction = (instigator) =>
{
Program.WriteLine("Called Action<Button>");
Program.WriteLine("Instigator's name is {0}", instigator.gameObject.name);
};
btn.onClick.AddListener(() => onClickSomthingAction(btn));6단계 코드의 람다 () => onClickSomthingAction(btn) 이 두 델리게이트를 잇습니다. onClick 은 매개변수 없는 함수를 원하고, 우리 함수는 Button 을 원합니다. 그래서 매개변수 없는 람다 안에서 btn 을 넣어 부릅니다.
| 델리게이트 | 모양 | 어디에 |
|---|---|---|
Action | 매개변수 없음, 반환 없음 | .NET |
Action<T1, T2> | T1, T2 를 받고 반환 없음 | .NET |
Func<T1, TResult> | T1 을 받고 TResult 를 반환 | .NET (다음 편) |
UnityAction<T> | Action 과 같은 모양 | Unity 이벤트(onClick 등) |
돌아보며
이번 편에서 얻은 것
- 함수 이름 뒤에 괄호가 없으면 함수 자체를 넘긴다
- 델리게이트 선언은 함수의 모양에 이름을 붙이는 타입 선언이다
- 람다는 그 자리에서 만드는 이름 없는 함수이고, 매개변수 타입은 담는 곳을 보고 정해진다
다음에 풀 질문
- 값을 돌려주는 함수는 어떻게 담나 (Func)
- 람다가 바깥 변수를 쓰면, 그 값은 언제의 값인가 (클로저)
정리
- 델리게이트는 함수를 담는 그릇입니다. 버튼은 onClick 이라는 그릇에 담긴 함수들을 눌릴 때 부릅니다.
- 람다는 그 자리에서 만드는 함수입니다. 한 번만 쓸 동작은 이름을 짓지 않고 () => { } 로 넘깁니다.
- 직접 선언하기 전에 Action 과 Func 를 찾으세요. 흔한 모양은 이미 만들어져 있습니다.
다음 편에서는 값을 돌려주는 Func, 함수를 돌려주는 함수, 그리고 바깥 변수를 붙잡는 클로저를 봅니다.