사용자 정의 모듈
Module Script에 입출력 매개변수와 파티클 속성 계산을 정의해 여러 시스템에서 재사용할 수 있는 사용자 모듈을 만듭니다.
스폰과 업데이트, 렌더링 모듈만으로도 다양한 효과를 만들 수 있지만, 기본으로 제공하는 모듈로는 원하는 동작을 만들기 어려울 때가 있습니다. 입자가 수명에 맞춰 좌우로 흔들리며 올라가게 하고 싶은데 그런 모듈이 없다면, 여러 모듈을 억지로 겹쳐 비슷하게 흉내 내거나 직접 만들어야 합니다. 이럴 때 쓰는 것이 사용자 정의 모듈(Custom Module)입니다. 이 글에서는 입자의 X 속도를 수명에 따라 사인 곡선으로 바꾸는 모듈을 직접 만들고, 그 계산을 숫자로 따라가 본 뒤 이미터에 붙여 사용합니다.
직접 만드는 모듈
사용자 정의 모듈은 원하는 기능을 하도록 노드를 이어 직접 만든 나이아가라 모듈입니다. 블루프린트처럼 노드를 연결하는 시각적인 프로그래밍 방식이라, C++을 몰라도 파티클의 로직을 만들 수 있습니다. 만든 로직은 모듈 에셋으로 저장해 다른 나이아가라 시스템에서도 쓸 수 있습니다. 1장에서 블록에 비유했던 모듈을, 이번에는 블록 자체를 설계해 만드는 셈입니다.
사용자 정의 모듈을 쓰면 몇 가지 이점이 있습니다. 기본 모듈에 없는 독특한 동작이나 계산을 직접 만들 수 있고, 한 번 만든 모듈을 여러 이미터와 시스템에서 다시 쓸 수 있습니다. 또 여러 단계의 계산을 모듈 하나로 묶으면 이미터의 Particle Update 그룹이 짧고 읽기 쉬워집니다.
지그재그 모듈 만들기
여기서는 입자의 수명에 따라 X 방향으로 좌우 흔들림을 더하는 모듈을 만듭니다. 먼저 콘텐츠 브라우저(Content Browser)에서 모듈을 둘 폴더를 오른쪽 클릭하고 FX 메뉴에서 나이아가라 모듈 스크립트(Niagara Module Script)를 고릅니다. 버전에 따라 이 항목이 FX 바로 아래가 아니라 그 안의 하위 메뉴에 있을 수 있으므로, 보이지 않으면 메뉴 위쪽의 검색창에 Niagara Module을 입력해 찾습니다. 이름은 Niagara Custom Module의 약자를 붙여 NCM_ZigzagMovement처럼 정합니다. 만든 에셋을 더블 클릭하면 나이아가라 스크립트 에디터가 열리는데, 시스템을 편집하는 나이아가라 에디터와 비슷해 보이지만 모듈의 로직을 노드로 만드는 데 맞춰져 있습니다.
파라미터 맵과 입력
모듈 그래프에서는 값이 파라미터 맵(Parameter Map)을 따라 흐릅니다. 파라미터 맵은 입자의 속성과 모듈의 입력처럼 지금 쓸 수 있는 값을 모두 담고 Input Map에서 Output Module까지 이어지는 연결입니다. 값을 읽을 때는 Map Get 노드에서 지금 입자의 NormalizedAge와 Velocity, 그리고 모듈의 입력을 꺼내고, 값을 쓸 때는 Map Set 노드에서 Particles.Velocity에 결과를 넣습니다.
이 모듈에는 입력이 두 개 필요합니다. Float 입력 ZigzagStrength는 X 방향 속도의 진폭으로 단위는 cm/s이며 기본값은 0입니다. Float 입력 ZigzagFrequency는 입자 하나의 수명 동안 나아갈 위상을 라디안으로 나타내며 기본값은 6.283185(2π)입니다. 이 값은 초당 진동 횟수(Hz)가 아니라는 점에 주의해야 합니다. 입력은 쓰는 버전의 모듈 입력 네임스페이스에 만들며, 이렇게 만든 입력이 사용자 파라미터(User Parameters)로 저절로 노출되는 것은 아닙니다.
노드로 계산 만들기
계산은 다음 순서로 이어 붙입니다. 먼저 Map Get에서 Particles.NormalizedAge와 Particles.Velocity를 읽습니다. 다음으로 NormalizedAge에 ZigzagFrequency를 곱해 위상을 만들고, 라디안을 받는 Sine 노드로 그 사인 값을 구합니다. 그 결과에 ZigzagStrength를 곱하면 새 X 속도가 됩니다. 마지막으로 지금 Velocity의 Y와 Z는 그대로 두고 X만 새 값으로 바꾼 벡터를 만들어 Map Set의 Particles.Velocity에 연결합니다. 파라미터 맵은 Input Map에서 Map Set을 거쳐 Output Module로 이어지게 둡니다. 노드 연결을 식으로 적으면 다음과 같습니다.
phase = Particles.NormalizedAge * ZigzagFrequency
newX = sin(phase) * ZigzagStrength
Particles.Velocity = (newX, previousVelocity.y, previousVelocity.z)이 식은 노드 연결을 설명하는 의사 코드입니다. 예전 X에 틱마다 값을 더하면 점점 빨라지는 가속이 되므로, 이 예제에서는 X를 더하지 않고 덮어씁니다. 그래서 ZigzagStrength가 0이면 원래의 X를 그대로 두는 것이 아니라 X를 0으로 만듭니다. 이미터에 넣을 때는 나이를 갱신하는 Particle State 뒤, 위치를 계산하는 Solve Forces and Velocity 앞에 두어야 바뀐 속도가 위치에 반영됩니다. 초기 X 속도를 유지해야 하는 다른 효과라면 기준 속도를 따로 저장해 두고 어떻게 합칠지 별도로 설계해야 합니다.
계산을 다 만들었다면 Ctrl+S로 NCM_ZigzagMovement를 저장합니다.
수명에 따라 바뀌는 X 속도
한 수명 동안 X 속도가 사인 한 주기를 따릅니다
한 수명의 NormalizedAge 0/.25/.5/.75/1에서 X 속도 0/+100/0/-100/0 계산이 달라진다. Y/Z 보존·X 덮어쓰기·위치 곡선 아님·끝점 미관측 가능을 명시한다.
NIAGARA · CH3-5
한 수명에 X 속도가 사인 한 주기를 따릅니다
NormalizedAge × 2π를 라디안 위상으로 사용하고 Strength=100cm/s로 정한 계산 예입니다. 그래프는 위치 궤적이 아니라 새로 쓰는 X 속도입니다.
X만 교체하고 Y·Z는 보존합니다
newX = sin(NormalizedAge × 2π) × 100
이전 X에 값을 더하지 않고 계산한 X로 덮어씁니다. Strength가 0이면 X도 0이 됩니다.
2π는 한 수명에 한 주기라는 뜻입니다. Lifetime이 2초라면 한 주기도 2초이며, 2π Hz라는 뜻이 아닙니다.
본문 조건은 Particle State 뒤, 위치 적분 전 배치입니다. 다른 힘·Drag·Velocity 작성자는 끄고 기준 거동을 비교합니다. 끝점이 실제 소멸 프레임에 보인다는 보장은 없습니다.
이 예의 조건
- 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
- 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
| 범위 | 원문 |
|---|---|
| 본문·주석 | 한 수명 동안 X 속도가 사인 한 주기를 따릅니다 |
| 본문·주석 | NormalizedAge × 2π를 라디안 위상으로 사용하고 Strength=100 cm/s로 정한 계산 예입니다. 그래프는 위치 궤적이 아니라 새로 쓰는 X 속도입니다. |
| 관계·조건 설명 | 수명 진행률 0에서 0, 0.25에서 +100, 0.5에서 0, 0.75에서 -100, 1에서 0 cm/s입니다. Strength 100과 위상 2π를 사용한 수학적 속도 예입니다. |
| 관계·조건 설명 | 원 도표와 동일하게 수명 진행률 0/0.25/0.5/0.75/1에서 X 속도는 0/+100/0/-100/0 cm/s다. 곡선은 위치가 아닌 속도이며 Lifetime 끝점의 실제 출력을 보장하지 않는다. Strength 100, Frequency 2pi를 사용한다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 나이 초반 한 수명의 진행률로 X 속도를 읽는다. |
|
|
| 중간·후반 0.5와 0.75에서 방향 입력이 달라진다. |
|
|
| 끝점 1의 계산값은 0이지만 실제 관측은 설정에 달려 있다. |
|
|
한 수명 동안 X 속도가 사인 한 주기를 따릅니다
한 수명의 NormalizedAge 0/.25/.5/.75/1에서 X 속도 0/+100/0/-100/0 계산이 달라진다. Y/Z 보존·X 덮어쓰기·위치 곡선 아님·끝점 미관측 가능을 명시한다.
NIAGARA · CH3-5
한 수명에 X 속도가 사인 한 주기를 따릅니다
NormalizedAge × 2π를 라디안 위상으로 사용하고 Strength=100cm/s로 정한 계산 예입니다. 그래프는 위치 궤적이 아니라 새로 쓰는 X 속도입니다.
X만 교체하고 Y·Z는 보존합니다
newX = sin(NormalizedAge × 2π) × 100
이전 X에 값을 더하지 않고 계산한 X로 덮어씁니다. Strength가 0이면 X도 0이 됩니다.
2π는 한 수명에 한 주기라는 뜻입니다. Lifetime이 2초라면 한 주기도 2초이며, 2π Hz라는 뜻이 아닙니다.
본문 조건은 Particle State 뒤, 위치 적분 전 배치입니다. 다른 힘·Drag·Velocity 작성자는 끄고 기준 거동을 비교합니다. 끝점이 실제 소멸 프레임에 보인다는 보장은 없습니다.
이 예의 조건
- 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
- 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
| 범위 | 원문 |
|---|---|
| 본문·주석 | 한 수명 동안 X 속도가 사인 한 주기를 따릅니다 |
| 본문·주석 | NormalizedAge × 2π를 라디안 위상으로 사용하고 Strength=100 cm/s로 정한 계산 예입니다. 그래프는 위치 궤적이 아니라 새로 쓰는 X 속도입니다. |
| 관계·조건 설명 | 수명 진행률 0에서 0, 0.25에서 +100, 0.5에서 0, 0.75에서 -100, 1에서 0 cm/s입니다. Strength 100과 위상 2π를 사용한 수학적 속도 예입니다. |
| 관계·조건 설명 | 원 도표와 동일하게 수명 진행률 0/0.25/0.5/0.75/1에서 X 속도는 0/+100/0/-100/0 cm/s다. 곡선은 위치가 아닌 속도이며 Lifetime 끝점의 실제 출력을 보장하지 않는다. Strength 100, Frequency 2pi를 사용한다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 나이 초반 한 수명의 진행률로 X 속도를 읽는다. |
|
|
| 중간·후반 0.5와 0.75에서 방향 입력이 달라진다. |
|
|
| 끝점 1의 계산값은 0이지만 실제 관측은 설정에 달려 있다. |
|
|
위 그림은 ZigzagFrequency를 2π, ZigzagStrength를 100cm/s로 둔 계산 예입니다. 가로축은 수명 진행률인 NormalizedAge이고 세로축은 새로 쓰는 X 속도입니다. 진행률이 0일 때 X 속도는 0이고, 0.25에서 +100cm/s로 가장 크며, 0.5에서 다시 0, 0.75에서 -100cm/s로 가장 작고, 1에서 0으로 돌아옵니다. 즉 한 수명 동안 사인 곡선을 한 주기 그립니다. 이 그래프는 위치의 궤적이 아니라 속도이므로, 입자가 좌우로 ±100cm 움직인다는 뜻은 아닙니다.
그림의 오른쪽 설명은 이 모듈이 X만 바꾸고 Y와 Z는 그대로 둔다는 것과, 예전 X에 값을 더하지 않고 덮어쓰므로 Strength가 0이면 X도 0이 된다는 것을 짚습니다. 또 2π는 한 수명에 한 주기라는 뜻이어서, Lifetime이 2초라면 한 주기도 2초입니다. 그림 아래에는 이 계산이 Particle State 뒤, 위치 계산 전에 배치된 조건이며, 다른 힘이나 Drag, 속도를 쓰는 모듈은 끄고 비교한다는 조건이 적혀 있습니다. 진행률 1의 끝점이 실제로 입자가 사라지는 프레임에 보인다는 보장도 없습니다.
그림의 값을 숫자로 따라가 봅니다. ZigzagStrength가 100cm/s, ZigzagFrequency가 2π이고 수명이 2초로 고정이라고 합시다. 나이가 0.5초인 입자의 NormalizedAge는 0.5 ÷ 2 = 0.25이고, 위상은 0.25 × 2π = π/2이며, 그 사인 값은 1이므로 새 X 속도는 +100cm/s입니다. 나이가 1.5초라면 진행률 0.75에서 사인 값이 -1이므로 -100cm/s입니다. 이전 속도의 Y가 20, Z가 30이었다면 이 모듈을 지난 속도 벡터는 각각 (100, 20, 30)과 (-100, 20, 30)입니다. 이 계산은 X 속도의 부호가 바뀐다는 것이며, X 위치가 곧바로 ±100cm가 된다는 뜻은 아닙니다.
만든 모듈을 이미터에 붙이기
이제 만든 모듈을 시스템에 적용합니다. 앞에서 만든 NS_BasicSpawn이나 다른 나이아가라 시스템을 열고, 시스템 개요(System Overview)에서 움직임을 더할 이미터를 선택합니다. 그 이미터의 Particle Update 그룹에서 + 버튼을 누르고, 검색창에 NCM_ZigzagMovement를 입력해 찾아 추가합니다.
모듈을 추가하면 선택(Selection) 패널에 직접 정의한 ZigzagStrength와 ZigzagFrequency 입력이 나타납니다. 값을 바꿔 가며 미리보기에서 입자의 움직임이 어떻게 달라지는지 확인해 봅니다. ZigzagStrength는 좌우 속도의 진폭이므로 실제로 좌우로 움직이는 폭은 위상이 나아가는 속도와 Lifetime에도 달려 있습니다. 처음에는 다른 힘과 Drag, 속도를 쓰는 모듈을 끄고 이 모듈만의 기본 움직임을 먼저 비교합니다.
검색해도 모듈이 보이지 않는다면 모듈 스크립트의 설정을 확인합니다. 모듈의 사용 범위(Module Usage)에 Particle Update가 들어 있어야 그 그룹의 목록에 나타나고, 라이브러리 노출(Library Visibility) 설정에 따라 검색 결과에 보이는지가 달라집니다. 카테고리와 설명도 채워 두면 다른 사람이 모듈을 찾고 이해하기 쉽습니다.
모듈을 만들 때 챙길 것
그래프가 복잡해지면 C 키로 주석 상자를 만들어 각 부분이 무엇을 하는지 적어 둡니다. 입력으로는 쓰는 사람이 실제로 바꿔야 하는 값만 노출하고, 이름과 단위, 기본값을 설명에 남깁니다. 노드 사이의 데이터 흐름과 계산 순서를 분명히 이해하고 있어야 결과가 예상과 다를 때 원인을 찾을 수 있으며, 문제가 생기면 디버거로 입자의 속성 변화를 따라가며 어느 노드에서 값이 어긋나는지 찾습니다.
다른 이미터에 이 모듈을 넣는 사람에게는 사용 조건도 함께 알려야 합니다. 이 모듈은 X 속도를 덮어쓰므로 X 속도를 바꾸는 다른 모듈과 함께 쓰면 결과가 뒤바뀔 수 있고, 앞에는 나이를 갱신하는 Particle State가, 뒤에는 위치 계산이 있어야 합니다.
스크래치 패드(Scratch Pad)에서 먼저 만들어 시험해 보는 방법도 있습니다. 스크래치 패드 모듈은 그 시스템이나 이미터 안에서만 쓰지만, 다른 에셋에서도 쓰고 싶어지면 모듈 에셋으로 내보낼 수 있습니다.
정리
- 사용자 정의 모듈은 노드를 이어 직접 만든 모듈로, 콘텐츠 브라우저의
FX메뉴에서 나이아가라 모듈 스크립트(Niagara Module Script) 에셋을 만들고 스크립트 에디터에서 편집합니다. - 모듈 그래프의 값은 파라미터 맵을 따라 흐르며,
Map Get으로 입자의 속성과 입력을 읽고Map Set으로 결과를 씁니다. - 지그재그 모듈은
sin(NormalizedAge × ZigzagFrequency) × ZigzagStrength로 X 속도를 덮어쓰며, 2π는 한 수명에 한 주기라는 뜻입니다. - 덮어쓰는 모듈은 Strength가 0일 때 원래 값을 되살리지 않으므로, 원래 속도를 유지하려면 합치는 규칙을 따로 설계합니다.
- 모듈은
Particle State뒤,Solve Forces and Velocity앞에 두고, 목록에 보이려면 사용 범위와 라이브러리 노출 설정을 확인합니다.
연습 문제
1. 수명 4초, 나이 3초
수명이 4초로 고정된 입자가 3초 동안 살아 있습니다. ZigzagFrequency는 2π, ZigzagStrength는 50cm/s이며, 이전 속도의 Y는 5, Z는 -10이고 다른 힘과 Drag, 속도를 쓰는 모듈은 꺼져 있습니다. NormalizedAge와 위상, 새 속도 벡터를 구해 보세요. 같은 조건에서 Strength만 0으로 바꾸면 X가 이전 값으로 돌아갈까요?
해설 보기
진행률은 3 ÷ 4 = 0.75이고 위상은 0.75 × 2π = 3π/2입니다. 그 사인 값은 -1이므로 새 X는 -1 × 50 = -50이고, 새 속도 벡터는 (-50, 5, -10)cm/s입니다.
Strength를 0으로 바꾸면 계산한 X가 0이 되어 그 값으로 덮어쓰므로, 이전 X로 돌아가지 않습니다. 또 Frequency 2π는 이 입자의 4초 수명 동안 한 주기라는 뜻이며, 초당 한 주기나 2π Hz라는 뜻이 아닙니다.
2. 점점 빨라지는 지그재그
다른 사람이 같은 모듈을 만들면서 Map Set에서 X를 덮어쓰는 대신 이전 X에 새 값을 더하도록 연결했습니다. 재생해 보니 입자가 점점 빠르게 한쪽으로 밀려 갑니다. 왜 그럴까요?
해설 보기
Particle Update의 모듈은 틱마다 실행되므로, 이전 X에 새 값을 더하면 틱마다 속도가 쌓여 가속이 됩니다. 사인 값이 양수인 구간이 이어지는 동안 X 속도가 계속 커지고, 틱 간격에 따라 쌓이는 양도 달라집니다.
의도대로 좌우로 흔들리게 하려면 이 예제처럼 계산한 X로 덮어쓰거나, 더하는 방식을 쓰려면 틱 사이의 시간 간격을 곱해 가속으로 계산하고 결과를 따로 설계해야 합니다.
3. 검색해도 보이지 않는 모듈
NCM_ZigzagMovement를 저장했는데, 이미터의 Particle Update 그룹에서 +를 눌러 검색해도 나타나지 않습니다. 무엇을 확인해야 할까요?
해설 보기
모듈 스크립트의 사용 범위(Module Usage)에 Particle Update가 들어 있는지 확인합니다. 사용 범위에 없는 그룹에서는 모듈이 목록에 나타나지 않습니다.
다음으로 라이브러리 노출(Library Visibility) 설정이 검색 결과에 보이도록 되어 있는지 확인합니다. 그래도 보이지 않으면 에셋을 저장했는지, 검색어가 에셋 이름과 맞는지 확인합니다.
Map Get과 Map Set으로 값을 읽고 쓰는 방식은 Epic의 스크래치 패드 모듈 문서의 예제에서 확인할 수 있습니다.
이것으로 3장을 마칩니다. 3장에서는 시간과 시점, 거리에 따라 입자를 만드는 스폰 모듈에서 시작해, 살아 있는 입자를 바꾸는 업데이트 모듈, 이미터 사이의 이벤트 핸들러, 스프라이트의 렌더링 설정, 그리고 노드로 직접 만드는 모듈까지 모듈을 다루는 방법을 익혔습니다. 다음 장의 데이터 인터페이스의 기본 개념부터는 데이터 인터페이스와 사용자 파라미터, 커브와 그래디언트, 메시 데이터로 효과 바깥의 데이터를 받아 쓰는 방법을 다룹니다.