Unity/Tips

Unity delegate(대리자)와 Action 간단하지만 명확하게 + 예제

최애뎡 2022. 1. 2. 16:21
728x90
반응형

https://docs.microsoft.com/ko-kr/dotnet/csharp/programming-guide/delegates/

 

대리자 - C# 프로그래밍 가이드

C#의 대리자는 매개 변수 목록 및 반환 형식이 있는 메서드를 나타내는 형식입니다. 대리자는 메서드를 다른 메서드에 인수로 전달하는 데 사용됩니다.

docs.microsoft.com

Delegate (대리자)

- microsoft의 docs를 보면

delegate는 특정 매개 변수 목록 및 반환 형식이 있는 메서드에 대한 참조를 나타내는 형식이다.

delegate는 메서드를 다른 메서드에 인수로 전달하는 데 사용된다.

요런 내용으로 설명하고 있다.

 

코드로 예를 들면

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

public class Test : MonoBehaviour
{
    // delegate는 함수 자체를 인자로 넘길 수 있는 하나의 형식(위는 반환을 string)이라고 생각
    private delegate string stringDelegate();

    // 이렇게 매개변수도 받을 수 있음
    private delegate string stringDelegate2(string wow);

    /// <summary>
    /// 선언한 stringDelegate Delegate를 매개변수로 받아오고
    /// </summary>
    void DelegateTest(stringDelegate stringDG)
    {
        // string 형식으로 반환 하니까 받아서 log찍어서 확인
        string test = stringDG();
        Debug.Log(test);
    }

    string DebugTest()
    {
        Debug.Log("1");

        // return도 때릴 수 있음
        return "Hello World!!!";
    }

    string DebugTest2()
    {
        Debug.Log("2");
        return "Hello World~~~";
    }

    private void Start()
    {
        DelegateTest(DebugTest);

        // delegate를 선언하고
        stringDelegate ex = new stringDelegate(DebugTest);
        // 이렇게 한 메서드가 아니라 여러 메서드를 연결 할 수 있음
        ex += DebugTest2;

        DelegateTest(ex);
    }
}

요렇게 간단히 테스트해보면

이런 결과를 얻을 수 있음

ex로 선언한 delegate의 경우 DebugTest, DebugTest2를 연결했기 때문에 순차적으로 실행됨

그러나 로그를 보면

1 -> Hello World!!! -> 2 -> Hello World~~~가 아니라

1 -> 2 -> Hello World~~~가 떴는데 

이는 실행 순서가 DebugTest -> DebugTest2 -> DelegateTest() 순 이기 때문에 DelegateTest()에서는 DebugTest2의 반환 값인 Hello World~~~를 받았기 때문

 

--- 다음으로 ---

 

https://docs.microsoft.com/ko-kr/dotnet/api/system.action-1?view=net-6.0 

 

Action<T> 대리자 (System)

매개 변수가 하나이고 값을 반환하지 않는 메서드를 캡슐화합니다.Encapsulates a method that has a single parameter and does not return a value.

docs.microsoft.com

https://docs.unity3d.com/2022.1/Documentation/ScriptReference/Events.UnityAction.html

 

Unity - Scripting API: .UnityAction

Use this to create some dynamic functionality in your scripts. Unity Actions allow you to dynamically call multiple functions. Since Unity Actions have no arguments, functions they call must also have no arguments. See Delegates for more information.

docs.unity3d.com

Action의 경우는 microsoft와 unity의 docs를 다 보는 게 좋을 듯 (뭐 어차피 내용은 같음)

내용을 보면

- 매개 변수가 하나이고 값을 반환하지 않는 메서드를 캡슐화합니다.

- + System의 네임스페이스를 가짐 => using System;

- Unity Actions를 사용하면 여러 함수를 동적으로 호출할 수 있습니다.

- Unity Actions에는 인수가 없으므로 호출하는 함수에도 인수가 없어야 합니다.

요런 식으로 있음

[ * microsoft의 docs내용 중 위에 진하게 한 부분은 잘 이해가 안 가긴 하는데 음 .. 

정확하지는 않지만 매개변수도 없고 return type도 없는 게 맞는 걸로 알고 있음.. ]

 

코드로 예를 보면

using System;
using UnityEngine;

public class UpdateManager
{
    [Tooltip("Managers - Update에 돌릴 메서드 등록 위함")]
    public Action _update = null;

    [Tooltip("Managers - Update에 돌릴 메서드 등록(BgScroll)")]
    public Action _updateBgScroll = null;

    /// <summary>
    /// Managers - Update()
    /// </summary>
    public void OnUpdate()
    {
        if (_update != null)
            _update.Invoke();

        if (_updateBgScroll != null)
            _updateBgScroll.Invoke();
    }

    public void Clear()
    {
        _update = null;
        _updateBgScroll = null;
    }
}

개인 프로젝트에서 사용 중인 코드인데

(OnUpdate의 경우 Managers.cs의 Update()에서 돌고 있을 거임)

뭐 사실 Action을 하나로 두고 사용해도 되지만 가독성도 그렇고 기능을 확실히 분리해두는 게 좋다 생각하여 위딴 식으로 작성함

+

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

public class BgScroll : MonoBehaviour
{
    [Header("--- 미리 가지고 있어야 할 data ---")]
    [SerializeField, Tooltip("MeshRenderer")]
    MeshRenderer _mesh = null;

    [Header("--- 세팅 ---")]
    [SerializeField, Tooltip("배경 스크롤 스피드")]
    float _speed = 0;

    [Header("--- 참고용 ---")]
    [SerializeField, Tooltip("현재 배경의 Offset")]
    float _offset = 0;

    /// <summary>
    /// BgManager.cs에서 돌릴 BG를 Init
    /// Scroll - UpdateM - action 등록
    /// </summary>
    public void Init()
    {
        Managers.UpdateM._updateBgScroll -= Scroll;
        Managers.UpdateM._updateBgScroll += Scroll;
    }

    /// <summary>
    /// 비활성화 시 action 해제
    /// </summary>
    private void OnDisable()
    {
        if (Managers.Instance != null)
            Managers.UpdateM._updateBgScroll -= Scroll;
    }

    /// <summary>
    /// 배경 scroll
    /// </summary>
    void Scroll()
    {
        _offset += _speed * Time.deltaTime;
        _mesh.material.mainTextureOffset = new Vector2(_offset, 0);
    }
}

이런 식으로 (배경 스크롤 코드임)

Init()시에 Action에 등록 후 Scroll() 메서드를 열심히 굴려줌 (매개변수도 없고 return type도 없고)

 

* 참고로 delegate든 action이든 등록할 때 += 로 등록을 하는데 항상 -=를 한 뒤에 +=를 하는 습관을 들이는 게 좋음 +=를 했는데 이미 +=를 한 상태라면 두 번씩 호출될 수 있기 때문

반응형