김김김의 게임개발
  • 유니티 델리게이트&이벤트
    2023년 09월 15일 20시 55분 14초에 업로드 된 글입니다.
    작성자: noun06
    • 게임에서 플레이어와 몬스터를 소환하는 기능을 만들고 싶을 때, 보통 다음과 같은 메서드를 만들고 호출함.
    SpawnPlayer();
    SpawnMonster();

     

     

    • 이때 SpawnPlayer(); 메서드를 예를 들면 플레이어를 다양한 위치에 생성하고 싶을 수 있음. 즉, 하나의 동작에 다음과 같이 여러가지 상황이 부여될 수 있다는 것임.
    SpawnPlayer_ARoom();
    SpawnPlayer_BRoom();
    SpawnPlayer_RandomPlace();

     

     

    • 이때, 변수처럼 하나의 로직에서 다른 값으로 사용할 수 있게 할 수 있게하는 개념을 메서드에 적용할 수 있게 하는 것이 델리게이트임. 메서드를 변수처럼 만들어두고 할당한 기능이 작동하게 해주는 것임. 
    • 델리게이트의 기본 구조는 다음과 같음.
    delegate 반환형 델리게이트명(매개변수);

     

     

    • 앞의 소환 기능을 예시로 다음과 같이 델리게이트를 선언해야함.
    delegate void SpawnDelegateFunc();
    
    void Start()
    {
        InitStageData();
        SpawnPlayer();
        SpawnMonster();
    }

     

     

    • 다음으로 델리게이트를 저장할 변수를 만들어야함. 이때 변수의 타입은 델리게이트명을 사용하면 됨. 
    • 그리고 델리게이트에 사용할 함수들을 만듦. 사용될 함수들은 반환형과 매개변수가 같아야함. 
    delegate void SpawnDelegateFunc();
    
    void Start()
    {
        SpawnDelegateFunc spawnAction; // <<
    
        InitStageData();
        SpawnPlayer();
        SpawnMonster();
    }
    
    void SpawnPlayer_ARoom(){...};
    void SpawnPlayer_BRoom(){...};
    void SpawnPlayer_RandomPlace(){...};
    //사용 불가능한 경우
    //int SpawnPlayer _CRoom();
    //void SpawnPlayer _DRoom(string roomName);

     

     

    • 준비된 함수 중 사용하고 싶은 함수를 델리게이트 변수에 할당함.
    delegate void SpawnDelegateFunc();
    
    void Start()
    {
        SpawnDelegateFunc spawnAction;
        spawnAction = SpawnPlayer_ARoom; // <<
        //메서드를 변수에 저장만 한 상태이므로 ()는 붙이지 않음.
    
        InitStageData();
        SpawnPlayer();
        SpawnMonster();
    }
    
    void SpawnPlayer_ARoom(){...};
    void SpawnPlayer_BRoom(){...};
    void SpawnPlayer_RandomPlace(){...};

     

     

    • 이제 필요한 곳에서 저장된 델리게이트를 실행하면 됨.
    delegate void SpawnDelegateFunc();
    void Start()
    {
        SpawnDelegateFunc spawnAction;
        spawnAction = SpawnPlayer_ARoom;
    
        InitStageData();
        spawnAction(); // <<
        SpawnMonster();
    }
    
    void SpawnPlayer_ARoom(){...};
    void SpawnPlayer_BRoom(){...};
    void SpawnPlayer_RandomPlace(){...};

     

     

    • 델리게이트를 쓰는 가장 큰 이유는 확장성임. 측정 불가능한 경우의 수가 있을 때, 델리게이트를 사용하지않고 필요한 함수를 바로 가져다 쓰는 것은 힘듦. 
    • 예를 들어 여러 상황에 메시지를 전할 때가 많은 UI팝업 기능을 만든다 하면 델리게이트를 다음과 같이 활용할 수 있을 것임.
    using UnityEngine;
    
    public class UIPopup
    {
        public delegate void PopupConfirmFunc();
    
        public void PopupConfirm(PopupConfirmFunc confirmAction)
        {
            confirmAction();
        }
    }
    using UnityEngine;
    
    public class GameManager : MonoBehaviour
    {
        private UIPopup uiPopup;
    
        private void Start()
        {
            // UIPopup 객체 초기화
            uiPopup = new UIPopup();
    
            // 캐릭터 변경 팝업 호출
            uiPopup.PopupConfirm(ChangeCharacter);
    
            // 게임 종료 팝업 호출
            uiPopup.PopupConfirm(QuitGame);
        }
    
        // 캐릭터 변경 메서드
        private void ChangeCharacter()
        {
            Debug.Log("캐릭터 변경 로직을 실행합니다.");
            // 여기에 캐릭터 변경 로직 추가
        }
    
        // 게임 종료 메서드
        private void QuitGame()
        {
            Application.Quit();
        }
    }

     

     

    • 이와 같이 메서드의 매개변수로 변수를 넘기듯이 (각종 메서드들이 담겨있는)델리게이트를 넘길 수 있음.
    • 이제 각 기능별로 따로 클래스를 만들 필요도, 메서드를 따로 만들어 둘 필요가 없이 UIPopup을 모두 동일하게 사용할 수 있음. 
    • 별도로 기능을 구현하지 않고 전달받은 기능만 실행하도록 여러 상황에 대응할 수 있음!

     

    • 이벤트는 델리게이트의 한 종류라고 볼 수 있음. 기본적인 구조는 다음과 같음.
    event 델리게이트명 변수명;

     

     

    • 이벤트는 이벤트 발생자(pubilhser)과 이벤트 구독자(subscriber)로 구성됨.
    • 발생자는 이벤트가 발생하는 객체를 나타냄. 이벤트가 일어났음을 알릴 때 사용할 이벤트를 정의하고 발생 조건을 만족할 때 이벤트 핸들러라고 불리는 이벤트를 처리하는 함수를 호출함.
    • 구독자는 이벤트 처리를 감지하고 처리하는 객체/함수를 나타냄. 이벤트 발생자의 이벤트에 등록하여 이벤트가 발생했을 때 행당 이벤트를 처리하도록 설정됨.

     

    • 게임에서 플레이어 죽음 이라는 이벤트가 있을때 이벤트 발생자는 플레이이고 이벤트 구독자는 게임매니저, UI 등이 됨.
    • 게임 매니저나 UI는 플레이어 죽음 이벤트에 등록하여 이벤트가 발생할 때마다 점수 감소, UI창 활성화 등의 특정 동작을 수행함. 
    using UnityEngine;
    using System;
    
    public class Player : MonoBehaviour
    {
        // 플레이어 죽음 이벤트 선언
        public event Action onPlayerDeath;
    
        // 플레이어가 죽음을 처리하는 메서드
        public void Die()
        {
            Debug.Log("플레이어가 죽었습니다.");
    
            // 플레이어 죽음 이벤트 발생
            onPlayerDeath?.Invoke();
        }
    }
    public class GameManager : MonoBehaviour
    {
        public Player player;
    
        private void Start()
        {
            // 플레이어 죽음 이벤트에 게임 매니저의 메서드 등록
            player.onPlayerDeath += OnPlayerDeath;
        }
    
        // 플레이어가 죽었을 때 호출되는 메서드
        private void OnPlayerDeath()
        {
            Debug.Log("게임 매니저: 플레이어가 죽었습니다.");
            // 여기서 게임 오버 처리 또는 다른 동작 수행
        }
    }
    public class UIManager : MonoBehaviour
    {
        public Player player;
    
        private void Start()
        {
            // 플레이어 죽음 이벤트에 UI의 메서드 등록
            player.onPlayerDeath += OnPlayerDeath;
        }
    
        // 플레이어가 죽었을 때 호출되는 메서드
        private void OnPlayerDeath()
        {
            Debug.Log("UI: 플레이어가 죽었습니다.");
            // 여기서 UI 업데이트 또는 다른 동작 수행
        }
    }

     

     

    • 위 예시를 통해 이벤트 처리에서 유용하게 활용되는 옵저버 패턴의 핵심 아이디어의 확인이 가능함.
    • 옵저버 패턴은 어떤 객체(주체)의 상태가 변할 때, 그 객체에 의존하는 다른 객체(옵저버)들이 자동으로 알림을 받고 상태 변화에 대응하는 메커니즘으로 작동함.
    • 예시에서의 주체는 플레이어 객체이며 옵저버는 게임매니저와 UI매니저임. 주체가 죽음 이벤트를 발생시키면 옵저버들은 해당 이벤트를 구독하고 이벤트가 발생하면 동작을 수행하는거임.

     

    • 그런데 위 코드에서 이상하게도 event를 활용하였지만 delegate를 만들지 않음.
    • 대신 Action이라는 키워드를 사용하였는데 이는 C#에서 제공하는 내장 delegate임. 매개변수로 넘기는 부분은 Action 뒤에 제네릭으로 추가함.(뒤에 <타입>붙이기).
    • 한가지 조건이 있는데 Action은 반환형이 void인 델리게이트만 처리가 가능함.


    댓글