Project_WL/Assets/WL/Scripts/Combat/HitFlash.cs

129 lines
5.1 KiB
C#

using UnityEngine;
using WL.Player;
namespace WL.Combat
{
/// <summary>
/// 피격 시 대상 전신을 지정 색으로 물들였다가 되돌리는 하이라이트.
///
/// 힘민지는 같은 연출을 `Actor.cs:1595-1600` · `:2045-2066` 에서
/// `Renderer.material` / `sharedMaterials` 리스트 조작으로 했다가
/// **GC Alloc 2.3KB 문제로 전량 주석 처리**했다(`Actor.cs:2046` 주석 명시 · himminji-reuse 02 §7-2).
/// → WL 은 처음부터 **MaterialPropertyBlock** 으로 구현한다(머티리얼 인스턴스 생성 0 · 할당 0).
///
/// 대상 셰이더 `Toon/Toon`(Unity Chan Toon Shader)의 색 프로퍼티를 실측해
/// 베이스색 2종(_BaseColor·_Color) + 그림자색 2종(_1st_ShadeColor·_2nd_ShadeColor)을 함께 덮는다.
/// 베이스만 바꾸면 그늘진 면이 원래 색으로 남아 플래시가 반쪽만 보인다.
/// </summary>
[DisallowMultipleComponent]
public class HitFlash : MonoBehaviour
{
private static readonly string[] ColorProps = { "_BaseColor", "_Color", "_1st_ShadeColor", "_2nd_ShadeColor" };
[Header("수치 설정 (ScriptableObject)")]
[SerializeField] private CombatSettings settings;
[Header("참조 (비우면 자식 렌더러 전체)")]
[SerializeField] private Renderer[] renderers;
private MaterialPropertyBlock _block;
private int[] _propIds;
private Color[][] _originals; // [renderer][prop]
private bool[] _hasProp; // [prop] — 셰이더가 그 프로퍼티를 가지고 있는가
private float _timer;
private bool _active;
private Enemy _enemy;
private void Awake()
{
if (renderers == null || renderers.Length == 0) renderers = GetComponentsInChildren<Renderer>(true);
_enemy = GetComponent<Enemy>();
if (settings == null && _enemy != null) settings = _enemy.Settings;
_block = new MaterialPropertyBlock();
_propIds = new int[ColorProps.Length];
_hasProp = new bool[ColorProps.Length];
for (int i = 0; i < ColorProps.Length; i++) _propIds[i] = Shader.PropertyToID(ColorProps[i]);
_originals = new Color[renderers.Length][];
for (int r = 0; r < renderers.Length; r++)
{
_originals[r] = new Color[ColorProps.Length];
var m = renderers[r] != null ? renderers[r].sharedMaterial : null;
for (int i = 0; i < ColorProps.Length; i++)
{
bool has = m != null && m.HasProperty(ColorProps[i]);
_originals[r][i] = has ? m.GetColor(ColorProps[i]) : Color.white;
if (has) _hasProp[i] = true;
}
}
}
private void OnEnable()
{
if (_enemy != null) _enemy.Damaged += OnDamaged;
}
private void OnDisable()
{
if (_enemy != null) _enemy.Damaged -= OnDamaged;
Clear();
}
private void OnDamaged(Enemy e, float amount) { Flash(); }
/// <summary>피격 색 플래시 시작. 재피격 시 타이머만 되감는다.</summary>
public void Flash()
{
float dur = settings != null ? settings.hitFlashSeconds : 0.15f;
if (dur <= 0f) return;
_timer = dur;
_active = true;
Apply(1f);
}
private void LateUpdate()
{
if (!_active) return;
float dur = settings != null ? settings.hitFlashSeconds : 0.15f;
_timer -= Time.deltaTime;
if (_timer <= 0f) { Clear(); return; }
Apply(dur > 0f ? Mathf.Clamp01(_timer / dur) : 0f);
}
/// <summary>t = 1 이면 플래시 최대, 0 이면 원래 색.</summary>
private void Apply(float t)
{
Color flash = settings != null ? settings.hitFlashColor : new Color(1f, 0.3f, 0.22f, 1f);
float strength = (settings != null ? settings.hitFlashStrength : 0.85f) * t;
for (int r = 0; r < renderers.Length; r++)
{
var rend = renderers[r];
if (rend == null) continue;
rend.GetPropertyBlock(_block);
for (int i = 0; i < _propIds.Length; i++)
{
if (!_hasProp[i]) continue;
Color baseCol = _originals[r][i];
Color c = Color.Lerp(baseCol, flash, strength);
c.a = baseCol.a;
_block.SetColor(_propIds[i], c);
}
rend.SetPropertyBlock(_block);
}
}
/// <summary>덮어쓴 프로퍼티를 걷어내 원래 머티리얼 값으로 되돌린다.</summary>
public void Clear()
{
_active = false;
_timer = 0f;
if (renderers == null) return;
for (int r = 0; r < renderers.Length; r++)
if (renderers[r] != null) renderers[r].SetPropertyBlock(null);
}
}
}