423 lines
14 KiB
C#

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Streamingle.Contents.BossRaid
{
/// <summary>
/// 보스 모델의 비주얼 이펙트를 관리합니다.
/// 머티리얼에 직접 접근하여 피격 플래시, 색상 틴트, 등장/사망 연출.
/// </summary>
public class BossVisualEffect : MonoBehaviour
{
#region Fields
[Header("피격 플래시")]
[SerializeField] private float flashDuration = 0.12f;
[SerializeField] private Color flashColor = Color.white;
[SerializeField]
[Tooltip("크리티컬 피격 색상")]
private Color critFlashColor = new Color(1f, 0.3f, 0.1f);
[SerializeField]
[Range(0f, 3f)]
[Tooltip("피격 시 이미션 강도")]
private float flashEmissionIntensity = 2f;
[Header("페이즈 틴트")]
[SerializeField]
[Range(0f, 1f)]
[Tooltip("페이즈 색상 틴트 강도 (0=원본, 1=완전 틴트)")]
private float phaseTintStrength = 0.3f;
[Header("사망 연출")]
[SerializeField] private int deathBlinkCount = 6;
[SerializeField] private float deathBlinkInterval = 0.15f;
[SerializeField] private Color deathTintColor = new Color(0.3f, 0.3f, 0.3f);
private Renderer[] _renderers;
private Color _baseTint = Color.white;
private Coroutine _flashCoroutine;
private Coroutine _deathCoroutine;
private Coroutine _shakeCoroutine;
private Coroutine _phasePulseCoroutine;
// 원본 머티리얼 색상 저장
private struct MatOriginal
{
public Material mat;
public Color color;
public Color emission;
public bool hasColor;
public bool hasEmission;
}
private List<MatOriginal> _originals = new List<MatOriginal>();
// 셰이더 프로퍼티 (여러 셰이더 호환)
private static readonly int[] ColorProps = {
Shader.PropertyToID("_Color"),
Shader.PropertyToID("_BaseColor"),
Shader.PropertyToID("_MainColor"),
};
private static readonly int[] EmissionProps = {
Shader.PropertyToID("_EmissionColor"),
Shader.PropertyToID("_EmissiveColor"),
};
#endregion
#region Unity Messages
private void Awake()
{
_renderers = GetComponentsInChildren<Renderer>();
CacheOriginalColors();
}
private void OnDestroy()
{
RestoreOriginalColors();
}
#endregion
#region Public Methods
public void PlayAppearAnimation()
{
StartCoroutine(AppearCoroutine());
}
public void PlayHitFlash(bool isCritical)
{
if (_flashCoroutine != null)
StopCoroutine(_flashCoroutine);
Color color = isCritical ? critFlashColor : flashColor;
float duration = isCritical ? flashDuration * 1.8f : flashDuration;
float emissionMult = isCritical ? flashEmissionIntensity * 1.5f : flashEmissionIntensity;
_flashCoroutine = StartCoroutine(FlashCoroutine(color, duration, emissionMult));
// 모델 흔들림
float intensity = isCritical ? 0.15f : 0.08f;
float dur = isCritical ? 0.2f : 0.12f;
if (_shakeCoroutine != null)
StopCoroutine(_shakeCoroutine);
_shakeCoroutine = StartCoroutine(ModelShakeCoroutine(intensity, dur));
}
public void PlayDeathAnimation()
{
if (_deathCoroutine != null)
StopCoroutine(_deathCoroutine);
_deathCoroutine = StartCoroutine(DeathCoroutine());
}
public void SetColorTint(Color color)
{
_baseTint = color;
// 페이즈 펄스 시작
if (_phasePulseCoroutine != null)
StopCoroutine(_phasePulseCoroutine);
if (color != Color.white)
_phasePulseCoroutine = StartCoroutine(PhasePulseCoroutine(color));
else
LerpMaterialColors(Color.white, Color.black, 0f);
}
#endregion
#region Private Methods - Material
private void CacheOriginalColors()
{
_originals.Clear();
if (_renderers == null) return;
foreach (var r in _renderers)
{
if (r == null) continue;
foreach (var mat in r.materials)
{
var orig = new MatOriginal { mat = mat };
// 컬러 프로퍼티 찾기
foreach (var prop in ColorProps)
{
if (mat.HasProperty(prop))
{
orig.color = mat.GetColor(prop);
orig.hasColor = true;
break;
}
}
// 이미션 프로퍼티 찾기
foreach (var prop in EmissionProps)
{
if (mat.HasProperty(prop))
{
orig.emission = mat.GetColor(prop);
orig.hasEmission = true;
break;
}
}
_originals.Add(orig);
}
}
}
private void RestoreOriginalColors()
{
foreach (var orig in _originals)
{
if (orig.mat == null) continue;
if (orig.hasColor) SetMatColor(orig.mat, orig.color);
if (orig.hasEmission) SetMatEmission(orig.mat, orig.emission);
}
}
/// <summary>
/// 모든 머티리얼의 색상과 이미션을 보간합니다.
/// tintT: 0=원본 색상, 1=tintColor로 완전 대체
/// </summary>
private void LerpMaterialColors(Color tintColor, Color emissionColor, float tintT)
{
foreach (var orig in _originals)
{
if (orig.mat == null) continue;
if (orig.hasColor)
{
Color c = Color.Lerp(orig.color, tintColor, tintT);
SetMatColor(orig.mat, c);
}
if (orig.hasEmission)
SetMatEmission(orig.mat, emissionColor);
}
}
/// <summary>
/// 모든 머티리얼을 강제로 특정 색상으로 덮어씁니다. (플래시용)
/// </summary>
private void OverrideMaterialColors(Color color, Color emission)
{
foreach (var orig in _originals)
{
if (orig.mat == null) continue;
if (orig.hasColor) SetMatColor(orig.mat, color);
if (orig.hasEmission) SetMatEmission(orig.mat, emission);
}
}
private void SetMatColor(Material mat, Color color)
{
foreach (var prop in ColorProps)
{
if (mat.HasProperty(prop))
{
mat.SetColor(prop, color);
return;
}
}
}
private void SetMatEmission(Material mat, Color color)
{
foreach (var prop in EmissionProps)
{
if (mat.HasProperty(prop))
{
mat.SetColor(prop, color);
if (color != Color.black)
mat.EnableKeyword("_EMISSION");
return;
}
}
}
#endregion
#region Private Methods - Coroutines
private IEnumerator AppearCoroutine()
{
Vector3 targetScale = transform.localScale;
transform.localScale = Vector3.zero;
float elapsed = 0f;
float duration = 0.5f;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
float t = elapsed / duration;
float ease = 1f + 0.3f * Mathf.Sin(t * Mathf.PI) * (1f - t);
transform.localScale = targetScale * Mathf.Min(ease * t * 1.2f, ease);
yield return null;
}
transform.localScale = targetScale;
}
private IEnumerator FlashCoroutine(Color color, float duration, float emissionMult)
{
Color emission = color * emissionMult;
// 순간 흰색/피격색으로 전환
OverrideMaterialColors(color, emission);
yield return new WaitForSeconds(duration * 0.3f);
// 원래 색상으로 부드럽게 복원
float elapsed = 0f;
float fadeDuration = duration * 0.7f;
while (elapsed < fadeDuration)
{
elapsed += Time.deltaTime;
float t = elapsed / fadeDuration;
float ease = t * t; // EaseIn
// 피격색 → 원본 (또는 페이즈 틴트 적용 상태)으로 복원
foreach (var orig in _originals)
{
if (orig.mat == null) continue;
if (orig.hasColor)
{
Color target = Color.Lerp(orig.color, _baseTint, phaseTintStrength);
Color current = Color.Lerp(color, target, ease);
SetMatColor(orig.mat, current);
}
if (orig.hasEmission)
{
Color emTarget = (_baseTint != Color.white)
? _baseTint * 0.3f
: orig.emission;
Color emCurrent = Color.Lerp(emission, emTarget, ease);
SetMatEmission(orig.mat, emCurrent);
}
}
yield return null;
}
// 최종 상태 확정
if (_baseTint != Color.white)
LerpMaterialColors(_baseTint, _baseTint * 0.3f, phaseTintStrength);
else
RestoreOriginalColors();
_flashCoroutine = null;
}
private IEnumerator PhasePulseCoroutine(Color tintColor)
{
// 페이즈 진입 시 한 번 강하게 번쩍
OverrideMaterialColors(tintColor, tintColor * 2f);
yield return new WaitForSeconds(0.15f);
// 지속적 미세 펄스 (분노 모드 느낌)
float time = 0f;
while (true)
{
time += Time.deltaTime;
float pulse = 0.5f + 0.5f * Mathf.Sin(time * 3f); // 천천히 밝아졌다 어두워짐
float strength = Mathf.Lerp(phaseTintStrength * 0.5f, phaseTintStrength, pulse);
float emPulse = pulse * 0.4f;
foreach (var orig in _originals)
{
if (orig.mat == null) continue;
if (orig.hasColor)
{
Color c = Color.Lerp(orig.color, tintColor, strength);
SetMatColor(orig.mat, c);
}
if (orig.hasEmission)
{
Color em = tintColor * emPulse;
SetMatEmission(orig.mat, em);
}
}
yield return null;
}
}
private IEnumerator DeathCoroutine()
{
// 페이즈 펄스 정지
if (_phasePulseCoroutine != null)
{
StopCoroutine(_phasePulseCoroutine);
_phasePulseCoroutine = null;
}
// 회색 틴트
LerpMaterialColors(deathTintColor, Color.black, 0.6f);
// 깜빡임
for (int i = 0; i < deathBlinkCount; i++)
{
SetRenderersVisible(false);
yield return new WaitForSeconds(deathBlinkInterval);
SetRenderersVisible(true);
yield return new WaitForSeconds(deathBlinkInterval * 0.7f);
}
// 축소하며 사라짐
Vector3 startScale = transform.localScale;
float elapsed = 0f;
float shrinkDuration = 0.5f;
while (elapsed < shrinkDuration)
{
elapsed += Time.deltaTime;
float t = elapsed / shrinkDuration;
transform.localScale = Vector3.Lerp(startScale, Vector3.zero, t * t);
// 사라지면서 점점 어두워짐
LerpMaterialColors(Color.black, Color.black, t);
yield return null;
}
transform.localScale = Vector3.zero;
_deathCoroutine = null;
}
private IEnumerator ModelShakeCoroutine(float intensity, float duration)
{
Vector3 originalPos = transform.localPosition;
float elapsed = 0f;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
float t = 1f - (elapsed / duration);
float x = Random.Range(-intensity, intensity) * t;
float y = Random.Range(-intensity, intensity) * t;
transform.localPosition = originalPos + new Vector3(x, y, 0f);
yield return null;
}
transform.localPosition = originalPos;
_shakeCoroutine = null;
}
private void SetRenderersVisible(bool visible)
{
if (_renderers == null) return;
foreach (var r in _renderers)
{
if (r != null) r.enabled = visible;
}
}
#endregion
}
}