[Unity/C#]델리게이트 (Delegate)

강동현·2024년 1월 22일
0

Unity/C#

목록 보기
12/24

델리게이트 : Delegate

  • Delegate = 대리자
  • 함수 포인터 = 함수 객체(Pred=FunctionPointerPred=Function Pointer)
  • 함수의 주소값을 가지고 함수를 대신 호출
  • 선언된 델리게이트는 클래스
    • 참조형(Reference) 타입
      주소 값 연산 수행
      _myDelegate(델리게이트 변수)MyDelegate(인스턴스)의 주소를
      MyDelegate(인스턴스)TestFunction(함수)의 주소를 가지고
      참조하고, 참조해서 호출되는 과정

델리게이트 선언

  1. 함수의 반환타입델리게이트의 반환타입과 동일한지 확인
  2. 함수의 매개변수 목록 델리게이트의 매개변수 목록과 동일한지 확인
  • 1, 2 모두 성립 시, 델리게이트에 할당 가능
접근제한자 delegate 반환 형식 식별자(매개변수);
public delegate void MyDelegate();//델리케이트 타입의 정의
public MyDelegate _myDelegate;//델리케이트 변수(객체) 선언

public void TestFunction()
{
    Debug.Log("Test");
}
public delegate int MyDelegate2(int num);//델리케이트 타입의 정의
public MyDelegate2 _myDelegate2;//델리케이트 변수(객체) 선언

public int TestFunction2(int num)
{
    return num;
}

델리게이트 사용법

  • new를 통한 델리게이트 객체 생성
  • Pred를 활용한 델리게이트 객체 생성
  • 델리게이트 변수(=함수객체(Pred))()를 통한 실행
    • 델리게이트 변수에 연결된 모든 함수 객체가 실행
public void Start()
{
    //C# 1.0 버전(사용X) : 생성자를 활용한 새 객체 생성법
    _myDelegate = new MyDelegate(TestFunction);
    //C# 2.0 버전 : Pred(함수 객체)를 활용한 새 객체 생성법
    _myDelegate = TestFunction;
    _myDelegate();
}

델리게이트 의의

  • 그냥 함수 호출하면 되는데 뭐하러 델리게이트 변수 만들어서 함수 객체 할당하고
    • 요약: 그지랄을 왜 해야 허냐?
    • 델리게이트 = 타입
      • 이유: 타입함수1. 매개변수 or 2. 반환형식에 넣을 수 있음
      • 요약: 델리게이트함수 객체(Pred=Function Pointer)처럼 사용된다는 것
      • 델리게이트를 인자로 전달하거나 반환 값으로 리턴하면 매우 유용하기 떄문에 사용
TestFunc();
  • 인자 값delegate로 받는 예시
public class DelegateTest2 : MonoBehaviour
{
    public delegate void TestDelegate();
    TestDelegate _testDelegate;
    private void Start()
    {
        _testDelegate = TargetF;
    }
    void Do(TestDelegate del)
    {
        del();
    }
    void TargetF()
    {
        Debug.Log("TargetF");
    }
}
  • 리턴 값delegate로 받는 예시
public class DelegateTest3 : MonoBehaviour
{
    public delegate void DelegateTest();
    public DelegateTest _testDelegate;
    private void Start()
    {
        DelegateTest result = Do();
    }
    DelegateTest Do()
    {
        return _testDelegate = TargetF;
    }
    void TargetF()
    {
        Debug.Log("TargetF");
    }
}

델리게이트 콜백

  • 콜백(Call Back): 함수를 참조한 후, 나중에 호출하는 것
    • 호출을 뒤에 하고, 참조 및 수정을 먼저하는 것을 의미
  • 델리게이트와 콜백을 결합하면, 불필요한 코드 작성을 방지할 수 있음
  • 의존적이지 않고, 유연한 코드 작성이 가능
  • 의존적인 코드
class Player
{
    public enum Buff { None, Buff1, Buff2, }
    public Buff _buff;
    public void BuffCheck(Buff buff)
    {
        if (buff == Buff.Buff1) NoneBuff();
        else
        {
            if (buff == Buff.Buff1) Buff1();
            if (buff == Buff.Buff1) Buff2();
        }
    }
    public void Attack(Buff buff)
    {
        BuffCheck(buff);
        Debug.Log("Attack");
    }
    void NoneBuff() { }
    void Buff1() { Debug.Log("Buff1"); } //버프식 계산 함수1
    void Buff2() { Debug.Log("Buff2"); } //버프식 계산 함수1
}
private void Start()
{
    Player player = new Player();
    player._buff = Player.Buff.Buff1;
    player.Attack(player._buff);
}
  • 델리게이트를 사용한 코드
class Player
    {
        private delegate void BuffDelegate();
        private BuffDelegate _buffDelegate;
        public enum Buff { None, Buff1, Buff2, }
        private Buff _buff;
        //현재 걸린 버프에 따라 자동으로 호출 함수를 수정하기 위한 프로퍼티 내 델리게이트 수정 조치
        public Buff _Buff {
            get { return _buff; }
            set
            {
                if (_buff == value) return;
                _buff = value;
                if (_buff == Buff.Buff1) _buffDelegate = Buff1;
                else if (_buff == Buff.Buff2) _buffDelegate = Buff2;
                else if (_buff == Buff.None) _buffDelegate = NoneBuff;
            }
        }
        public void Attack()
        {
            _buffDelegate();//또는 _buffDelegate.Invoke();
            Debug.Log("Attack");
        }
        void NoneBuff() { }
        void Buff1() { Debug.Log("Buff1"); } //버프식 계산 함수1
        void Buff2() { Debug.Log("Buff2"); } //버프식 계산 함수1
    }
    private void Start()
    {
        Player player = new Player();
        player._Buff = Player.Buff.Buff1;
        player.Attack();
    }

델리게이트 체인 : Delegate Chain

  • 하나의 델리게이트여러 함수동시에 참조 가능
  • 델리게이트 연산 규칙
    • 연산(덧셈/뺄셈)은 항상 에서 발생한다.
    • 델리게이트로 들어오는 함수 객체(Pred)에는 순서존재
    • 동일한 함수라도 다른 함수로 처리된다.
  • 예시
    del = A + A + B + C + A + B
    (출력): A A B C A B
    del - A - B - A
    (출력): A + B + C
  • Delegate.Combine(Delegate1, Delegate2)을 사용하는 구버전(사용X)
  • [방법4]를 애용하자

0. 델리게이트 대입(=)

  • 델리게이트에 특정 함수 포인터를 할당한다.
  • [주의] 기존에 연결된 델리게이트들을 없앨 수 있으므로 주의하자.
public delegate void TestDelegate();
private TestDelegate testDel;
testDel += Test1;
testDel += Test2;
testDel += Test3;
testDel = Test1;//기존에 연결된 Test1 -> Test2 -> Test3 델리게이트 체인은 없어지고 Test1 함수 포인터만 남음

1. 델리게이트 덧셈(+)

    • A 연결 -> B 연결 -> C 연결된 Delegate 실행 시
    • A 실행 -> B 실행 -> C 실행
public delegate void TestDelegate();
private TestDelegate _testDelegate;
void Chain1() { Debug.Log("Chain1"); }
void Chain2() { Debug.Log("Chain2"); }
void Chain3() { Debug.Log("Chain3"); }

private void Start()
{
    //방법1
    TestDelegate test1 = new TestDelegate(Chain1);
    TestDelegate test2 = new TestDelegate(Chain2);
    TestDelegate test3 = new TestDelegate(Chain3);

    _testDelegate = Delegate.Combine(test1, test2) as TestDelegate;
    _testDelegate = Delegate.Combine(_testDelegate, test3) as TestDelegate;
    _testDelegate.Invoke();

    _testDelegate += Chain1;
    _testDelegate += Chain2;
    _testDelegate += Chain3;

    //방법2
    _testDelegate = new TestDelegate(Chain1) 
                  + new TestDelegate(Chain2) 
                  + new TestDelegate(Chain3);
    _testDelegate.Invoke();

    //방법3
    _testDelegate += new TestDelegate(Chain1);
    _testDelegate += new TestDelegate(Chain2);
    _testDelegate += new TestDelegate(Chain3);
    _testDelegate.Invoke();

    //방법4(C# 2.0버전 : 가장추천)
    _testDelegate += Chain1;
    _testDelegate += Chain2;
    _testDelegate += Chain3;
    _testDelegate.Invoke();
}

1. 델리게이트 뺄셈(-)

    • A 연결 -> B 연결 -> C 연결된 Delegate에 뺄샘 수행 시
    • C 제거 -> B 제거 -> A 제거
private delegate void TestDelegate();
private TestDelegate testDelegate;
void Chain1() { Debug.Log("Chain1"); }
void Chain2() { Debug.Log("Chain2"); }
void Chain3() { Debug.Log("Chain3"); }

private void Start()
{
    testDelegate = Chain1;
    testDelegate += Chain2;
    testDelegate += Chain3;

    testDelegate -= Chain2;
    testDelegate -= Chain3;

    testDelegate.Invoke();
}

델리게이트 실전 사용 예시

  • 싱글톤을 활용한 참조
    • 장점: 직관적
    • 단점: 코드간 의존성이 높아지고, 코드가 길어진다.
IEnumerator CoDie()
{
    isDead = true;
    anim.SetInteger("Stat", 2); // 사망
    //적이 죽었을 때, 
    DelegateUIManager.instance.AddScore(transform.position);//스코어 증가
    DelegateItemManager.instance.DropItem(transform.position);//아이템 드랍
    DelegateParticle.instance.PlayDieParticle(transform.position);//파티클 재생
    //죽음 효과음 추가
    //적 동료 생성
    //...
    //죽었을 때 효과가 늘어나면, CoDie 코드가 계속 길어진다.
    //결론: 싱글톤 클래스의 의존성이 커짐(하나가 바뀌면 전부 영향을 받게 됨)
    yield return new WaitForSeconds(0.5f);
    Destroy(gameObject);
}
  • 델리게이트를 활용한 참조
    • 장점: 코드간 의존성낮아지고, 코드가 짧고, 간결해짐
    • 단점: 코드 흐름을 파악하기 어려워짐
IEnumerator CoDie()
{
    isDead = true;
    anim.SetInteger("Stat", 2); // 사망
    //Delegate 변환
    dieDelegate(transform.position);
    yield return new WaitForSeconds(0.5f);
    Destroy(gameObject);
}
  • DelegateItemManager.cs
    • Delegate 연산(+)을 사용해 체인 연결
    private void Start()
    {
        if(instance == null)
        {
            instance = this;
        }
        else
        {
            Destroy(instance);
        }
        #region
        AddDelegate();
        #endregion
    }
    #region
    public void AddDelegate()
    {
        enemy.dieDelegate += DropItem;
    }
    #endregion
  • DelegateUIManager.cs
    private void Start()
    {
        if (instance == null)
        {
            instance = this;
        }
        else
        {
            Destroy(instance);
        }
        num = 0;
        text.text = num.ToString();
        #region
        AddDelegate();
        #endregion
    }
    #region
    public void AddDelegate()
    {
        enemy.dieDelegate += AddScore;
    }
    #endregion
  • DelegateParticle.cs
    private void Start()
    {
        if(instance == null)
        {
            instance = this;
        }
        else Destroy(instance);
        #region
        AddDelegate();
        #endregion
    }
    #region
    public void AddDelegate()
    {
        enemy.dieDelegate += PlayDieParticle;
    }
    #endregion
    public void PlayDieParticle(Vector3 pos)
    {
        particle.transform.position = pos;
        particle.Play();
    }

델리게이트 이벤트 : Delegate Event

  • 이벤트(Event): 사건이 발생했음을 알리는 것 or 용도
  • event 키워드로 델리게이트 선언
public delegate void TestDelegate();
public event TestDelegate testEvent;//델리게이트의 이벤트화
  • 외부에서 대입 불가
  • 외부에서 호출 불가
  • 내부델리게이트 대입문을 포함해 제작
  • 내부델리게이트 호출 함수를 포함해 제작
public class TestDelegate{
	public delegate TestEvent();
    public event TestEvent testEvent;
    public void StartEvent(){
    	testEvent = Test;
    	testEvent.Invoke();
    }
}
public class EvternalClass{
	private void Start(){
    	TestDelegate testDelegate = new TestDelegate();
    	testDelegate.StartEvent();
		public void Test() { Debug.Log("Test"); }        
	}
}

이벤트가 필요한 이유

  • 이벤트외부에서 델리게이트 사용불가능하게 만든다.
  • 이는 객체의 삭제를 다룰 때, 본인이 삭제해야지, 타인이 삭제하면 문제가 발생할 수 있기 때문
  • 즉, 객체 삭제 = 본인 안
profile
GAME DESIGN & CLIENT PROGRAMMING

0개의 댓글