diff --git a/Assets/_DDD/_Scripts/RestaurantEvent/InteractableHighlight.cs b/Assets/_DDD/_Scripts/RestaurantEvent/InteractableHighlight.cs index e98a86ffc..8c77ccd52 100644 --- a/Assets/_DDD/_Scripts/RestaurantEvent/InteractableHighlight.cs +++ b/Assets/_DDD/_Scripts/RestaurantEvent/InteractableHighlight.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using UnityEngine; +using Sirenix.OdinInspector; using QualityLevel = HighlightPlus.QualityLevel; - using HighlightPlus; namespace DDD @@ -15,10 +15,13 @@ public enum InteractionOutlineType Objective } + [System.Serializable] public struct InteractionOutlineData { public Color Color; + [Range(0.1f, 5f)] public float Width; + [Range(0f, 1f)] public float Opacity; } @@ -27,32 +30,77 @@ public struct InteractionOutlineData [AddComponentMenu("DDD/Interaction/InteractableHighlight")] public class InteractableHighlight : MonoBehaviour { - public static Dictionary OutlineData = new() - { - {InteractionOutlineType.Available, new InteractionOutlineData() {Color = Color.white, Width = 0.5f, Opacity = 1f}}, - {InteractionOutlineType.Focused, new InteractionOutlineData() {Color = Color.yellow, Width = 0.5f, Opacity = 1f}}, - {InteractionOutlineType.Unavailable, new InteractionOutlineData() {Color = Color.gray, Width = 0.5f, Opacity = 1f}}, - {InteractionOutlineType.Objective, new InteractionOutlineData() {Color = Color.cyan, Width = 0.5f, Opacity = 1f}}, - {InteractionOutlineType.None, new InteractionOutlineData() {Color = Color.clear, Width = 0.5f, Opacity = 0f}} - }; + [Title("Outline Styles")] + [SerializeField] private InteractionOutlineData _availableStyle = new() {Color = Color.white, Width = 1f, Opacity = 1f}; + [SerializeField] private InteractionOutlineData _focusedStyle = new() {Color = Color.yellow, Width = 1f, Opacity = 1f}; + [SerializeField] private InteractionOutlineData _unavailableStyle = new() {Color = Color.gray, Width = 0.5f, Opacity = 1f}; + [SerializeField] private InteractionOutlineData _objectiveStyle = new() {Color = Color.cyan, Width = 1f, Opacity = 1f}; + + [Title("Animation Settings")] + [SerializeField, Range(0.5f, 10f)] private float _breathingSpeed = 2f; + [SerializeField, Range(0.1f, 1f)] private float _breathingRange = 0.3f; + [SerializeField] private bool _enableBreathingEffect = true; + + [Title("Highlight Component Settings")] + [SerializeField, Range(0f, 1f)] private float _alphaCutOff = 0.5f; + [SerializeField] private bool _combineMeshes = true; + [SerializeField] private bool _constantWidth = true; + [SerializeField] private QualityLevel _outlineQuality = QualityLevel.Highest; + [SerializeField] private bool _outlineIndependent = true; + [SerializeField, Range(0, 4)] private int _outlineBlurPasses = 1; + [SerializeField, Range(1f, 20f)] private float _outlineSharpness = 8f; + + [Title("Debug")] + [SerializeField, ReadOnly] private InteractionOutlineType _currentOutlineType; + [SerializeField, ReadOnly] private float _currentOpacityMultiplier = 1f; + // 런타임에 생성되는 딕셔너리 + private Dictionary _outlineData; + + // Private runtime variables + private float _opacityMultiply = 1.0f; + private float _breathingTime; private HighlightEffect _highlightComponent; private RestaurantInteractionComponent _interactionComponent; private IInteractor _interactor; + private InteractionOutlineType _lastAppliedType = InteractionOutlineType.None; + private void Awake() { + // OutlineData 딕셔너리 초기화 + InitializeOutlineData(); + // Cache HighlightEffect _highlightComponent = GetComponent(); _interactionComponent = GetComponent(); - // highlightEffect에 alphaCutoff, constantWidth, combineMeshes, outlineQuality, outlineIndependent 등의 필수 옵션이 켜져있는지 확인 - _highlightComponent.alphaCutOff = 0.5f; - _highlightComponent.combineMeshes = true; - _highlightComponent.constantWidth = true; - _highlightComponent.outlineQuality = QualityLevel.Highest; - _highlightComponent.outlineIndependent = true; - _highlightComponent.outlineBlurPasses = 1; - _highlightComponent.outlineSharpness = 8; + // HighlightEffect 설정 적용 + ApplyHighlightSettings(); + } + + private void InitializeOutlineData() + { + _outlineData = new Dictionary + { + {InteractionOutlineType.Available, _availableStyle}, + {InteractionOutlineType.Focused, _focusedStyle}, + {InteractionOutlineType.Unavailable, _unavailableStyle}, + {InteractionOutlineType.Objective, _objectiveStyle}, + {InteractionOutlineType.None, new InteractionOutlineData() {Color = Color.clear, Width = 0.5f, Opacity = 0f}} + }; + } + + private void ApplyHighlightSettings() + { + if (!_highlightComponent) return; + + _highlightComponent.alphaCutOff = _alphaCutOff; + _highlightComponent.combineMeshes = _combineMeshes; + _highlightComponent.constantWidth = _constantWidth; + _highlightComponent.outlineQuality = _outlineQuality; + _highlightComponent.outlineIndependent = _outlineIndependent; + _highlightComponent.outlineBlurPasses = _outlineBlurPasses; + _highlightComponent.outlineSharpness = _outlineSharpness; } private void Update() @@ -60,9 +108,35 @@ private void Update() FetchPlayerInteractorComponent(); var currentType = GetCurrentOutlineType(); + _currentOutlineType = currentType; // 디버그용 + + if (_enableBreathingEffect) + { + UpdateBreathingEffect(currentType); + } + else + { + _opacityMultiply = 1f; + _breathingTime = 0f; + } + + _currentOpacityMultiplier = _opacityMultiply; // 디버그용 ApplyOutlineType(currentType); - - + } + + private void UpdateBreathingEffect(InteractionOutlineType type) + { + if (type == InteractionOutlineType.Available || type == InteractionOutlineType.Focused) + { + _breathingTime += Time.deltaTime; + float sin01 = (Mathf.Sin(_breathingTime * _breathingSpeed) + 1) / 2; + _opacityMultiply = 1f - _breathingRange + sin01 * _breathingRange; + } + else + { + _opacityMultiply = 1f; + _breathingTime = 0f; + } } private void FetchPlayerInteractorComponent() @@ -108,24 +182,22 @@ private InteractionOutlineType GetCurrentOutlineType() } } - private InteractionOutlineType lastAppliedType = InteractionOutlineType.None; - private void ApplyOutlineType(InteractionOutlineType type) { // 같은 타입이면 불필요한 프로퍼티 세팅을 피함 - if (lastAppliedType == type) + if (_lastAppliedType == type && _enableBreathingEffect == false) return; - lastAppliedType = type; + _lastAppliedType = type; if (!_highlightComponent) return; // OutlineData에서 해당 타입의 스타일 가져오기 - if (!OutlineData.TryGetValue(type, out var data)) + if (!_outlineData.TryGetValue(type, out var data)) { // 데이터가 없으면 None 타입 적용 - data = OutlineData[InteractionOutlineType.None]; + data = _outlineData[InteractionOutlineType.None]; } // HighlightEffect에 적용 @@ -137,9 +209,12 @@ private void ApplyOutlineType(InteractionOutlineType type) else { _highlightComponent.highlighted = true; - _highlightComponent.outlineColor = data.Color; + Color color = data.Color; + // color.a = data.Opacity * _opacityMultiply; + color *= _opacityMultiply; + _highlightComponent.outlineColor = color; _highlightComponent.outlineWidth = data.Width; - _highlightComponent.outline = data.Opacity * opacityMultiply; + _highlightComponent.outline = 1; } } @@ -161,5 +236,18 @@ private bool CanExecuteInteraction() } return _interactor.CanInteractTo(_interactionComponent); } + + // 런타임에 스타일 변경을 위한 메서드들 + [Button("Refresh Outline Data")] + private void RefreshOutlineData() + { + InitializeOutlineData(); + } + + [Button("Apply Highlight Settings")] + private void ApplyHighlightSettingsButton() + { + ApplyHighlightSettings(); + } } } \ No newline at end of file