// ================================================================ // CardParticleEffect.cs // 카드 선택 시 파티클 이펙트를 켜고 끄는 컴포넌트 // CardSelectionEffect.cs의 파티클 버전 대체품 // ================================================================ // ⚠️ 기존 LevelUpUIManager + CardUI 시스템과 통합 버전 // - Time.unscaledDeltaTime 사용 → Time.timeScale = 0 에서도 동작 // - Instantiate로 매번 새로 생성되는 카드 프리팹에 대응 // ================================================================ using UnityEngine; // Unity 엔진 핵심 네임스페이스 /// /// 카드 하나에 부착되어 선택 시 파티클 이펙트를 재생/정지하는 컴포넌트 /// CardUI.SetSelected()에서 Select()/Deselect()가 호출됩니다 /// public class CardParticleEffect : MonoBehaviour { // ======================================================================= // 인스펙터 노출 필드 // ======================================================================= [Header("=== 파티클 설정 ===")] [SerializeField] private ParticleSystem _particleSystem; // 카드 자식으로 배치된 파티클 시스템을 할당합니다 (미할당 시 자동 캐싱) [Header("=== 스케일 설정 ===")] [SerializeField] private float _selectedScale = 1.08f; // 선택 시 카드 확대 비율 (1.0 = 원본 크기) [SerializeField] private float _scaleSpeed = 8.0f; // 스케일 변화의 보간 속도 // ======================================================================= // 내부 상태 변수 // ======================================================================= private bool _isSelected; // 현재 선택 상태 여부 private Vector3 _originalScale; // 원본 스케일 (복원용) private Vector3 _targetScale; // 목표 스케일 private bool _isInitialized; // 초기화 완료 여부 (중복 초기화 방지) // ======================================================================= // 읽기 전용 프로퍼티 // ======================================================================= public bool IsSelected => _isSelected; // 외부에서 선택 상태를 확인할 수 있는 프로퍼티 // ======================================================================= // Unity 라이프사이클: Awake - 컴포넌트 캐싱 // ======================================================================= private void Awake() { CacheComponents(); // Awake에서 컴포넌트를 미리 캐싱합니다 } /// /// 파티클 시스템을 캐싱하고 초기 상태를 설정합니다 /// Awake에서 한 번만 호출하여 런타임 GetComponent 호출을 제거합니다 /// private void CacheComponents() { if (_isInitialized) return; // 이미 초기화되었으면 중복 실행을 방지합니다 if (_particleSystem == null) // Inspector에서 안 넣었으면 _particleSystem = GetComponentInChildren(); // 자식에서 자동으로 찾습니다 if (_particleSystem != null) // 파티클을 찾았으면 { _particleSystem.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear); // 시작 시 파티클을 완전히 정지시킵니다 var main = _particleSystem.main; // Main 모듈에 접근합니다 main.playOnAwake = false; // 자동 재생을 꺼서 Select() 호출 전까지 조용히 대기합니다 main.useUnscaledTime = true; // Time.timeScale=0에서도 파티클이 재생되도록 설정합니다 } _originalScale = transform.localScale; // 원본 스케일을 저장합니다 (나중에 복원용) _targetScale = _originalScale; // 초기 목표 스케일 = 원본 _isInitialized = true; // 초기화 완료 플래그를 설정합니다 } // ======================================================================= // Unity 라이프사이클: Update - 매 프레임 스케일 보간 // ⚠️ Time.unscaledDeltaTime 사용 → Time.timeScale = 0 에서도 동작 // ======================================================================= private void Update() { if (!_isInitialized) return; // 초기화 안 됐으면 스킵합니다 (방어 코드) if (Vector3.SqrMagnitude(transform.localScale - _targetScale) < 0.0001f) // 스케일이 목표에 거의 도달했으면 return; // 불필요한 연산을 스킵합니다 (SqrMagnitude: 루트 연산 없이 거리 비교) transform.localScale = Vector3.Lerp( // 현재 → 목표로 부드럽게 보간합니다 transform.localScale, // 현재 스케일 _targetScale, // 목표 스케일 _scaleSpeed * Time.unscaledDeltaTime // ⚠️ unscaledDeltaTime → timeScale=0에서도 동작합니다 ); } // ======================================================================= // 공개 API: 외부에서 선택/해제를 제어하는 메서드들 // ======================================================================= /// /// 이 카드를 선택 상태로 전환합니다 /// 파티클 재생 + 스케일 확대 /// CardUI.SetSelected(true)에서 호출됩니다 /// public void Select() { if (!_isInitialized) CacheComponents(); // Instantiate 직후 호출에 대비하여 방어적 초기화 if (_isSelected) return; // 이미 선택 상태면 중복 처리를 방지합니다 _isSelected = true; // 선택 상태 플래그를 켭니다 _targetScale = _originalScale * _selectedScale; // 목표 스케일을 확대 비율로 설정합니다 if (_particleSystem != null) // 파티클이 있으면 _particleSystem.Play(true); // 파티클 재생을 시작합니다 (true = 자식 파티클도 함께) } /// /// 이 카드를 해제 상태로 전환합니다 /// 파티클 정지 (자연 소멸) + 스케일 원복 /// CardUI.SetSelected(false)에서 호출됩니다 /// public void Deselect() { if (!_isSelected) return; // 이미 해제 상태면 중복 처리를 방지합니다 _isSelected = false; // 선택 상태 플래그를 끕니다 _targetScale = _originalScale; // 목표 스케일을 원본으로 되돌립니다 if (_particleSystem != null) // 파티클이 있으면 _particleSystem.Stop(true, ParticleSystemStopBehavior.StopEmitting); // 방출을 중지합니다 (이미 나온 파티클은 자연스럽게 사라짐) } /// /// 이펙트를 즉시 초기화합니다 (보간 없이 바로 꺼짐) /// CardUI.Setup()에서 호출되어 카드 재사용 시 깨끗한 상태를 보장합니다 /// public void ResetEffect() { if (!_isInitialized) CacheComponents(); // 초기화 안 됐으면 실행합니다 _isSelected = false; // 선택 상태를 해제합니다 _targetScale = _originalScale; // 스케일 목표를 원본으로 되돌립니다 transform.localScale = _originalScale; // 스케일을 즉시 원본으로 적용합니다 (보간 없이) if (_particleSystem != null) // 파티클이 있으면 _particleSystem.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear); // 즉시 정지하고 남은 파티클도 모두 제거합니다 } }