개발블로그
[유니티 C#] Action 과 Func 본문
💪 GOAL
https://devheehee.tistory.com/21
지난 포스팅에서 Delegate의 사용 방법을 알아보았다.
지난 시간에 이어서 C#에서 Delegate를 좀 더 간단히 사용할 수 있도록 미리 선언한 Delegate 변수인
Action과 Func의 차이점을 알아보자.
- C#에서의 델리게이트 기능을 손쉽게 사용할 수 있도록 지원하는 Action과 Func에 대해 이해한다.
🔶 Action
Action 타입은 입력과 출력이 없는 메소드를 가리킬 수 있는 델리게이트다.
입력과 출력이 없는 메소드를 등록할 수 있고, 등록된 메소드는 원하는 시점에 매번 실행할 수 있다.
예제 코드를 보자.
public class Monster : Monobehaviour
{
Action attack;
void Start()
{
attack += Punch;
attack += Kick;
}
void Update()
{
if(Input.GetMouseButtonDown(0))
{
attack();
}
}
void Punch()
{
Debug.Log("Punch!");
}
void Kick()
{
Debug.Log("Kick!");
}
}
Action 타입의 변수에는 += 를 사용해서 메소드를 등록할 수 있다.
이 예제 코드는 마우스 왼쪽 버튼을 누를 때마다 attack(); 을 실행하게 된다.
attack(); 이 실행되면 attack에 등록된 Punch()와 Kick() 이 실행되어 'Punch!' 와 'Kick!' 로그가 순서대로 출력된다.
🔶 Action과 Func, 그리고 그 둘의 차이점
✔ Action<T>
Action<T> 에서 T자리에 n개의 type을 입력하면 그 type들이 전송가능한 매개변수로 자동 인식된다.
public delegate void Action(); // 매개변수가 없으며 값을 반환하지 않는 메소드를 캡슐화
public delegate void Action<in T>(T obj);
public delegate void Action<in T1, in T2>(T1 arg1, T2 arg2);
.
.
.
public delegate void Action<in T1,in T2,in T3,in T4,in T5,in T6,in T7,in T8,in T9,in T10,in T11,in T12,in T13,in T14,in T15,in T16>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16);
Action은 위와 같은 선언부가 숨어있다.
매개변수가 16개까지 미리 선언되어 있어서 리턴값이 없는 델리게이트 사용이 필요할 때 Action을 통해 기능을 사용할 수 있다.
예제코드를 보자.
using UnityEngine;
using System;
public class Study_Action : MonoBehaviour
{
Action<int, float> _action; // int와 float을 매개변수로 받고, 리턴값이 없는 이벤트
void Start()
{
_action += PrintAction;
_action?.Invoke(1, 1.0f);
}
void PrintAction(int a, float b)
{
Debug.Log("int a = " + a + " float b = " + b + "f" );
}
}
1. Action을 선언한 뒤 매개변수로 int와 float을 받은 _action을 선언했다.
2. 스크립트가 실행되면 _action 대리자에 PrintAction 함수를 넣어준다. (델리게이트 체인)
3. _action 대리자가 null이면 null을 반환하고 null이 아니면 대리자 내부의 메소드를 실행한다.
매개변수 int와 float을 넣어준다.
PrintAction()을 _action 대리자에 체인해주었으므로
_action?.Invoke(1, 1.0f); 를 실행하면 _action이 캡슐화하고있는 메소드를 실행한다.
✔ Func<TResult>
Func<TResult>는 리턴값이 필요한 Event이다.
Func<TResult> 에서 TResult는 반환값이 들어간다.
만약 매개변수가 있다면 Func<T1, TResult>의 형식으로 마지막 파라미터는 반환값의 type을 넣어줘야 한다.
public delegate TResult Func<out TResult>(); // 매개변수가 없으며 값을 반환하는 메소드를 캡슐화
public delegate TResult Func<in T, out TResult>(T arg);
.
.
.
public delegate TResult Func<in T1,in T2,in T3,in T4,in T5,in T6,in T7,in T8,in T9,in T10,in T11,in T12,in T13,in T14,in T15,in T16,out TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16);
Func는 위와 같은 선언부가 숨어있다.
Action과 마찬가지로 매개변수가 16개까지 미리 선언되어있고, 마지막 파라미터는 반환 값의 type을 넣어준다.
형식 매개변수 T
- 매개 변수 형식으로 이 형식 매개변수이다.
매개변수 obj
- 이 대리자로 캡슐화 되는 메소드의 매개변수.
형식 매개변수 TResult (리턴값이 있는 Func 에서 사용)
- 이 대리자로 캡슐화 되는 메소드의 반환 값 형식으로 이 형식 매개변수이다.
* Action 예제
메인화면에 기본 자동차를 띄우고 Action을 이용해서 리스트에 나타난 자동차슬롯을 누르면 로그를 출력하도록 작성했다.
메인화면에서는 기본 자동차가 회전하고 Select 버튼을 누르면 자동차 리스트가 나타난다.
자동차 리스트에서 슬롯을 선택하면 "input change car" 로그가 출력된다.
코드를 보자.
CarType.cs
public enum ECarType
{
None,
BlueCar,
GreenCar,
Hiace,
RedBus,
BlueTruck,
TukTuk,
YellowBus,
Max,
}
실무에서는 엑셀에 데이터파일을 작성해서 파싱해 사용하지만 이번 예제에서는 그렇게 하지않고 자동차 종류를 enum문에 담았다.
여기서 None과 Max는 자동차의 종류는 아니지만
enum문 초기화를 위해서 None을 추가했고, for문을 돌리기 위해서 Max 를 추가했다. 가시성을 위함이다.
Panel_Main.cs
using UnityEngine;
public class Panel_Main : MonoBehaviour
{
[SerializeField] GameObject _panelSelectCar = null;
[SerializeField] Transform _carParent = null;
GameObject _gbjCar = null;
void Start()
{
Init();
}
void Update()
{
// Rotate Car
_gbjCar.transform.Rotate(new Vector3(0f, 10f, 0f) * Time.deltaTime);
}
void Init()
{
// Default Car
_gbjCar = Instantiate((GameObject)Resources.Load("Cars/BlueCar"), _carParent);
}
public void OnBtnSelectCar()
{
_panelSelectCar.SetActive(true);
_panelSelectCar.GetComponent<Panel_SelectCar>().Init(ChangeCar);
_gbjCar.SetActive(false);
}
// 로그 출력
public void ChangeCar(ECarType eType)
{
//change car
Debug.Log("input change car");
}
}
처음에 뜨는 Main 화면에서는 기본 자동차(BlueCar)를 생성하고, Update문에서 계속 회전시킨다.
Panel_SelectCar.cs
using System;
using UnityEngine;
using System.Collections.Generic;
public class Panel_SelectCar : MonoBehaviour
{
[SerializeField] Transform _slotParent = null;
[SerializeField] List<GameObject> _carList = new List<GameObject>();
public void Init(Action<ECarType> onBtn)
{
for (int i = 0; i < _carList.Count; ++i)
{
var slot = Instantiate((GameObject)Resources.Load("Prefabs/Comp Slot"), _slotParent);
slot.GetComponent<Comp_CarSlot>().SetSlot((ECarType)i+1, onBtn);
}
}
}
자동차리스트를 보여주는 프리팹에 붙어있는 Panel_SelectCar 스크립트이다.
이 스크립트는 하이라키뷰를 보면 이해가 쉽다.
_slotParent에 Comp Slot 프리팹을 생성하기 위해서 선언하고,
자동차의 종류만큼 슬롯을 추가하기 위해 리스트 _carList를 선언했다.
Init() 에서는 리스트에 담긴 차의 종류만큼 for문을 돌면서 슬롯을 생성해준다.
슬롯을 생성한 후에는 Comp_CarSlot의 함수 SetSlot을 호출한다.
using System;
using UnityEngine;
using UnityEngine.UI;
public class Comp_CarSlot : MonoBehaviour
{
[SerializeField] Transform _carParent = null;
GameObject _gbjCar = null;
[SerializeField] Text _textName = null;
[SerializeField] Text _textExplain = null;
ECarType _carType = ECarType.None;
Action<ECarType> _onBtn;
public void SetSlot(ECarType carType, Action<ECarType> onBtn)
{
_onBtn = onBtn;
switch(carType)
{
case ECarType.BlueCar:
{
_gbjCar = Instantiate((GameObject)Resources.Load("Cars/BlueCar"), _carParent);
_textName.text = "Blue car";
_textExplain.text = "파란차 입니다.";
break;
}
case ECarType.GreenCar:
{
_gbjCar = Instantiate((GameObject)Resources.Load("Cars/GreenCar"), _carParent);
_textName.text = "Green car";
_textExplain.text = "초록차 입니다.";
break;
}
case ECarType.Hiace:
{
_gbjCar = Instantiate((GameObject)Resources.Load("Cars/Hiace"), _carParent);
_carParent.transform.localScale = new Vector3(35f, 35f, 35f);
_textName.text = "HI Ace";
_textExplain.text = "하이에이스 입니다.";
break;
}
case ECarType.RedBus:
{
_gbjCar = Instantiate((GameObject)Resources.Load("Cars/RedBus"), _carParent);
_carParent.transform.localScale = new Vector3(30f, 30f, 30f);
_textName.text = "Red Bus";
_textExplain.text = "빨간 버스 입니다.";
break;
}
case ECarType.BlueTruck:
{
_gbjCar = Instantiate((GameObject)Resources.Load("Cars/BlueTruck"), _carParent);
_carParent.transform.localScale = new Vector3(25f, 25f, 25f);
_textName.text = "Blue Truck";
_textExplain.text = "파란 트럭 입니다.";
break;
}
case ECarType.TukTuk:
{
_gbjCar = Instantiate((GameObject)Resources.Load("Cars/TukTuk"), _carParent);
_textName.text = "TUK TUK";
_textExplain.text = "툭툭 입니다.";
break;
}
case ECarType.YellowBus:
{
_gbjCar = Instantiate((GameObject)Resources.Load("Cars/YellowBus"), _carParent);
_carParent.transform.localScale = new Vector3(30f, 30f, 30f);
_textName.text = "Blue bus";
_textExplain.text = "파란 버스 입니다.";
break;
}
}
}
public void Onbtn()
{
// 의존성이 없음.
_onBtn?.Invoke(_carType);
}
}
SetSlot() 함수에서 switch-case 문으로 슬롯을 세팅해주고, 슬롯(버튼)을 클릭하면 ECarType 정보를 넘겨주기 위해서
Action<ECarType> _onBtn에 매개변수 onBtn을 대입해주었다.
슬롯을 클릭하면 Panel_SelectCar의 Init()에서 슬롯을 세팅과 초기화를 해주는 함수이다.
Init함수는 Panel_Main에서 Select버튼을 클릭했을 때 슬롯을 세팅해주고,
Panel_SelectCar에서 슬롯을 클릭하면 Panel_Main의 ChangeCar() 함수를 실행하며 콘솔에 로그를 출력한다.
* 공부하는 단계입니다. 잘못된 부분이 있다면 피드백 부탁드립니다😊
* e-mail : heehee970@naver.com
'Unity > 스크립팅' 카테고리의 다른 글
[유니티 C#] 유한 상태머신(Finite State Machine, FSM) (0) | 2021.10.13 |
---|---|
[유니티 C#] GetAxis, GetAxisRaw. 오브젝트가 바라보는 방향으로 이동하기 (0) | 2021.09.29 |
[유니티 C#] 유니티의 시간 마법사 Time.timeScale, 그리고 Time.unscaledTime (0) | 2021.08.11 |
[유니티 C#] Time.deltaTime, Application.targetFrameRate을 통한 프레임 제어 (0) | 2021.08.04 |
객체 지향 프로그래밍(OOP)에서의 클래스 개요. 변수와 프로퍼티의 차이 (0) | 2021.08.02 |