UnityEvent, UnityAction and Delegate

 유니티에서 버튼에 CallBack을 등록시키기 위해서 에디터에서 클래스와 메쏘드를 지정하는 방법이 있고, 스크립트에서 직접 등록하는 방법이 있다.

이 때 등장하는 것이 UnityEvent이다. UnityEvent는 MonoBehavior를 상속받는 모든 클래스에 사용가능하다. 일단 선언이 되면 Editor에서 CallBack 함수를 등록하는 GUI가 추가되는 것을 볼 수 있다.

event
간단하기야 당연히 이렇게 Editor에서 Class의 Method를 지정해 주는 것이 간단하지만, 실제로는 에셋과 스크립트 간의 버젼 충돌을 피하고 원활한 업데이트를 위해서 스크립트로 이벤트를 집어 넣어주는 경우가 많다. 그 때 쓰이는 함수가 AddListener이다.

인자로 들어가는 것이 Events.UnityAction인데 이 UnityAction은 유니티에서 엔진에서 사용하려고 미리 만들어 놓은  C# Delegate이다. 정의는 아래와 같이 void부터 4개의 인자를 가지는 Delegate까지 커버한다.


 그렇다면 해당 Delegate를 등록받는 onClick 이라는 녀석은 무엇일까?
바로 이 onClick이 UnityEventBase를 상속받는 UnityEvent 클래스의 Instance이다.  즉, 이 Class의 Instance에 UnityAction을 Register하는 것이다. 그런 다음 해당 클래스의 Invoke 메소드가 호출되면서 모든 등록된 Delegate들이 실행되게 된다.

 

UnityEvent가 UnityAction을 취하는 방법을 살펴 보면,

위 코드에서 A키를 8회 눌렀을 때 출력은 아래와 같다. 순서대로 찍히고 마지막에 다 찍힌다.

 

동일한 기능을 하는 코드를  UnityEvent와 UnityAction을 이용하면 아래와 같이 구현할 수 있다.

이렇게 하면 코드량은 조금 더 길어 보일 지 모르나, 각 이벤트에 등록된 UnityAction의 추가(AddListener) 및 삭제(RemoveListener)가 동적으로 자유로우므로, 마치 Observer Pattern을 적용한 느낌도 나면서 훨씬 더 유연한 코드 작성이 가능하다. (사실 UnityEvent는 미리 구현하여 내장된 Observer Pattern으로 보는 것이 맞을 것이다.)


아래와 같이 추상 클래스인 UnityEvent<T0>를 상속하여 선언함으로써 인자를 넘겨 주는 타입의 UnityAction을 호출할 수도 있다. 인자를 4개 받는 것까지 확장 가능하다.


Unity에서 사용하는 Mono 2.x는 C# 3.0까지는 안전하게 지원하므로, C# 3.0 버젼의 Lambda를 이용할 수 있다. Lambda expression은 anonymous function의 일종으로 이를 이용하면 간단히 delegate를 생성 가능하다.  따라서, 아래와 같이 동일 기능을 더 간단히 구현할 수 있다. Lambda 정의 Ref

또한, C# 2.0에서부터 도입된, delegate-method-group-conversion에 의해서 바로 Method name을 Delegate에 Assign할 수도 있다. 

이러한 모든 방법들을 다 Event를 거는데 활용해보면, 결과적으로 다음 코드와 같이 다양한 형태를 받는 AddListener가 나올 수 있게 된다.

 

출력은 아래와 같고 버튼 클릭 시에 모두 잘 동작한다.

UnityEvent와 UnityAction에 대한 이러한 이해를 기반으로 Unity Official Tutorial 에 나와있는 EventManager Tutorial를 살펴보면 이해가 훨씬 쉽다.

 

P.S. Delegate를 다룰 때 많이 보게되는 event keyword는 위에서 언급한 UnityEvent와는 다른 개념이다. 이는 Private Field를 생성해주는 키워드이다. 이와 관련해서는 StackOverflow에서 Jon Skeet이 잘 설명해주고 있다. Why do we need the “event” keyword while defining events?

An event is fundamentally like a property – it’s a pair of add/remove methods (instead of the get/set of a property). When you declare a field-like event (i.e. one where you don’t specify the add/remove bits yourself) a public event is created, and a private backing field. This lets you raise the event privately, but allow public subscription. With a public delegate field, anyone can remove other people’s event handlers, raise the event themselves, etc – it’s an encapsulation disaster.

“이벤트는 근본적으로 Property와 비슷하다. Property가 get/set 을 가지는 반면 add/remove method를 가지고 있다. Field-like event를 선언하게 되면(명시적인 add/remove 선언을 하지 않은), public event가 만들어지고, back단에 private field가 만들어진다. 이로써 public하게 등록 가능한, private event가 만들어지는 것이다. 단순한 Public delegate field로써는 다른 누구든지 해당 delegate 이벤트를 제거하거나 호출할 수 있고 이는 encapsulation 재앙을 가져온다.”

따라서 기능적인 관점에서 delegate 앞에 event keyword를 붙이나 안붙이나 비슷하지만, Encapsulation의 관점에서 본다면 event를 붙이게 되면 Access Contorl을 가능하게 할 수 있다. (선언된 Class 외부에서 Invoke, Clear, Assign(+=, -=제외)을 할 수 없다.)