프로그래밍 / C++ / 언리얼

Programming/C# | Unity

[Unity] 델리게이트(Dlegate) & 이벤트(Event) 쉽게 접근하기.

아트성 2022. 8. 13. 15:16

Delegate

C#문법에서 타입이란 "값"을 담을 수 있는 존재다. 그렇다면, 그 "값"의 범위에 "함수"도 포함될수 있지 않을까?

delegate는 일반적인 class구문이 아니고 delegate라는 예약어로 표현된다.

 

만약 함수 여러개를 동시에 실행시키고 싶을때 delegate를 쓴다.

delegate의 장점이라고 하면 복잡한 코드와, 복잡한 함수의 연결을 좀 더 간단하게 처리할수있다.

 

 

선언하는 방법은 아래와 같다. (선언된 클래스에서만 사용가능하다.)

    public delegate void ChainFunction(int value);
    ChainFunction chain;

 

함수 2개 넣고 실행

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Base : MonoBehaviour
{
    // 함수 여러개를 동시에 실행시키고 싶을대 delegate를 쓴다.
    public delegate void ChainFunction(int value); // 매개변수를 int형으로 받는 델리게이트
    ChainFunction chain;

    int score;
    int health;

    public void SetScore(int value)
    {
        score += value;
        Debug.Log($"점수가{value} 만큼 증가했습니다. 총 획득 점수 = {score}");
    }

    public void SetHealth(int value)
    {
        health += value;
        Debug.Log($"플레이어의 체력이 {value} 만큼 증가했습니다. 총 체력 = {health}");
    }

    private void Start()
    {
        // 함수를 더해줄 때
        chain += SetScore;
        chain += SetHealth;
        chain(5);
    }
}

 

 

함수를 하나만 지울 때

    private void Start()
    {
        // 함수를 더해줄 때
        chain += SetScore;
        chain += SetHealth;

        // 함수를 빼줄 때
        chain -= SetHealth;

        chain(5);
    }

 

 

함수를 모두 비우고 호출시킬 때

    private void Start()
    {
        // 함수를 더해줄 때
        chain += SetScore;
        chain += SetHealth;

        // 함수를 빼줄 때
        chain -= SetHealth;
        chain -= SetScore;


        chain(5);
    }

 

 

NullReferenceException 해결 방법

    private void Start()
    {
        // 함수를 더해줄 때
        chain += SetScore;
        chain += SetHealth;

        // 함수를 빼줄 때
        chain -= SetHealth;
        chain -= SetScore;

        if(chain != null)
        {
            chain(5);
        }
    }

 

 

Event

Event(콜백)는 Observer 디자인 패턴과 연관성이 있다. 이벤트는 작업 실행을 알리기 위해 보내는 메시지다.

이 작업은 버튼 클릭과 같은 사용자 조작으로 발생하거나, 속성 값 변경 등이 일어났을때, 어떠한 일련의 상태들의 변화가 필요한 구독자들에게 전달할때 유용하다. 여기서 구독자란 다른 외부에있는 클래스에 선언된 함수를 의미한다.

여기서 Event를 사용할때 주의할사항들이 있다.

 

1. 클래스에서 이벤트를 제공한다.

2. 외부에서 자유롭게 해당 이벤트를 구독하거나 해지하는 것이 가능하다.

3. 외부에서 구독 / 해지는 가능하지만 이벤트 발생은 오직 내부에서만 가능하다.

 

이와같이 Observer패턴을 잘 활용하면, 변화에 빠르게 대처해야하는 함수들을 보다 쉽고 빠르게 다루기가 쉽다.

Event를 사용하는 방법은 2가지로 나뉜다.

 

첫번째 방법은 증감연산자(+=, -=)를 이용해 구독 / 취소 하는방법이고, 

두번째 방법은 유니티엔진안에 Events라는 클래스 내부에 정의된 필드들을 사용해서 구독 / 취소하는 방법이다. 

 

방법 1

방법 1은 델리게이트와 연동시키는 방법이다.

선언하는 방법은 아래와 같다.

public delegate void ChainFunction(int value);
public static event ChainFunction OnStart;

우선 스크립트 두개를 준비한다.

스크립트 연결은 Hierarchy에있는 오브젝트 아무곳에 넣어주고 실행시키면 된다.

순서는 #1  →  #2  →  #3 순서로 간다.

 

# 1. Int Type delegate와 Event 선언

# 3. 실행

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Base : MonoBehaviour
{
    public delegate void ChainFunction(int value);
    public static event ChainFunction OnStart;

    private void OnDisable()
    {
        OnStart(5);
    }
}

 

 

# 2. 구독자 추가 ( Test.cs → SetAttack, SetScore, SetHealth )

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Test : MonoBehaviour
{
    int attack;
    int score;
    int health;
    
    private void Start()
    {
        Base.OnStart += SetAttack;
        Base.OnStart += SetScore;
        Base.OnStart += SetHealth;
    }
    
    public void SetAttack(int value)
    {
        attack += value;
        Debug.Log($"공격력이 {value}만큼 증가했습니다. 현재 공격력 = {attack}");
    }
    
    public void SetScore(int value)
    {
        score += value;
        Debug.Log($"점수가{value} 만큼 증가했습니다. 총 획득 점수 = {score}");
    }

    public void SetHealth(int value)
    {
        health += value;
        Debug.Log($"플레이어의 체력이 {value} 만큼 증가했습니다. 총 체력 = {health}");
    }
}

 

 

OnDisable에 OnStart메서드를 호출했기때문에, 게임이 종료되고, 여러 함수들이 호출되는것을 확인할 수있다.

 

 

 

방법 2

방법2는 델리게이트없이 UnityEvent내부에 있는 필드(AddListener, Invoke, RemoveListener) 를 상속받아서 이용하는 방법이다. 

 

이번에는 방법1이랑은 다르게 int타입이 아닌 float타입으로 선언했다. (선언할때는 반드시 클래스 외부에다 선언하거나, 스크립트를 따로 만들어서 선언해야 한다.)

#1  →  #2  →  #3  →  #4순서로 간다.

 

# 1. Event의 자식 클래스인 ChainFunction 클래스(float Type) 선언

# 2. ChainFunction 클래스의 인스턴스 선언

# 4. Invoke로 여러 함수들 호출

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ChainFunction : UnityEngine.Events.UnityEvent<float> { } // Event 자식 클래스 선언

public class Base : MonoBehaviour
{
    public static ChainFunction OnStart = new ChainFunction();

    private void OnDisable()
    {
        OnStart.Invoke(7.5f);
    }
}

 

 

# 3. 구독자 추가

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Test : MonoBehaviour
{
    float health = 100f;
    float attack = 300f;
    float defense = 200f;

    private void Start()
    {
        Base.OnStart.AddListener(SetHealth);
        Base.OnStart.AddListener(SetDefense);
        Base.OnStart.AddListener(SetAttack);
    }

    private void SetHealth(float Damage)
    {
        health -= Damage;
        Debug.Log($"체력이 {Damage}만큼 감소했습니다. 현재 HP = {health}");
    }

    private void SetDefense(float Damage)
    {
        defense -= Damage;
        Debug.Log($"방어력이 {Damage}만큼 감소했습니다. 현재 방어력 = {defense}");
    }

    private void SetAttack(float Damage)
    {
        attack -= Damage;
        Debug.Log($"공격력이 {Damage}만큼 감소했습니다. 현재 공격력 = {attack}");
    }
}

 

반응형