조건문과 루프 구조의 기초
비교와 값 선택, 암묵적 파티클 반복을 구분하고 높이에 따라 색상이 달라지는 모듈을 구성합니다.
입자가 특정 높이를 넘으면 색을 바꾸거나, 속력이 빨라지면 크기를 키우고 싶을 때가 있습니다. 이런 효과에는 조건에 따라 다른 값을 고르는 계산이 필요합니다. 그런데 블루프린트에 익숙하다면 여기서 헷갈리기 쉽습니다. 블루프린트는 실행 핀을 따라 Branch로 갈라지지만, 나이아가라 스크립트는 나이아가라 스크립트 기본 개념에서 본 것처럼 실행 핀이 아니라 데이터의 흐름으로 동작하기 때문입니다. 이 글에서는 나이아가라에서 조건에 따라 값을 고르는 방법과, 입자마다 저절로 일어나는 반복과 계산 안의 반복을 구분한 뒤, 높이에 따라 색이 바뀌는 모듈을 직접 만들어 봅니다.
조건에 따라 값 고르기
나이아가라에서 조건을 다루는 기본 노드는 Select입니다. Select는 Bool 조건에 따라 같은 타입의 두 값 가운데 하나를 내보냅니다. 예를 들어 조건이 참이면 파란색, 거짓이면 빨간색을 고르고, 고른 결과를 Map Set으로 Particles.Color에 쓰는 식입니다. 나이아가라의 If 계열 노드도 값을 비교하고 고르는 입력과 출력을 확인해서 쓰며, 블루프린트의 Branch처럼 True, False, Do 같은 실행 핀을 이어 붙이는 방식으로 이해하지 않습니다.
조건은 비교 노드로 만듭니다. Greater Than, Less Than, Equal 같은 비교 연산은 Bool 결과를 내고, 여러 조건은 AND, OR, NOT으로 엮습니다. 시간을 조건으로 쓸 때는 어떤 값을 비교하는지 주의해야 합니다. 입자가 생긴 뒤 흐른 시간은 Particles.Age이고 전체 수명은 Particles.Lifetime이므로, "일정 시간이 지났는가"를 알고 싶다면 Age를 비교합니다.
이름이 비슷한 Static Switch는 다른 용도입니다. Static Switch는 편집할 때 정한 선택에 따라 어느 계산 경로를 컴파일할지 고르는 노드여서, 틱마다 바뀌는 위치 같은 조건에 쓰지 않습니다.
Select처럼 값을 고르는 노드를 썼다고 해서, 고르지 않은 쪽 입력의 계산이 반드시 생략되는 것은 아닙니다. 비싼 계산이 실제로 빠지는지는 컴파일 결과와 실제 비용을 보고 확인해야 합니다.
입자마다의 반복과 계산 안의 반복
Particle Update의 스크립트는 살아 있는 입자마다 한 번씩 실행됩니다. 그래서 모든 입자에 같은 계산을 적용하려고 그래프에 For Each Particle 같은 노드를 넣을 필요가 없습니다. 반복은 이미 입자마다 일어나고 있습니다. 한 입자 안에서 모듈들은 데이터 의존을 따라 순서대로 실행되지만, 서로 다른 입자들이 인덱스 순서대로 차례차례 실행된다고 가정해서는 안 됩니다. 특히 GPU에서는 많은 입자가 동시에 계산됩니다.
한 입자의 계산 안에서 여러 표본을 모아 더하는 일은 이것과 다른 반복입니다. 이런 반복이 필요하다면 Custom HLSL이나 시뮬레이션 스테이지(Simulation Stage) 같은 곳에서 지원하는 반복 방식과 CPU, GPU의 제약을 확인해야 합니다. 블루프린트 For Loop의 First Index, Loop Body, Completed 같은 핀을 나이아가라에도 있는 공통 인터페이스로 생각하지 않으며, 반복 계산만으로 새 입자가 생기지도 않습니다.
계산 안의 반복은 비용을 크게 늘릴 수 있습니다. 반복 횟수가 k라면, 입자 수와 실행 빈도에 k가 곱해진 만큼 계산이 늘어날 수 있기 때문입니다. 그래서 반복의 상한을 정해 두고, 같은 결과를 단순한 수식이나 미리 계산해 둔 값으로 낼 수 있는지 먼저 확인합니다.
높이에 따라 색 바꾸기
이제 입자가 일정 높이를 넘으면 빨간색에서 파란색으로 바뀌는 모듈을 만들어 봅니다. 원점에서 위로 움직이는 입자의 시뮬레이션 공간 Z가 50cm를 넘는지를 검사하는 예제이며, NS_BasicSpawn을 복제해 실습합니다.
먼저 움직임을 단순하게 맞춥니다. Emitter State가 계속 켜져 있도록 하고 Emitter Update에 Spawn Rate를 둡니다. Particle Spawn의 Initialize Particle에서 Lifetime을 1초, 초기 위치를 원점으로 두고, Add Velocity로 Z 속도를 100cm/s로 줍니다. 초기 위치를 바꾸는 Shape Location이나 중력, 저항 같은 모듈은 이 실습에서 끕니다. Particle Update에는 Particle State와 Solve Forces and Velocity를 두고, 수명이 다하면 입자가 사라지게 합니다. 이미터의 Local Space를 켜면 아래의 Z 비교는 시스템을 기준으로 한 높이가 되므로, 월드의 높이를 검사하려면 좌표 변환과 기준 위치를 따로 맞춰야 합니다.
다음으로 Particle Update용 스크래치 패드 모듈을 만들어 Solve Forces and Velocity 뒤에 둡니다. 엔진의 Scale Color 원본 에셋을 고치지 않고, 다른 Color나 Scale Color 모듈은 이 실습의 결과를 덮어쓰지 않도록 꺼 둡니다.
모듈 그래프에서는 Input Map을 Map Get과 Map Set에 연결합니다. Map Get에 Particles.Position을 추가하고, 지금의 좌표 공간을 유지한 채 Position을 Vector로 바꾼 뒤 성분을 나눠 Z를 얻습니다. 이 Z를 Greater Than의 왼쪽 입력에, 50을 오른쪽 입력에 넣으면 Z가 50보다 클 때 참이 됩니다.
그다음 비교의 Bool 결과를 Select의 조건에 연결합니다. 출력 타입은 Color로 맞추고, 참일 때의 값은 RGBA (0, 0, 1, 1)인 파란색, 거짓일 때의 값은 (1, 0, 0, 1)인 빨간색으로 둡니다. 고른 색을 Map Set의 Particles.Color에 쓰고, 바뀐 맵을 Output Module로 보냅니다.
마지막으로 Apply와 Save를 누르고 시스템을 다시 시작합니다. Sprite Renderer의 Color 바인딩이 Particles.Color인지와, 머티리얼이 Particle Color의 RGB와 Alpha를 읽는지도 확인합니다.
기준 높이에서 비교 결과 확인하기
설정대로라면 Z가 50 이하일 때는 빨간색, 50을 넘으면 파란색이 됩니다. 원점에서 100cm/s로 일정하게 올라간다고 계산하면, 0.5초에 Z가 정확히 50cm가 됩니다. 비교 조건이 "50 초과"이므로 이 순간은 거짓이어서 빨간색입니다. 0.6초에 Z를 읽는다면 60cm이므로 참이 되어 파란색을 고릅니다.
| 나이 | Z(cm) | Z > 50 | 고르는 색 |
|---|---|---|---|
| 0.3초 | 30 | 거짓 | 빨간색 |
| 0.5초 | 50 | 거짓 | 빨간색 |
| 0.6초 | 60 | 참 | 파란색 |
| 0.9초 | 90 | 참 | 파란색 |
다만 실제 시뮬레이션이 정확히 이 시각에 값을 읽는다는 보장은 없습니다. 틱 간격에 따라 처음으로 색이 바뀌는 시점은 조금씩 달라지므로, 표는 수식으로 세운 예상입니다. 색이 바뀌는 시점을 예상할 때는 같은 틱에서 Solve Forces and Velocity가 계산한 뒤의 위치를 읽는다는 조건도 지켜야 합니다.
색이 전혀 바뀌지 않는다면 먼저 입자가 50cm에 도달할 수 있는지 계산해 보고, 그다음 좌표 공간과 그 뒤에서 색을 다시 쓰는 모듈이 있는지 확인합니다.
정리
- 나이아가라는 실행 핀이 아니라 데이터 흐름으로 동작하므로, 조건은
Select처럼 Bool에 따라 값을 고르는 노드로 다룹니다. - 비교 노드가 Bool을 만들고
AND,OR,NOT으로 조건을 엮으며, 흐른 시간은Particles.Age로 비교합니다. Static Switch는 편집할 때 컴파일할 경로를 고르는 노드이고, 고르지 않은 입력의 계산이 반드시 생략되는 것은 아닙니다.- Particle Update는 이미 입자마다 실행되므로 입자를 도는 반복 노드가 필요 없고, 입자들이 인덱스 순서대로 실행된다고 가정하지 않습니다.
- 계산 안의 반복은 비용에 반복 횟수를 곱하므로 상한을 두고, 단순한 수식이나 미리 계산한 값으로 바꿀 수 있는지 먼저 봅니다.
연습 문제
1. 속도를 40cm/s로 바꾸면
초기 위치 0, 수명 1초, 높이 조건 "50cm 초과"와 나머지 실습 설정은 그대로 둡니다. Z 속도를 40cm/s로 바꾸면 입자가 살아 있는 동안 파란색으로 바뀔까요? 바뀌지 않는 결과만 보고 Color 바인딩이 잘못되었다고 단정할 수 있을까요?
해설 보기
일정한 속도로 1초 동안 올라갈 수 있는 높이는 40 × 1 = 40cm이므로 50cm를 넘지 못합니다. 그래서 사라지기 전까지 모든 입자가 빨간색을 고릅니다.
이 결과는 높이 조건에 도달하지 못해 생긴 정상적인 결과이므로, Color 바인딩이 잘못되었다고 단정할 수 없습니다. 바인딩을 고치기 전에 초기 위치와 속도, 수명, 그리고 실제로 읽은 Z를 먼저 확인합니다.
2. 시간이 지나면 색 바꾸기
생긴 지 0.3초가 지난 입자만 노란색으로 바꾸려고, Particles.Lifetime이 0.3보다 큰지 비교했습니다. 그런데 수명이 1초인 입자들이 생기자마자 모두 노란색입니다. 무엇이 잘못되었을까요?
해설 보기
Particles.Lifetime은 입자의 전체 수명이므로 생긴 순간부터 1초로 정해져 있습니다. 그래서 1 > 0.3이 처음부터 참이 되어 모든 입자가 바로 노란색이 됩니다.
흐른 시간을 비교하려면 Particles.Age를 써야 합니다. Particles.Age > 0.3으로 바꾸면 생긴 지 0.3초가 지난 입자부터 노란색이 됩니다. 이때 Particle State가 나이를 갱신하고 있어야 합니다.
3. 입자마다 반복하는 노드
입자마다 색을 계산하려고 그래프에 입자 수만큼 반복하는 노드를 찾고 있습니다. 이런 노드가 필요할까요? 입자 하나의 색을 주변 표본 8개의 평균으로 계산하고 싶다면 어떤 점을 고려해야 할까요?
해설 보기
필요하지 않습니다. Particle Update의 스크립트는 이미 살아 있는 입자마다 한 번씩 실행되므로, 그래프에는 입자 하나에 대한 계산만 만들면 됩니다.
표본 8개의 평균처럼 입자 하나의 계산 안에서 되풀이해야 하는 작업은 별도의 반복입니다. Custom HLSL이나 시뮬레이션 스테이지에서 지원하는 반복 방식과 CPU, GPU의 제약을 확인하고, 반복 횟수 8이 입자 수와 실행 빈도에 곱해져 비용이 늘어난다는 점을 고려합니다. 같은 결과를 미리 계산한 텍스처나 단순한 수식으로 낼 수 있는지도 먼저 검토합니다.
다음 글 파티클 속성 읽기 및 쓰기에서는 모듈이 입자의 속성을 읽고 쓰는 순서와, 같은 속성을 여러 모듈이 쓸 때 어느 값이 남는지를 살펴봅니다.