using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; // 표정에 따라 캐릭터의 "커스텀 연출"을 켜고 끄는 범용 컴포넌트. 캐릭터 루트에 붙인다. // 눈 하이라이트, 볼 홍조, 땀방울, 눈동자 색 등 표정별로 달라지는 것들을 Key로 등록해 두고, // ExpressionData.CustomKeys에 그 Key를 넣으면 그 표정이 재생되는 동안 활성화된다. (DialogPlayer가 호출) // // 각 Effect는 아래 중 필요한 것만 채운다 (여러 개 동시 가능): // - 텍스처 스왑 : Texture Target + Texture Property + Active/Inactive Texture (MaterialPropertyBlock — 가볍고 원본 머티리얼 안 건드림) // - 오브젝트 on/off : Toggle Object (+ Invert) // - 그 외 무엇이든 : On Set (UnityEvent) — 활성 시 true, 비활성 시 false public class CharacterExpressionCustom : MonoBehaviour { [Serializable] public class BoolEvent : UnityEvent { } [Serializable] public class Effect { [Tooltip("표정(ExpressionData.CustomKeys)에서 참조할 키. 예: eyeHighlightOff, blush, sweat")] public string Key; [Header("텍스처 스왑 (선택)")] [Tooltip("텍스처를 바꿀 렌더러. 비우면 텍스처 스왑 안 함")] public Renderer TextureTarget; [Tooltip("바꿀 텍스처 프로퍼티. 이 프로젝트 BMAC_Toon 알베도=_Texture2D (URP Lit=_BaseMap)")] public string TextureProperty = "_Texture2D"; [Tooltip("활성 시 텍스처. 비우면 원본 유지")] public Texture ActiveTexture; [Tooltip("비활성(기본) 시 텍스처. 비우면 시작 시점의 원본으로 복원")] public Texture InactiveTexture; [Header("오브젝트 on/off (선택)")] public GameObject ToggleObject; [Tooltip("켜면 반전 — 활성 시 오브젝트를 '끈다' (예: 하이라이트 오브젝트 숨기기)")] public bool Invert; [Header("그 외 (선택)")] [Tooltip("활성 시 true, 비활성 시 false로 호출 — 인스펙터에서 무엇이든 연결")] public BoolEvent OnSet; // 런타임 내부 상태 [NonSerialized] public bool Active; [NonSerialized] public bool Captured; [NonSerialized] public int PropId; [NonSerialized] public Texture CapturedDefault; // 시작 시점의 원본 텍스처 } [SerializeField] private List _effects = new(); private MaterialPropertyBlock _mpb; private void Awake() { _mpb = new MaterialPropertyBlock(); foreach (var e in _effects) Capture(e); } private static void Capture(Effect e) { if (e.Captured) return; e.PropId = Shader.PropertyToID(e.TextureProperty); if (e.TextureTarget != null && e.TextureTarget.sharedMaterial != null) e.CapturedDefault = e.TextureTarget.sharedMaterial.GetTexture(e.PropId); e.Captured = true; } // 주어진 Key 집합에 맞춰 모든 Effect를 켜고 끈다 (집합에 없는 Effect는 비활성/기본으로). public void SetActiveKeys(ICollection keys) { foreach (var e in _effects) { bool want = keys != null && keys.Count > 0 && !string.IsNullOrEmpty(e.Key) && keys.Contains(e.Key); Apply(e, want); } } // 전부 기본(비활성)으로 되돌린다 — 대화 종료 시 public void ResetAll() { foreach (var e in _effects) Apply(e, false); } private void Apply(Effect e, bool active) { Capture(e); if (e.Active == active) return; // 상태 동일하면 스킵 e.Active = active; // 텍스처 스왑 if (e.TextureTarget != null) { var tex = active ? (e.ActiveTexture != null ? e.ActiveTexture : e.CapturedDefault) : (e.InactiveTexture != null ? e.InactiveTexture : e.CapturedDefault); e.TextureTarget.GetPropertyBlock(_mpb); // 기존 MPB 값 보존 _mpb.SetTexture(e.PropId, tex); e.TextureTarget.SetPropertyBlock(_mpb); } // 오브젝트 on/off if (e.ToggleObject != null) e.ToggleObject.SetActive(active ^ e.Invert); // 그 외 e.OnSet?.Invoke(active); } }