Unity VFX Graph C# API 프로그래밍
Unity VFX Graph는 노드 기반으로 GPU 파티클 시스템을 구성하는 도구입니다. C# 스크립트와 연동하면 게임플레이 이벤트에 반응하는 동적 이펙트, 런타임 파라미터 변경, 커스텀 데이터 전달 등을 구현할 수 있습니다.
1. 기본 설정
섹션 제목: “1. 기본 설정”using UnityEngine;using UnityEngine.VFX;
public class VFXController : MonoBehaviour{ [SerializeField] private VisualEffect vfx;
void Start() { // VisualEffect 컴포넌트 참조 if (vfx == null) vfx = GetComponent<VisualEffect>();
// 재생 제어 vfx.Play(); // vfx.Stop(); // vfx.Reinit(); // 초기 상태로 재시작 }}2. 프로퍼티 바인딩
섹션 제목: “2. 프로퍼티 바인딩”VFX Graph에서 노출한 프로퍼티를 C#에서 읽고 씁니다.
public class VFXPropertyExample : MonoBehaviour{ [SerializeField] private VisualEffect vfx;
// VFX Graph에서 Exposed로 설정한 프로퍼티 이름 private static readonly int SpawnRateID = Shader.PropertyToID("SpawnRate"); private static readonly int ColorID = Shader.PropertyToID("MainColor"); private static readonly int PositionID = Shader.PropertyToID("SpawnPosition"); private static readonly int TextureID = Shader.PropertyToID("SplatTexture");
void Update() { // float vfx.SetFloat(SpawnRateID, 100f);
// 이름 문자열 직접 사용 (성능 비용 있음, 디버그용) vfx.SetFloat("SpawnRate", 100f);
// Vector3 vfx.SetVector3(PositionID, transform.position);
// Color (HDR 가능) vfx.SetVector4(ColorID, new Color(1f, 0.5f, 0f, 1f));
// Texture // vfx.SetTexture(TextureID, myTexture);
// bool vfx.SetBool("IsActive", true);
// int vfx.SetInt("MaxParticles", 5000); }
void ReadProperties() { // 프로퍼티 존재 여부 확인 if (vfx.HasFloat(SpawnRateID)) { float rate = vfx.GetFloat(SpawnRateID); Debug.Log($"Current spawn rate: {rate}"); } }}3. 이벤트 전송
섹션 제목: “3. 이벤트 전송”VFX Graph의 On Event 블록을 트리거합니다.
public class VFXEventExample : MonoBehaviour{ [SerializeField] private VisualEffect vfx;
// 이벤트 이름을 ID로 캐싱 (성능 최적화) private static readonly int OnHitEventID = Shader.PropertyToID("OnHit"); private static readonly int OnExplosionID = Shader.PropertyToID("OnExplosion"); private static readonly int OnDeathEventID = Shader.PropertyToID("OnDeath");
public void PlayHitEffect() { vfx.SendEvent(OnHitEventID); }
public void PlayExplosionAt(Vector3 position) { // 이벤트 어트리뷰트로 추가 데이터 전달 var eventAttribute = vfx.CreateVFXEventAttribute(); eventAttribute.SetVector3("Position", position); eventAttribute.SetFloat("Intensity", 2.0f); vfx.SendEvent(OnExplosionID, eventAttribute); }
public void OnPlayerDeath() { vfx.SendEvent(OnDeathEventID); }
// 내장 이벤트 이름 상수 void BuiltinEvents() { vfx.SendEvent(VisualEffectAsset.PlayEventName); // "OnPlay" vfx.SendEvent(VisualEffectAsset.StopEventName); // "OnStop" }}4. VFXEventAttribute — 커스텀 페이로드
섹션 제목: “4. VFXEventAttribute — 커스텀 페이로드”public class WeaponFX : MonoBehaviour{ [SerializeField] private VisualEffect muzzleFlash; [SerializeField] private VisualEffect impactFX;
private VFXEventAttribute _muzzleAttr; private VFXEventAttribute _impactAttr;
void Awake() { // 매 프레임 생성하지 않도록 미리 생성 _muzzleAttr = muzzleFlash.CreateVFXEventAttribute(); _impactAttr = impactFX.CreateVFXEventAttribute(); }
public void FireWeapon(Ray ray) { // 총구 이펙트 _muzzleAttr.SetVector3("Direction", ray.direction); muzzleFlash.SendEvent("OnFire", _muzzleAttr);
// 레이캐스트 if (Physics.Raycast(ray, out RaycastHit hit, 100f)) { PlayImpact(hit); } }
void PlayImpact(RaycastHit hit) { // 충돌 지점 데이터를 이펙트에 전달 _impactAttr.SetVector3("Position", hit.point); _impactAttr.SetVector3("Normal", hit.normal);
// 표면 타입에 따른 색상 Color surfaceColor = GetSurfaceColor(hit.collider); _impactAttr.SetVector4("Color", surfaceColor);
impactFX.SendEvent("OnImpact", _impactAttr); }
Color GetSurfaceColor(Collider col) { if (col.CompareTag("Metal")) return Color.gray; if (col.CompareTag("Wood")) return new Color(0.5f, 0.3f, 0.1f); return Color.white; }}5. 런타임 파라미터 애니메이션
섹션 제목: “5. 런타임 파라미터 애니메이션”public class VFXAnimator : MonoBehaviour{ [SerializeField] private VisualEffect vfx; [SerializeField] private float intensityMultiplier = 1f;
private float _currentIntensity; private float _targetIntensity;
public void SetIntensity(float target, float duration = 0.5f) { _targetIntensity = target; StopAllCoroutines(); StartCoroutine(AnimateIntensity(duration)); }
private System.Collections.IEnumerator AnimateIntensity(float duration) { float start = _currentIntensity; float elapsed = 0f;
while (elapsed < duration) { elapsed += Time.deltaTime; float t = elapsed / duration; _currentIntensity = Mathf.Lerp(start, _targetIntensity, t); vfx.SetFloat("Intensity", _currentIntensity * intensityMultiplier); yield return null; }
_currentIntensity = _targetIntensity; vfx.SetFloat("Intensity", _currentIntensity * intensityMultiplier); }
// 플레이어 속도에 따른 속도감 이펙트 void Update() { if (TryGetComponent<Rigidbody>(out var rb)) { float speed = rb.velocity.magnitude; vfx.SetFloat("SpeedTrail", Mathf.InverseLerp(0f, 20f, speed)); vfx.SetVector3("Velocity", rb.velocity); } }}6. 다중 VFX 풀링
섹션 제목: “6. 다중 VFX 풀링”using System.Collections.Generic;using UnityEngine;using UnityEngine.VFX;
public class VFXPool : MonoBehaviour{ [SerializeField] private VisualEffect prefab; [SerializeField] private int poolSize = 20;
private Queue<VisualEffect> _pool = new();
void Start() { for (int i = 0; i < poolSize; i++) { var vfx = Instantiate(prefab); vfx.gameObject.SetActive(false); _pool.Enqueue(vfx); } }
public VisualEffect Spawn(Vector3 position, Quaternion rotation) { if (_pool.Count == 0) { Debug.LogWarning("VFX Pool exhausted"); return null; }
var vfx = _pool.Dequeue(); vfx.transform.SetPositionAndRotation(position, rotation); vfx.gameObject.SetActive(true); vfx.Reinit(); vfx.Play();
StartCoroutine(ReturnAfterDelay(vfx, 3f)); return vfx; }
private System.Collections.IEnumerator ReturnAfterDelay(VisualEffect vfx, float delay) { yield return new WaitForSeconds(delay); vfx.Stop(); yield return new WaitForSeconds(0.5f); // 파티클 소멸 대기 vfx.gameObject.SetActive(false); _pool.Enqueue(vfx); }}7. 디버그 및 유의사항
섹션 제목: “7. 디버그 및 유의사항”// VFX 상태 확인Debug.Log($"Alive particle count: {vfx.aliveParticleCount}");Debug.Log($"Is playing: {vfx.isActiveAndEnabled}");
// 프로퍼티 이름 오류 방지: HasXxx로 먼저 확인if (vfx.HasFloat("MyProperty")) vfx.SetFloat("MyProperty", value);else Debug.LogWarning("VFX property 'MyProperty' not found or not exposed");VFX Graph C# API의 핵심은 SendEvent로 VFX Graph 내부 흐름을 트리거하고, SetFloat/SetVector3 등으로 프로퍼티를 런타임에 조작하는 것입니다. Shader.PropertyToID로 이름을 ID로 캐싱하면 매 프레임 문자열 해시 비용을 피할 수 있습니다. VFXEventAttribute를 재사용하면 힙 할당을 줄일 수 있어 고빈도 이펙트에서 성능상 유리합니다.