Action과 delegate, Unityevent모두 메서드를 대신 호출해 주는 “대리자”라는 성격을 가진 점에서 비슷합니다. 각각 차이가 존재하며, 그에 따라 용도도 조금씩 달라집니다. 이러한 차이를 아는 것은 코드를 효율적으로 구성하는 데 도움이 많이 됩니다. 이번 시간은 대리자를 왜 사용하는지와 각각의 차이점을 알아 보겠습니다.
목차
[대리자를 사용하는 이유]
대리자는 함수를 파라미터로 넘겨준다는 점에서 커플링을 최소화 해줍니다. 이에 따라 코드가 더 유연해 지는 것 입니다. 여기서 커플링이란 클래스와 클래스간 강한결합으로 인해서 어느 한쪽을 수정하면 다른 한쪽도 같이 수정해야하는 경우를 말합니다. 설명만 들으면 감이 잘 안오실 테니 예시를 들어보겠습니다.
만약 적이 생성될 때 UI를 통해서 적이 생성 되었다는 알림을 띄운다고 가정할 때 대리자를 사용하지 않는다면, Enemy클래스 내부에서 UI를 띄우는 함수를 구현하게 될 것 입니다. 이렇게 할 경우 Enemy와 UI가 강한 결합으로 이어져 수정이 어려운 코드가 되어 버립니다. 이때 사용할 수 있는 것이 대리자 입니다.
using UnityEngine;
using UnityEngine.UI;
// 이벤트 매니저 클래스
public class EventManager : MonoBehaviour
{
// 적 생성 이벤트를 정의하는 대리자
public delegate void EnemySpawnedDelegate();
// 적 생성 이벤트를 선언
public static event EnemySpawnedDelegate OnEnemySpawned;
// 적 생성 이벤트를 발생시키는 메서드
public static void SpawnEnemy()
{
Debug.Log("적이 생성되었습니다.");
// 적 생성 이벤트 발생
OnEnemySpawned?.Invoke();
}
}
// 적 클래스
public class Enemy : MonoBehaviour
{
private void Start()
{
// 적이 생성되었을 때 이벤트 매니저에 등록
EventManager.OnEnemySpawned += EnemySpawnHandler;
}
private void OnDestroy()
{
// 적이 파괴될 때 이벤트 매니저에서 등록 해제
EventManager.OnEnemySpawned -= EnemySpawnHandler;
}
// 적 생성 이벤트 핸들러
private void EnemySpawnHandler()
{
Debug.Log("적 생성 이벤트를 받았습니다.");
// 여기에 적 객체가 받을 동작을 구현합니다.
}
}
// UI 매니저 클래스
public class UIManager : MonoBehaviour
{
public Text enemySpawnedText;
private void Start()
{
// 적 생성 이벤트를 감지하여 UI를 업데이트
EventManager.OnEnemySpawned += UpdateUI;
}
private void OnDestroy()
{
// 이벤트 해제
EventManager.OnEnemySpawned -= UpdateUI;
}
// UI 업데이트 메서드
private void UpdateUI()
{
enemySpawnedText.text = "적이 생성되었습니다!";
}
}
Enemy
클래스가 UIManager
클래스를 직접 참조하지 않고, 대신 EventManager
를 통해 이벤트를 발생시켜 UI를 업데이트합니다. 이렇게 하면 Enemy
와 UIManager
간의 결합이 줄어들어 커플링이 최소화됩니다. 만약 향후에 UI를 변경하거나 다른 동작을 수행해야 한다면, UIManager
를 수정하여 EventManager
의 이벤트만 처리하면 됩니다. 이렇게 하면 Enemy
클래스의 수정 없이도 기능을 확장하거나 변경할 수 있습니다.
[Action의 개념]
Action은 C#에서 사용되는 델리게이트(delegate)의 일종으로, 반환값이 없는 메서드나 함수를 나타냅니다.
[Action의 사용법]
Action은 C#에서 사용되는 대리자로, 매개변수를 가지지 않고 반환값이 없는 메서드를 나타냅니다. 이것을 사용하면 함수나 메서드를 하나의 변수로 다룰 수 있어 코드를 더 유연하게 만들어줍니다. 간단한 예시를 통해 Action의 사용법을 설명하겠습니다.
using UnityEngine;
using System;
public class Example : MonoBehaviour
{
// Action 선언
private Action greet;
private void Start()
{
// Action에 람다식 할당
greet = () => Debug.Log("Hello, Unity!");
// Action 실행
greet();
}
}
Start 함수에서 Action에 람다식을 할당하고, 이후 Action을 실행하여 “Hello, Unity!” 메시지를 디버그 로그로 출력합니다.
[Action의 특성]
- 매개변수 타입: Action은 제네릭(Generic)을 사용하여 하나 이상의 매개변수를 정의할 수 있습니다. 예를 들어,
Action<T>
는 하나의 매개변수를 받는 Action을 나타내며,Action<T1, T2>
는 두 개의 매개변수를 받는 Action을 나타냅니다. - 메서드의 반환값: Action은 반환값이 없는 메서드나 함수를 대체하여 사용됩니다. 따라서 Action으로 정의된 메서드는 반환값이 없으며, void 타입을 반환합니다.
- 람다식 사용: Action은 람다식(lambda expression)을 사용하여 메서드를 정의할 수 있습니다. 이를 통해 Action을 선언하고 동시에 메서드의 내용을 구현할 수 있습니다.
- 이벤트 처리: Action은 이벤트 처리에 특히 유용하게 사용됩니다. 이벤트를 등록하고 실행하는 데에 사용되며, 이를 통해 이벤트 기반 프로그래밍을 쉽게 구현할 수 있습니다.
- 콜백 함수 전달: 비동기 작업을 처리할 때 Action을 사용하여 콜백 함수를 전달할 수 있습니다. 비동기 작업의 완료 후 수행될 동작을 Action으로 전달하여 쉽게 처리할 수 있습니다.
- 메모리 할당: Action은 대리자이므로 메모리를 할당합니다. 따라서 과도한 Action 사용은 메모리 부하를 일으킬 수 있으므로, 신중하게 사용해야 합니다.
- 유연성: Action을 사용하면 메서드나 함수를 변수로 다룰 수 있어서 코드를 더 모듈화하고 유연하게 만들어줍니다. 필요에 따라 매개변수의 개수나 타입을 조절하여 다양한 상황에 대처할 수 있습니다.
[delegate의 개념]
delegate는 C#에서 사용되는 대리자 타입으로, 다른 메서드의 참조를 저장하거나 전달하는데 사용됩니다.
[delegate 사용 예시]
using UnityEngine;
using UnityEngine.UI;
public class ButtonManager : MonoBehaviour
{
// delegate 정의: 클릭 이벤트 처리를 위한 델리게이트
public delegate void ButtonClickDelegate();
// 델리게이트 변수 선언
private ButtonClickDelegate buttonClickDelegate;
private void Start()
{
// 델리게이트 변수 초기화: 클릭 이벤트 처리할 메서드를 참조
buttonClickDelegate = OnButtonClick;
// 유니티 버튼 오브젝트 찾기
Button button = GetComponent<Button>();
// 버튼 클릭 이벤트에 델리게이트 변수 연결
button.onClick.AddListener(InvokeButtonClick);
}
// 버튼 클릭 이벤트를 처리할 메서드
private void OnButtonClick()
{
Debug.Log("Button Clicked!");
}
// 델리게이트 변수를 호출하여 대리할 메서드 실행
private void InvokeButtonClick()
{
buttonClickDelegate?.Invoke();
}
}
위 코드에서는 먼저 ButtonClickDelegate
라는 델리게이트를 정의합니다. 이는 버튼 클릭 이벤트를 처리할 메서드의 시그니처를 나타냅니다. 그런 다음 ButtonManager
클래스에서는 buttonClickDelegate
라는 델리게이트 변수를 선언하고 초기화합니다. Start()
메서드에서는 버튼의 클릭 이벤트에 InvokeButtonClick
메서드를 등록하여 버튼이 클릭되면 buttonClickDelegate
를 호출하도록 합니다. InvokeButtonClick
메서드에서는 buttonClickDelegate
를 호출하여 등록된 메서드를 실행합니다.
[delegate 특성]
- 메서드 시그니처: 델리게이트는 대리할 메서드의 시그니처(매개변수 타입, 반환값 타입)를 나타냅니다. 델리게이트가 대리할 메서드의 시그니처와 일치해야 합니다.
- 유형 안전성: 델리게이트는 형식 안전성을 가집니다. 즉, 델리게이트가 참조하는 메서드와 일치하지 않는 메서드를 대리할 수 없습니다. 이는 컴파일러가 델리게이트에 대한 타입 안전성을 검사할 수 있게 합니다.
- 대리할 메서드 참조: 델리게이트는 다른 메서드의 참조를 보유하고 호출할 수 있습니다. 이를 통해 메서드를 변수로 전달하거나, 다른 메서드에게 메서드를 전달할 수 있습니다.
- 이벤트 처리: 델리게이트는 이벤트 처리에 주로 사용됩니다. 예를 들어, UI 요소의 클릭 이벤트나 타이머 이벤트 등을 처리할 때 델리게이트를 사용합니다.
- 람다식 사용: 델리게이트를 초기화할 때 람다식(lambda expression)을 사용하여 메서드를 정의할 수 있습니다. 이를 통해 델리게이트를 선언하고 동시에 메서드의 내용을 구현할 수 있습니다.
- 다중 호출: 델리게이트는 여러 메서드를 한 번에 호출할 수 있습니다. 이를 통해 하나의 델리게이트를 통해 여러 작업을 한 번에 처리할 수 있습니다.
- 동적 메서드 호출: 델리게이트를 사용하면 동적으로 메서드를 호출할 수 있습니다. 실행 시에 어떤 메서드를 호출할지 결정하고 호출할 수 있습니다.
[UnityEvent개념]
유니티에서 사용되는 이벤트 시스템의 일부로, 게임 오브젝트 간에 통신하고 상호작용하는 데 사용됩니다. 간단하게 말해, UnityEvent는 특정 상황이나 조건이 발생했을 때 실행될 메서드들을 저장하고 관리하는 컨테이너라고 생각할 수 있습니다.
[UnityEvent 사용 예시]
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Events;
public class ButtonClickHandler : MonoBehaviour
{
// UnityEvent 선언
public UnityEvent onButtonClick;
private void Start()
{
// UnityEvent에 메서드 연결
onButtonClick.AddListener(OnButtonClicked);
}
// 버튼 클릭 시 호출될 메서드
void OnButtonClicked()
{
Debug.Log("Button Clicked!");
}
}
위 코드에서는 ButtonClickHandler
클래스를 정의하고, 이 클래스에 UnityEvent를 선언합니다. Start()
메서드에서는 UnityEvent에 OnButtonClicked()
메서드를 연결하고, OnButtonClicked()
메서드는 버튼이 클릭되었을 때 실행될 내용을 정의합니다.
[UnityEvent의 특성]
- 이벤트 트리거링: UnityEvent는 특정 이벤트가 발생했을 때 연결된 모든 메서드를 호출합니다. 이를 통해 특정 상황이나 조건이 발생했을 때 다양한 동작을 실행할 수 있습니다.
- 다중 메서드 연결: UnityEvent는 여러 개의 메서드를 연결하여 사용할 수 있습니다. 이를 통해 하나의 이벤트에 여러 동작을 수행할 수 있습니다. 즉, 하나의 UnityEvent에 여러 개의 이벤트를 연결하여 처리할 수 있습니다.
- 런타임 변경: UnityEvent는 런타임 중에도 변경할 수 있습니다. 이를 통해 동적으로 이벤트를 추가하거나 제거할 수 있습니다. 따라서 게임이 실행 중일 때도 이벤트를 관리하고 조작할 수 있습니다.
- 시각적으로 편리한 편집: 유니티 인스펙터 창에서 UnityEvent를 편집할 수 있습니다. 이를 통해 개발자는 쉽게 이벤트를 추가하고 관리할 수 있습니다. 인스펙터 창에서 UnityEvent를 사용하여 이벤트를 설정하고 관리할 수 있습니다.
- 안전성: UnityEvent는 형식 안전성을 가집니다. 즉, 연결된 메서드의 시그니처와 일치하지 않는 메서드를 UnityEvent에 연결할 수 없습니다. 이를 통해 컴파일러가 UnityEvent에 대한 타입 안전성을 검사할 수 있습니다.
[Action과 delegate, UnityEvent 차이]
- Delegate:
- 특정한 메서드 시그니처를 가지는 메서드를 참조하고자 할 때 사용됩니다.
- 다중 메서드를 연결하거나 해제해야 할 때 유용합니다.
- Action:
- 반환값이 없는 메서드를 대신 호출할 때 사용됩니다.
- 비동기 작업, 이벤트 처리, 콜백 등에서 사용됩니다.
- 메서드 시그니처를 미리 정의하지 않고도 사용할 수 있어 편리합니다.
- UnityEvent:
- 유니티에서 게임 오브젝트 간의 이벤트 처리를 위해 사용됩니다.
- 주로 UI 상호작용, 애니메이션 제어, 게임 이벤트 처리 등에서 사용됩니다.
- 런타임 중에도 변경 가능하고, 인스펙터 창에서 편집할 수 있어 유니티 개발 환경에 특화되어 있습니다.
간단하게 말해서, Delegate는 일반적인 C# 프로그래밍에서 사용되는 메서드 참조를 다룰 때, Action은 반환값이 없는 메서드 호출을 다룰 때, UnityEvent는 유니티에 인스펙터에서도 사용할 수 있기 때문에 유니티의 오브젝트를 사용할 때 주로 사용됩니다.
[마무리 글]
이 3가지의 특성만 잘 기억하시고, 예시코드를 이해하셨으면 대리자를 사용해야하는 상황이 오셨을 때 자연스럽게 필요한 대리자를 골라서 사용하실 수 있을 것 입니다.