동적 파라미터 갱신
블루프린트의 게임 상태를 사용자 변수에 지속적으로 전달해 캐릭터 속도에 따라 파티클 크기와 움직임이 반응하게 합니다.
캐릭터가 빨리 달릴수록 발밑의 먼지가 커지고, 체력이 줄수록 주변의 효과가 약해지게 하려면, 플레이 중에 바뀌는 값을 계속 효과에 넘겨야 합니다. 블루프린트 제어에서 사용자 파라미터에 값을 한 번 넣는 방법을 익혔다면, 이번에는 바뀌는 값을 이어서 넘기는 방법을 다룹니다. 이때는 무엇을 넘기는지뿐 아니라 값의 단위와 얼마나 자주 갱신할지, 그리고 나이아가라의 어느 단계에서 그 값을 읽는지를 함께 정해야 합니다. 이 글에서는 속력에 따라 크기가 바뀌는 발밑 먼지를 만들고, 갱신 주기를 고르는 기준을 정리합니다.
읽고, 바꾸고, 넘기고, 쓰기
값은 네 단계를 거쳐 효과에 닿습니다. 블루프린트에서 원래 값을 읽고, 필요하면 시각 표현에 맞는 범위로 바꾼 뒤, 그 나이아가라 컴포넌트에 Set Niagara Variable로 넘깁니다. 그러면 나이아가라 시스템 쪽에서는 이름과 타입이 같은 사용자 파라미터가 그 값을 받고, 그 파라미터에 연결된 모듈의 입력이 값을 읽어 씁니다.
각 단계에서 챙길 것이 있습니다. 먼저 값의 뜻에 맞는 타입과 setter를 고릅니다. 속력은 Float, 방향은 Vector, 월드의 위치는 Position으로 넘기는 식입니다. 다음으로 값을 읽는 단계를 정합니다. Particle Spawn에서 읽는 초기 속도는 새로 생기는 입자에만 반영되므로, 이미 있는 입자의 크기나 색을 계속 바꾸려면 Particle Update에서 읽습니다. 목표를 따라가게 하려면 그 위치를 읽는 Point Attraction Force 같은 모듈의 좌표 공간을 맞춥니다. 값을 넘기기만 해서는 입자가 저절로 목표를 따라가지 않습니다.
속력에 따라 커지는 발밑 먼지
이 실습에서는 User.PlayerSpeed의 단위를 처음부터 끝까지 cm/s로 유지하고, 나이아가라 안에서 한 번만 크기 배율로 바꿉니다. 블루프린트와 나이아가라가 각자 값을 다른 범위로 바꾸면, 기준 속력을 고칠 때 두 곳을 함께 고쳐야 하고 한 곳만 고치면 크기가 어긋나기 때문입니다. 작업은 앞 절의 네 단계를 따라 나이아가라 시스템에서 값을 받아 쓰는 쪽을 먼저 만들고, 그다음 캐릭터 블루프린트에서 속력을 읽어 넘기는 쪽을 만듭니다.
나이아가라 시스템
먼저 기본 스프라이트 시스템을 복제해 NS_SpeedDust로 저장합니다. Emitter State와 Spawn Rate를 구성하고, Particle Spawn의 Initialize Particle에서 Lifetime은 0.5초에서 1초 사이, 스프라이트 크기는 5cm, 색은 밝은 갈색으로 둡니다. 같은 그룹의 Add Velocity로는 Z를 20cm/s, X와 Y를 -10cm/s에서 10cm/s 사이의 값으로 줍니다. 아래로 끄는 중력은 우선 끄고, Particle Update에 Particle State와 Solve Forces and Velocity를 둡니다.
다음으로 한 장짜리 먼지 텍스처를 쓰는 Translucent 머티리얼을 Sprite Renderer에 지정합니다. RGB와 Alpha에 Particle Color를 반영하고, 필요하면 머티리얼의 DepthFade로 표면과 만나는 자리를 부드럽게 합니다. 렌더러에 공통 Soft Particle 옵션이 있다고 가정하지 않습니다.
그다음 Float 사용자 파라미터 User.PlayerSpeed를 만들고 기본값을 0으로 둡니다. Particle Update의 Scale Sprite Size에서 이 값을 Clamp(PlayerSpeed / 600, 0, 1) × 3으로 바꿔 Uniform 배율에 연결합니다. 즉 600cm/s를 기준 속력으로 삼아, 그때 크기가 세 배가 되게 하는 식입니다. 이 모듈은 초기 크기를 기준으로 배율을 적용하므로, 지금 크기에 틱마다 배율을 계속 곱하는 방식과는 다릅니다.
마지막으로 Particle State에서 수명이 다하면 입자가 사라지게 하고, Scale Color의 Alpha를 NormalizedAge에 따라 1에서 0으로 줄입니다. 먼지가 캐릭터를 따라오지 않고 월드에 남게 하려면 이미터의 Local Space를 끕니다.
캐릭터 블루프린트
플레이어 캐릭터에 나이아가라 컴포넌트를 추가하고 NS_SpeedDust를 지정합니다. 이 컴포넌트를 Mesh의 자식으로 옮긴 뒤 Parent Socket에 실제 발의 본이나 소켓 이름을 지정하고 상대 위치를 맞추며, Auto Activate는 끕니다.
다음으로 Event Tick에서 Get Velocity의 수평 성분의 길이를 Vector Length XY로 구합니다. 점프할 때의 수직 속력까지 먼지 크기에 반영하고 싶을 때만 3D의 Vector Length를 씁니다. Get Velocity나 길이를 구하는 노드는 실행 핀이 없는 순수 노드이므로 값의 선으로만 잇고, 실행 선은 setter로 보냅니다.
그다음 나이아가라 컴포넌트를 Target으로 Set Niagara Variable (Float)를 불러, User.PlayerSpeed에 원래의 수평 속력을 cm/s 그대로 넘깁니다. 블루프린트에서 다시 0에서 10 같은 범위로 바꾸지 않습니다. 캐릭터의 Max Walk Speed가 600과 다르다면 나이아가라 쪽의 기준 속력도 함께 맞춥니다.
이어서 속력이 5cm/s를 넘고 캐릭터가 지면에 있는지를 비교해 이동 상태를 나타내는 Bool을 만듭니다. 이 Bool이 이전 상태와 달라졌을 때만 Activate나 Deactivate를 부릅니다. 값은 Activate보다 먼저 넘기고, Tick마다 Reset으로 다시 시작하지 않습니다.
마지막으로 EndPlay에서 따로 만든 타이머나 바깥의 참조가 있다면 정리합니다. 컴파일과 저장을 한 뒤 멈춰 있을 때와 걸을 때, 달릴 때, 점프할 때 먼지가 생기는지와 크기가 어떻게 바뀌는지 비교합니다. Deactivate한 뒤 기존 입자가 사라지는 방식은 Inactive Response와 수명을 따릅니다.
넘긴 값과 예상 크기
Clamp(PlayerSpeed / 600, 0, 1) × 3 식에 몇 가지 속력을 넣으면 다음과 같습니다.
| 넘긴 수평 속력 | Uniform 배율 | 초기 5cm의 예상 크기 |
|---|---|---|
| 0cm/s | 0 | 0cm, 이동 상태도 꺼짐 |
| 100cm/s | 0.5 | 2.5cm |
| 300cm/s | 1.5 | 7.5cm |
| 600cm/s 이상 | 3 | 최대 15cm |
이 값은 식으로 계산한 결과이며, 실제로 화면에서 차지하는 크기는 카메라와 텍스처, 겹침에 따라 달라집니다. 최대 배율과 기준 속력을 먼저 정하고, 필요하면 입력을 부드럽게 보간합니다.
이 설계에는 한 가지 특징이 있습니다. Particle Update에서 지금의 속력을 읽으므로, 캐릭터가 멈추면 이미 생긴 먼지까지 크기가 0으로 줄어듭니다. 이것이 부자연스럽다면, 생길 때의 배율을 Particle Spawn에서 속성에 저장해 두고 수명에 따른 페이드는 따로 유지하는 설계와 비교해 봅니다.
수직 속도를 어떻게 다루는지도 결과를 바꿉니다. 속도가 (300, 400, 1200)cm/s라면 수평 길이는 sqrt(300² + 400²) = 500cm/s입니다. 넘긴 값을 500으로 두면 배율은 500 ÷ 600 × 3 = 2.5이고, 초기 5cm의 크기는 12.5cm입니다. 3D 길이는 sqrt(300² + 400² + 1200²) = 1300cm/s여서 같은 식에서는 최대 배율 3이 되지만, 이 실습은 수평 속력을 고른 것입니다. 이 값도 식의 결과이며, 화면에서 12.5cm로 재었다는 뜻은 아닙니다.
갱신 주기 고르기
값을 얼마나 자주 넘길지는 값의 성질에 따라 고릅니다. 속도나 조준점처럼 프레임마다 따라가야 하는 값은 Tick에서 넘깁니다. 이때 작은 차이는 무시해도 되는지 확인하되, 초기값을 넘기는 일은 빠뜨리지 않습니다. 체력이나 장비, 스킬 상태처럼 가끔 바뀌는 값은 바뀌는 이벤트에서 넘깁니다. 바람이나 온도처럼 느리게 바뀌는 값은 타이머로 일정 간격마다 넘길 수 있는데, 타이머는 기본적으로 게임 스레드에서 불리므로 비동기 작업이 되는 것은 아닙니다.
Actor Component나 Skeletal Mesh, Static Mesh 같은 데이터 인터페이스가 지원하는 데이터라면, 블루프린트에서 계속 넘기는 대신 나이아가라가 필요할 때 직접 읽게 할 수도 있습니다. 블루프린트의 반복 전달은 줄지만, 데이터를 준비하고 샘플링하는 비용과 CPU, GPU 지원의 조건이 없어지는 것은 아닙니다. 여러 시스템이 함께 쓰는 값은 블루프린트 제어에서 다룬 나이아가라 파라미터 컬렉션에 둡니다.
서버가 정하는 값은 게임 코드가 클라이언트에 전달하고, 시각 효과는 그 값을 받아 쓰게 합니다. 아무 비동기 작업(Async Task)에서나 UObject나 나이아가라 컴포넌트를 직접 고치는 것을 갱신 주기의 최적화 방법으로 쓰지 않습니다.
정리
- 바뀌는 값은 블루프린트에서 읽고 바꿔
Set Niagara Variable로 넘기며, 같은 이름과 타입의 사용자 파라미터가 모듈의 입력에 연결되어 있어야 합니다. - Particle Spawn에서 읽는 값은 새 입자에만 반영되므로, 이미 있는 입자를 계속 바꾸려면 Particle Update에서 읽습니다.
- 값의 단위는 한 곳에서만 바꾸는 편이 좋으며, 이 실습은 cm/s를 그대로 넘겨 나이아가라에서 배율로 바꿉니다.
- 효과를 켜고 끄는 일은 이동 상태가 바뀔 때만 하고, 값은
Activate보다 먼저 넘기며 Tick마다 다시 시작하지 않습니다. - 갱신 주기는 Tick, 이벤트, 타이머, 데이터 인터페이스, 파라미터 컬렉션 가운데 값의 성질에 맞게 고릅니다.
연습 문제
1. 공중에서 움직일 때
속도가 (-180, 240, 0)cm/s이고 캐릭터가 지면에 있지 않습니다. 수평 속력과 크기 배율, 예상 크기를 구해 보세요. 이때 생성을 켜는 조건은 참일까요? 이미 있는 입자의 크기를 Update에서 읽는 경로와 새로 만들지의 여부를 구별해 설명해 보세요.
해설 보기
수평 속력은 sqrt(180² + 240²) = 300cm/s이고, 배율은 300 ÷ 600 × 3 = 1.5, 예상 크기는 5 × 1.5 = 7.5cm입니다.
속력은 5cm/s를 넘지만 지면 조건이 거짓이므로, 이동 상태를 나타내는 Bool은 거짓입니다. 상태가 바뀌는 순간에 Deactivate하며, 남아 있는 입자를 어떻게 그리고 갱신하고 끝낼지는 시스템의 Inactive Response와 수명에 따라 확인합니다. setter로 값을 넘기는 일과 크기를 계산하는 식, 생성을 켜는 조건은 서로 다른 단계입니다.
2. 블루프린트에서 한 번 더 바꾼 값
다른 사람이 블루프린트에서 속력을 속력 ÷ 60으로 0에서 10 사이로 바꿔 넘겼는데, 나이아가라에서는 여전히 Clamp(PlayerSpeed / 600, 0, 1) × 3을 쓰고 있습니다. 600cm/s로 달릴 때 배율은 얼마가 될까요? 무엇이 문제일까요?
해설 보기
블루프린트에서 넘긴 값은 600 ÷ 60 = 10이고, 나이아가라에서 10 ÷ 600 × 3 = 0.05가 되어 먼지가 거의 보이지 않습니다. 단위를 두 곳에서 바꿔서 값이 두 번 줄어든 것입니다.
값의 단위는 한 곳에서만 바꿉니다. 블루프린트에서는 cm/s를 그대로 넘기고 나이아가라에서만 배율로 바꾸거나, 블루프린트에서 바꾼다면 나이아가라의 식도 그 범위에 맞게 고칩니다. 어느 쪽이든 파라미터에 담기는 값의 단위를 문서로 남겨 둡니다.
3. 체력 효과를 Tick마다
체력에 따라 효과의 색을 바꾸려고 Event Tick에서 매 프레임 체력을 넘기고 있습니다. 체력은 피격되거나 회복될 때만 바뀝니다. 어떻게 바꾸면 좋을까요?
해설 보기
체력은 가끔만 바뀌는 값이므로, 피격이나 회복처럼 체력이 바뀌는 이벤트에서만 값을 넘기면 됩니다. 매 프레임 같은 값을 다시 넘기는 일을 줄일 수 있습니다.
이벤트 방식으로 바꿀 때는 효과가 처음 켜질 때 지금의 체력 값을 한 번 넘기는 일을 빠뜨리지 않습니다. 그렇지 않으면 다음 이벤트가 올 때까지 기본값으로 보일 수 있습니다.
다음 글 이벤트 기반 파티클 스폰에서는 피격이나 충돌 같은 게임의 이벤트가 일어난 위치와 방향에 맞춰 일회성 효과를 만드는 방법을 살펴봅니다.