Humility

아무리 노력해도 최고가 되지 못할 수 있다⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀그럼에도 노력하는자가 가장 겸손한 것 아닌가

공부하는 블로그

유니티/문법

[Unity] 유니티 C#) Event ( 이벤트 ), Action ( 액션 )

새벽_글쓴이 2025. 1. 8. 23:52
반응형

주요 이벤트 3가지

 

1. UnityEvent

기본 개념

UnityEvent는 Unity에서 제공하는 이벤트 시스템으로,
Inspector에서 시각적으로 이벤트를 연결하고 관리할 수 있게 해주는 메커니즘

 

기본 사용법

// UnityEvent 정의
public UnityEvent onGameStart;

// 이벤트 발생
void StartGame() {
    onGameStart.Invoke();
}

// 이벤트 구독
void OnEnable() {
    onGameStart.AddListener(HandleGameStart);
}

 

2. C# Evnet

기본 개념

"특정 상황이 발생했음" 을 다른 클래스에게 알리는 메커니즘

 

기본 사용법

// 이벤트 정의
public event Action<int> onScoreChanged;

// 이벤트 발생
void UpdateScore(int newScore) {
    onScoreChanged?.Invoke(newScore);
}

// 이벤트 구독
void OnEnable() {
    onScoreChanged += HandleScoreChanged;
}

 

3. Action ( 델리게이트 )

 

기본 개념

Action은 C#에서 제공하는 델리게이트의 한 종류로

반환값이 없는 메서드를 참조할 수 있는 타입

 

기본 사용법

// Action 정의
public Action<Vector3> onPlayerMoved;

// Action 실행
void MovePlayer(Vector3 position) {
    onPlayerMoved?.Invoke(position);
}

// Action 구독
void OnEnable() {
    onPlayerMoved = (pos) => {
        // 위치 업데이트 처리
    };
}

각 방식의 특징

UnityEvent

  • Unity Inspector에서 직접 연결 가능
  • 직렬화 지원 (Scene에 저장 가능)
  • 런타임 중에도 동적으로 리스너 추가/제거 가능
  • 성능은 C# Event보다 약간 느림

C# Evnet

  • 코드 기반으로만 구독 가능
  • 캡슐화가 잘 됨
  • 성능이 가장 좋음
  • 메모리 관리가 용이

Action

  • 가장 간단한 구현
  • 람다식 사용 가능
  • 일회성 이벤트에 적합
  • 구독자 관리가 다소 까다로움

특징적인 사용 상황

UnityEvent

  • UI 버튼 클릭 이벤트
  • 씬 전환 이벤트
  • 애니메이션 이벤트 트리거
  • Inspector에서 설정이 필요한 게임 이벤트

C# Evnet

  • 게임 상태 변경 통지
  • 캐릭터 상태 변경 알림
  • 시스템 간 통신
  • 데이터 모델 변경 알림

Action

  • 비동기 작업 완료 콜백
  • 임시 이벤트 처리
  • 간단한 함수 콜백
  • 람다식을 활용한 동적 이벤트 처리

각 활용 사례와 예시 코드

UnityEvent

public class UIManager : MonoBehaviour
{
    // 버튼 클릭시 발생하는 이벤트
    [SerializeField] private UnityEvent onStartButtonClicked;
    // UI 업데이트 이벤트
    [SerializeField] private UnityEvent<int> onScoreUpdated;
    
    public void StartButtonClick()
    {
        onStartButtonClicked.Invoke();
    }
    
    public void UpdateScore(int newScore)
    {
        onScoreUpdated.Invoke(newScore);
    }
}

// 다른 컴포넌트에서 구독
public class GameController : MonoBehaviour
{
    [SerializeField] private UIManager uiManager;
    
    void OnEnable()
    {
        // Inspector에서도 연결 가능하지만 코드로도 연결 가능
        uiManager.onScoreUpdated.AddListener(HandleScoreUpdate);
    }
    
    void HandleScoreUpdate(int score)
    {
        Debug.Log($"Score updated: {score}");
    }
}

 

C# Evnet

public class PlayerHealth : MonoBehaviour
{
    // 체력 변경 이벤트
    public event Action<int> onHealthChanged;
    // 플레이어 사망 이벤트
    public event Action onPlayerDied;
    
    private int currentHealth = 100;
    
    public void TakeDamage(int damage)
    {
        currentHealth -= damage;
        // 체력 변경 알림
        onHealthChanged?.Invoke(currentHealth);
        
        if(currentHealth <= 0)
        {
            // 사망 이벤트 발생
            onPlayerDied?.Invoke();
        }
    }
}

// 이벤트 구독자
public class HealthUI : MonoBehaviour
{
    [SerializeField] private PlayerHealth playerHealth;
    
    void OnEnable()
    {
        playerHealth.onHealthChanged += UpdateHealthBar;
        playerHealth.onPlayerDied += ShowGameOverScreen;
    }
    
    void UpdateHealthBar(int health)
    {
        // 체력바 UI 업데이트
    }
    
    void ShowGameOverScreen()
    {
        // 게임오버 화면 표시
    }
    
    void OnDisable()
    {
        playerHealth.onHealthChanged -= UpdateHealthBar;
        playerHealth.onPlayerDied -= ShowGameOverScreen;
    }
}

 

Action

public class QuestSystem : MonoBehaviour
{
    // 퀘스트 완료시 실행될 액션
    public Action<string> onQuestCompleted;
    // 아이템 획득시 실행될 액션
    public Action<string, int> onItemCollected;
    
    private void CompleteQuest(string questId)
    {
        // 퀘스트 완료 처리
        onQuestCompleted?.Invoke(questId);
    }
    
    private void CollectItem(string itemId, int amount)
    {
        onItemCollected?.Invoke(itemId, amount);
    }
}

// 비동기 작업 예시
public class AssetLoader : MonoBehaviour
{
    public void LoadAsset(string assetPath, Action<GameObject> onComplete)
    {
        StartCoroutine(LoadAssetCoroutine(assetPath, onComplete));
    }
    
    private IEnumerator LoadAssetCoroutine(string assetPath, Action<GameObject> onComplete)
    {
        // 에셋 로드 처리
        yield return new WaitForSeconds(1f); // 가상의 로딩 시간
        
        GameObject loadedAsset = null; // 실제로는 여기서 에셋을 로드
        
        // 로드 완료 후 콜백 실행
        onComplete?.Invoke(loadedAsset);
    }
}

// 사용 예시
void Start()
{
    var loader = GetComponent<AssetLoader>();
    loader.LoadAsset("SomeAsset", (loadedObject) => {
        Debug.Log("Asset loaded: " + loadedObject.name);
    });
}

이벤트 액션액션 의 차이

event Action

public event Action onComplete;  // 선언된 클래스 내에서만 호출 가능

// 외부에서는 += 또는 -= 로만 구독/해제 가능
gameManager.onComplete += HandleComplete;  // 가능
gameManager.onComplete -= HandleComplete;  // 가능

// 아래 작업들은 컴파일 에러 발생
gameManager.onComplete = null;  // 에러!
gameManager.onComplete = () => Debug.Log("완료");  // 에러!

 

Action (delegate)

public Action onComplete;  // 누구나 직접 호출, 할당 가능

// 외부에서 이렇게 직접 할당 가능
gameManager.onComplete = () => Debug.Log("완료");
// 또는 이렇게 직접 새로운 액션으로 덮어쓸 수 있음
gameManager.onComplete = null;

 

주요 차이점

1. 접근 제어

  • Action: 누구나 직접 호출, 할당, null 설정 가능
  • event Action: 선언된 클래스 내부에서만 호출 가능, 외부에서는 구독/해제만 가능

2. 캡슐화

  • Action: 캡슐화가 되지 않음 (외부에서 완전한 제어 가능)
  • event Action: 캡슐화가 됨 (외부에서 제한된 접근만 가능)

 

예시코드

public class GameManager : MonoBehaviour
{
    // 일반 Action - 외부에서 자유롭게 조작 가능
    public Action onGameStart;
    
    // event Action - 제한된 접근
    public event Action onGameEnd;
    
    public void StartGame()
    {
        onGameStart?.Invoke();  // 정상 동작
    }
    
    public void EndGame()
    {
        onGameEnd?.Invoke();    // 정상 동작
    }
}

public class OtherClass : MonoBehaviour
{
    private GameManager gameManager;
    
    void Start()
    {
        // Action은 직접 할당 가능
        gameManager.onGameStart = null;  // 가능
        gameManager.onGameStart = () => Debug.Log("게임 시작");  // 가능
        
        // event Action은 구독/해제만 가능
        gameManager.onGameEnd += HandleGameEnd;  // 가능
        // gameManager.onGameEnd = null;  // 컴파일 에러!
    }
}

 

보안과 캡술화가 중요한 경우에는 이벤트 액션
더 유연한 제어가 필요한 경우에는 일반 액션

 


유니티 이벤트이벤트의 사용 상황

 

실제 개발에선 C# 이벤트를 더 많이 사용하는 편이다

이유

1. 성능적 이점

  • C# eventUnityEvent보다 더 빠른 실행 속도를 보여준다
  • 특히 자주 호출되는 이벤트(예: 매 프레임 업데이트되는 플레이어 상태)의 경우 성능 차이가 누적될 수 있다

2. 코드 관리 측면

  • Inspector에서 연결된 이벤트는 나중에 디버깅하기 어려울 수 있다
  • 코드로 관리되는 C# event는 추적과 디버깅이 더 용이하다
  • 버전 관리(Git 등)에서도 코드로 관리되는 것이 더 안정적이다

3. 안정성

  • UnityEvent는 Inspector에서 잘못된 연결이 발생할 수 있다
  • C# event는 컴파일 시점에 타입 체크가 되어 더 안전하다

 

그래도 UnityEvent가 필요한 경우가 있다

 

UnityEvent가 필요한 경우

1. UI 버튼 클릭 이벤트처럼 디자이너가 직접 설정해야 하는 경우

2. 프리팹 단위로 이벤트 설정을 저장해야 하는 경우

3. 런타임에서 동적으로 이벤트 연결을 변경해야 하는 경우

 

예시 코드

public class GameSystem : MonoBehaviour
{
    // UI나 에디터 작업이 필요한 부분만 UnityEvent 사용
    [SerializeField] 
    private UnityEvent onGameStart;
    
    // 게임 로직에 관련된 부분은 C# event 사용
    public event Action<int> onScoreChanged;
    public event Action<float> onHealthChanged;
    public event Action onGameOver;
}
반응형