콘텐츠로 이동

Unity VFX Graph C# API 프로그래밍

Unity VFX Graph는 노드 기반으로 GPU 파티클 시스템을 구성하는 도구입니다. C# 스크립트와 연동하면 게임플레이 이벤트에 반응하는 동적 이펙트, 런타임 파라미터 변경, 커스텀 데이터 전달 등을 구현할 수 있습니다.


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(); // 초기 상태로 재시작
}
}

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}");
}
}
}

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;
}
}

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);
}
}
}

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);
}
}

// 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를 재사용하면 힙 할당을 줄일 수 있어 고빈도 이펙트에서 성능상 유리합니다.