using UnityEngine; namespace Colosseum.Skills.Effects { /// /// VFX 생성 효과. /// 지정된 위치에 파티클 프리팹을 생성하고, 생명주기 및 재생 옵션을 제어합니다. /// [CreateAssetMenu(fileName = "VfxEffect", menuName = "Colosseum/Skills/Effects/VFX")] public class VfxEffect : SkillEffect { [Header("VFX 설정")] [Tooltip("생성할 VFX 프리팹")] [SerializeField] private GameObject vfxPrefab; [Header("위치 설정")] [Tooltip("VFX 생성 위치: Caster=시전자, CasterForward=시전자 정면, Target=대상 위치, GroundPoint=지면 타겟 위치")] [SerializeField] private VfxSpawnLocation spawnLocation = VfxSpawnLocation.Caster; [Tooltip("위치 오프셋")] [SerializeField] private Vector3 offset = Vector3.zero; [Header("생명주기")] [Tooltip("VFX 자동 파괴 시간 (0이면 파괴하지 않음)")] [Min(0f)] [SerializeField] private float lifetime = 2f; [Tooltip("시전자에게 부모 설정 여부")] [SerializeField] private bool parentToCaster = false; [Tooltip("VFX 크기 배율")] [Min(0.01f)] [SerializeField] private float scaleMultiplier = 1f; [Header("파티클 설정")] [Tooltip("ParticleSystem 자동 재생 여부 (false면 스폰만 하고 외부에서 제어)")] [SerializeField] private bool autoPlay = true; [Tooltip("ParticleSystem 루핑 여부")] [SerializeField] private bool loop = false; /// /// VFX 효과는 순수 시각 효과이므로 모든 클라이언트에서 로컬 실행됩니다. /// public override bool IsVisualOnly => true; private Vector3? groundPosition; /// /// 지면 타겟 위치를 캡처하여 ApplyEffect에서 사용할 수 있도록 합니다. /// public override void ExecuteOnCast(GameObject caster, GameObject targetOverride = null, Vector3? groundPosition = null) { this.groundPosition = groundPosition; base.ExecuteOnCast(caster, targetOverride, groundPosition); this.groundPosition = null; } /// /// VFX 프리팹을 생성하고 파티클을 재생합니다. /// protected override void ApplyEffect(GameObject caster, GameObject target) { if (vfxPrefab == null || caster == null) return; try { Vector3 spawnPos = GetSpawnPosition(caster, target); Quaternion spawnRot = caster.transform.rotation; Transform parent = parentToCaster ? caster.transform : null; // 비제네릭 Instantiate로 캐스팅 예외 방지 GameObject instance = (GameObject)Object.Instantiate(vfxPrefab, spawnPos + offset, spawnRot, parent); instance.transform.localScale *= scaleMultiplier; if (parentToCaster) instance.transform.localPosition = offset; if (autoPlay) { ParticleSystem[] particleSystems = instance.GetComponentsInChildren(true); foreach (var ps in particleSystems) { var main = ps.main; main.loop = loop; if (loop) main.stopAction = ParticleSystemStopAction.None; ps.Clear(true); ps.Play(true); } } if (lifetime > 0f) Object.Destroy(instance, lifetime); } catch (System.Exception e) { Debug.LogError($"[VfxEffect] VFX 생성 실패: {e.Message}\n{e.StackTrace}", this); } } /// /// spawnLocation에 따른 VFX 생성 위치를 계산합니다. /// private Vector3 GetSpawnPosition(GameObject caster, GameObject target) { return spawnLocation switch { VfxSpawnLocation.Caster => caster.transform.position, VfxSpawnLocation.CasterForward => caster.transform.position + caster.transform.forward * 2f, VfxSpawnLocation.Target => target != null ? target.transform.position : caster.transform.position, VfxSpawnLocation.GroundPoint => groundPosition ?? caster.transform.position, _ => caster.transform.position }; } } public enum VfxSpawnLocation { Caster, CasterForward, Target, GroundPoint } }