매크로 생성 및 간단한 사용 예
매크로의 입출력 실행 핀을 정의해 반복되는 노드 흐름을 묶고 함수와 매크로의 선택 기준을 이해합니다.
앞에서 반복되는 로직을 함수로 묶어 재사용했습니다. 그런데 함수로는 묶기 어려운 노드 조합도 있습니다. 예를 들어 참조가 유효하면 이쪽, 유효하지 않으면 저쪽으로 실행을 나누는 패턴은 실행 출력이 두 개 필요한데, 실행 핀이 있는 함수 호출 노드에는 실행 출력이 하나뿐입니다. 이럴 때 쓰는 또 하나의 재사용 단위가 매크로(macro)입니다.
매크로는 여러 노드를 하나의 블록으로 묶어 그래프를 깔끔하게 만든다는 점에서 함수와 비슷하지만, 동작하는 방식이 다릅니다. 함수는 별도의 작업 단위로 남아 호출될 때마다 그곳으로 실행이 넘어가는 반면, 매크로는 컴파일할 때 그 안의 노드들이 사용한 자리마다 그대로 펼쳐집니다. 틀에 맞춰 같은 모양을 찍어 내는 템플릿처럼 동작한다고 생각하면 됩니다. 이 글에서는 디버그 메시지를 출력하는 작은 매크로를 만들어 보고, 함수와 매크로 가운데 무엇을 고를지 기준을 정리합니다.
노드 묶음을 펼쳐 넣는 매크로
공식 문서는 매크로를 "노드를 접어 둔 그래프(collapsed graph)와 사실상 같은 것"으로 설명합니다. 여러 노드를 하나로 접어 놓고 이름을 붙인 그래프 조각이며, 함수처럼 입력과 출력 핀을 가질 수 있습니다. 매크로의 입구와 출구는 터널(tunnel) 노드라는 특별한 노드가 맡습니다.
매크로가 함수와 가장 크게 다른 점은 실행 핀입니다. 공식 문서에 따르면 매크로는 함수와 달리 실행 출력 핀을 둘 이상 가질 수 있고, 실행 입력도 여러 개 만들 수 있습니다. 반대로 실행 핀 없이 데이터만 다루는 매크로도 만들 수 있습니다. 그래서 흐름 제어 노드를 특정한 패턴으로 묶어 재사용하는 데 알맞습니다. 사실 앞 글에서 본 For Loop나 Do Once도 엔진에 기본으로 들어 있는 매크로입니다.
PrintDebugMessage 매크로 만들기
매크로는 함수와 거의 같은 방법으로 만듭니다. 마이 블루프린트(My Blueprint) 패널의 Macros 묶음 옆 + 버튼을 누르고 이름을 PrintDebugMessage로 입력하면, 매크로를 정의하는 그래프가 새 탭으로 열립니다. 이 그래프에는 매크로의 입구인 Inputs 터널 노드와 출구인 Outputs 터널 노드가 놓여 있습니다.
다음으로 입력과 출력을 정합니다. Inputs 터널 노드를 선택하고 디테일 패널의 Inputs 항목에서 새 입력을 세 개 추가합니다. 실행 입력인 In은 타입을 Exec로, Message는 String으로, Duration은 Float로 정합니다. 이어서 Outputs 터널 노드를 선택하고 실행 출력 Out을 Exec 타입으로 추가합니다. 이렇게 입력과 출력을 바꾸면 터널 노드의 핀이 자동으로 따라 바뀝니다. 함수와 달리 실행 핀도 직접 타입을 골라 추가한다는 점이 다릅니다.
이제 매크로의 내용을 채웁니다. 그래프에 Print String을 놓고, Inputs의 In 실행 핀을 Print String의 실행 입력에 잇습니다. Inputs의 Message 핀은 Print String의 In String에, Duration 핀은 Print String의 Duration에 연결합니다. 마지막으로 Print String의 출력 실행 핀을 Outputs의 Out에 이은 뒤 컴파일하고 저장합니다.
이벤트 그래프에서 매크로 쓰기
이벤트 그래프로 돌아와 빈 곳을 오른쪽 클릭하고 PrintDebugMessage를 검색해 매크로 노드를 놓습니다. 마이 블루프린트의 Macros 목록에서 그래프로 끌어다 놓아도 됩니다. Event BeginPlay의 실행 핀을 매크로 노드의 In에 잇고, Message에는 매크로를 이용한 메시지!를, Duration에는 5.0을 입력합니다. 컴파일하고 저장한 뒤 플레이하면 화면에 매크로를 이용한 메시지!가 5초 동안 표시됩니다.
이벤트 그래프에는 PrintDebugMessage 노드 하나만 보이지만, 컴파일된 결과에서는 그 자리에 매크로 안의 Print String이 펼쳐져 들어갑니다. 같은 매크로를 다섯 곳에서 쓰면 다섯 곳에 각각 펼쳐지는 셈입니다. 이 덕분에 매크로 안의 노드는 사용한 그래프의 일부처럼 실행되고, 이벤트 그래프에서 쓴 매크로라면 그 안에 Delay 같은 잠재 노드를 넣을 수도 있습니다. 대신 매크로를 고치면 사용한 모든 곳이 다시 컴파일되어야 반영됩니다.
PrintDebugMessage는 매크로를 만드는 방법을 익히려는 예제입니다. 실행 입력과 출력이 하나씩뿐인 이 기능은 함수로 만들어도 똑같이 동작합니다. 또 Duration은 메시지를 화면에 표시하는 시간일 뿐, 다음 노드의 실행을 그 시간만큼 멈추게 하는 값이 아닙니다.
함수와 매크로 가운데 고르기
함수와 매크로는 둘 다 재사용을 위한 도구이지만, 위에서 본 차이 때문에 알맞은 자리가 다릅니다.
FUNCTION · MACRO · REUSE
호출 경계가 필요하면 함수, 실행 갈래 재사용은 매크로
매크로는 호출 위치에 그래프를 펼치고 함수는 호출 경계를 유지합니다. 노드를 묶는다는 공통점보다 실행 핀·대기·호출 대상의 조건으로 선택합니다.
| 기준 | 함수 | 매크로 |
|---|---|---|
| 컴파일 | 별도 함수로 호출합니다. | 호출 위치마다 내부 그래프를 펼칩니다. |
| 실행 핀 | Impure 호출은 한 실행 입력·출력, Pure 호출은 실행 핀이 없습니다. | 터널에 여러 실행 입력·출력을 만들 수 있습니다. |
| 임시 값 | 이름 있는 지역 변수를 함수 범위에서 사용합니다. | 익명 지역 값도 사용할 수 있습니다. 모든 값이 멤버 변수인 것은 아닙니다. |
| 대기·재귀 | Delay 같은 잠재 노드는 넣지 않으며, 종료 조건을 둔 재귀 호출은 가능합니다. | 허용된 문맥에서는 잠재 노드를 사용할 수 있지만, 자기 자신을 펼치는 재귀는 허용하지 않습니다. |
| 디버깅 | 함수 호출 경계가 Call Stack에 나타납니다. | 별도 매크로 프레임 없이 호출한 함수의 일부로 나타납니다. |
| 선택 기준 | 점수 변경·계산 같은 책임, 외부 호출이나 자식의 재정의가 필요할 때. | 여러 실행 갈래와 작은 그래프 패턴을 재사용할 때. |
- 기준: 컴파일
- 함수: 별도 함수로 호출합니다.
- 매크로: 호출 위치마다 내부 그래프를 펼칩니다.
- 기준: 실행 핀
- 함수: Impure 호출은 한 실행 입력·출력, Pure 호출은 실행 핀이 없습니다.
- 매크로: 터널에 여러 실행 입력·출력을 만들 수 있습니다.
- 기준: 임시 값
- 함수: 이름 있는 지역 변수를 함수 범위에서 사용합니다.
- 매크로: 익명 지역 값도 사용할 수 있습니다. 모든 값이 멤버 변수인 것은 아닙니다.
- 기준: 대기·재귀
- 함수: Delay 같은 잠재 노드는 넣지 않으며, 종료 조건을 둔 재귀 호출은 가능합니다.
- 매크로: 허용된 문맥에서는 잠재 노드를 사용할 수 있지만, 자기 자신을 펼치는 재귀는 허용하지 않습니다.
- 기준: 디버깅
- 함수: 함수 호출 경계가 Call Stack에 나타납니다.
- 매크로: 별도 매크로 프레임 없이 호출한 함수의 일부로 나타납니다.
- 기준: 선택 기준
- 함수: 점수 변경·계산 같은 책임, 외부 호출이나 자식의 재정의가 필요할 때.
- 매크로: 여러 실행 갈래와 작은 그래프 패턴을 재사용할 때.
본문의 PrintDebugMessage 예제
실행 입력·출력이 각각 하나이므로 함수로도 구현할 수 있습니다. Duration=5는 화면 표시 시간이고 다음 실행을 5초 기다리게 하는 값은 아닙니다.
위 그림은 함수와 매크로를 다섯 가지 기준으로 비교합니다. 컴파일 방식에서 함수는 별도의 함수로 남아 호출되고, 매크로는 사용한 위치마다 내부 그래프를 펼칩니다. 실행 핀은 함수의 경우 비순수 호출이 실행 입력과 출력을 하나씩, 순수 호출은 실행 핀이 없지만, 매크로는 터널에 실행 입력과 출력을 여러 개 만들 수 있습니다. 임시 값은 함수가 이름 있는 지역 변수를 함수 범위에서 쓰는 데 비해, 매크로에서는 이름 없는 지역 값도 쓸 수 있으며 매크로가 쓰는 값이 모두 블루프린트의 멤버 변수인 것은 아닙니다.
대기와 재귀에서는 둘이 반대입니다. 함수에는 Delay 같은 잠재 노드를 넣을 수 없지만, 함수가 자기 자신을 다시 호출하는 재귀 호출은 종료 조건을 둔다면 할 수 있습니다. 매크로는 허용된 문맥에서 잠재 노드를 쓸 수 있지만 자기 자신을 펼치는 재귀는 할 수 없습니다. 펼치는 방식이라 자기 자신을 넣으면 끝없이 펼쳐지기 때문입니다. 디버깅할 때는 함수 호출이 호출 스택(Call Stack)에 따로 나타나는 반면, 매크로는 별도의 단계 없이 그것을 사용한 그래프의 일부로 나타납니다. 그림 아래 상자는 본문의 PrintDebugMessage가 실행 입력과 출력이 하나씩이라 함수로도 만들 수 있고, Duration이 다음 실행을 기다리게 하는 값이 아니라는 점을 다시 짚습니다.
그림의 마지막 기준인 선택 기준을 문장으로 풀면 이렇습니다. 점수 변경이나 대미지 계산처럼 한 가지 일을 맡아 깔끔하게 묶고 싶을 때, 다른 블루프린트에서 호출하거나 자식 블루프린트에서 재정의해야 할 때, 재귀나 지역 변수가 필요할 때는 함수를 씁니다. 반대로 참조가 유효한지에 따라 Valid와 Invalid 두 갈래로 실행을 나누는 것처럼 여러 실행 갈래가 필요할 때, 또는 Sequence나 Branch, 반복 노드를 특정한 모양으로 묶은 작은 그래프 패턴을 되풀이할 때는 매크로가 알맞습니다.
반대로 체력을 0 이상으로 제한하는 계산처럼 값을 하나 받아 하나를 돌려주는 일은 함수로 충분하므로, 그런 기능 때문에 매크로를 만들 필요는 없습니다. 또 블루프린트 안에 만든 매크로는 그 블루프린트에서만 쓸 수 있습니다. 여러 블루프린트에서 같은 매크로를 쓰려면 매크로 라이브러리(Blueprint Macro Library) 에셋에 모아 둡니다.
처음에는 둘의 구분이 모호하게 느껴질 수 있습니다. 일단 함수로 만들어 보고, 실행 출력이 둘 이상 필요하거나 잠재 노드를 묶어야 할 때 매크로를 떠올리면 크게 틀리지 않습니다.
정리
- 매크로는 여러 노드를 접어 둔 그래프 조각으로, 컴파일할 때 사용한 자리마다 내부 노드가 펼쳐집니다.
- 매크로의 입구와 출구는
Inputs와Outputs터널 노드이며, 디테일 패널에서Exec타입을 포함한 입력과 출력을 추가합니다. - 매크로는 실행 입력과 출력을 여러 개 가질 수 있고, 허용된 문맥에서
Delay같은 잠재 노드를 쓸 수 있지만 재귀는 할 수 없습니다. - 한 가지 일을 묶거나 외부 호출, 재정의, 재귀가 필요하면 함수를, 여러 실행 갈래나 흐름 제어 패턴을 재사용할 때는 매크로를 씁니다.
- 블루프린트 안의 매크로는 그 블루프린트에서만 쓰며, 여러 블루프린트가 함께 쓰려면 매크로 라이브러리에 둡니다.
연습 문제
1. 표시 시간과 실행 대기 구분하기
Duration을 5.0으로 정한 PrintDebugMessage의 Out을 다른 Print String에 바로 연결했습니다. 두 노드 사이에 Delay나 비동기 작업은 없습니다. 두 번째 Print String은 첫 메시지 표시가 끝나는 5초 뒤에 실행될까요?
해설 보기
5초를 기다리지 않고 바로 실행됩니다. 이 매크로는 안에서 Print String을 실행한 뒤 곧바로 Out으로 이어지며, Duration은 메시지를 화면에 보여 주는 시간일 뿐입니다. 두 번째 출력도 같은 실행 경로를 따라 이어서 호출됩니다.
5초 뒤에 다음 동작을 하고 싶다면 Delay처럼 실제로 기다리는 노드를 잠재 노드가 허용되는 이벤트 그래프의 흐름에 넣어야 합니다. 화면 표시 옵션과 실행 지연은 서로 다른 설정입니다.
2. 실행 출력이 두 개 필요할 때
오브젝트 참조를 받아, 유효하면 Valid 출력으로, 유효하지 않으면 Invalid 출력으로 실행을 보내는 재사용 단위를 만들고 싶습니다. 함수와 매크로 가운데 무엇으로 만들어야 할까요?
해설 보기
매크로로 만들어야 합니다. 실행 핀이 있는 함수 호출 노드에는 실행 출력이 하나뿐이라서, 결과에 따라 두 갈래로 실행을 나눌 수 없습니다.
매크로라면 Outputs 터널에 Valid와 Invalid 두 실행 출력을 만들고, 안에서 Is Valid의 결과를 Branch로 나누어 각 출력에 이으면 됩니다. 엔진에도 이와 같은 역할의 Is Valid 매크로 노드가 기본으로 들어 있습니다.
3. 매크로로 바꾸면 안 되는 경우
체력을 받아 0보다 작으면 0을, 아니면 그대로 돌려주는 ClampHealth 함수가 있습니다. 이 기능을 자식 블루프린트에서 다른 방식으로 재정의할 계획도 있습니다. 이것을 매크로로 바꾸는 것이 좋을까요?
해설 보기
함수로 두는 편이 좋습니다. 값 하나를 받아 하나를 돌려주는 계산이라 여러 실행 출력이 필요하지 않고, 함수로 충분히 깔끔하게 표현됩니다.
또 자식 블루프린트에서 재정의하려면 함수여야 합니다. 매크로는 사용한 자리에 펼쳐지는 그래프 조각이라 자식 블루프린트가 재정의할 수 없습니다. 재정의는 블루프린트에서의 상속 기본 개념에서 다룹니다.
매크로를 만들고 입출력을 정하는 방법은 언리얼 엔진 공식 문서의 매크로 설명에서 확인할 수 있습니다.
이것으로 2장을 마칩니다. 2장에서는 값을 담는 변수의 타입과 Get, Set 노드에서 시작해, 노드를 묶어 재사용하는 함수, 실행을 시작하는 이벤트, 실행을 나누고 되풀이하는 흐름 제어 노드, 그리고 실행 핀을 여러 개 가질 수 있는 매크로까지 블루프린트 그래프를 이루는 기본 요소를 익혔습니다. 다음 장의 액터 블루프린트의 기본 개념부터는 액터와 컴포넌트, 상속과 인터페이스처럼 블루프린트를 클래스로 설계하는 방법을 다룹니다.