2025-08-13 12:21:35 +00:00
|
|
|
using System.Collections.Generic;
|
|
|
|
using UnityEngine;
|
2025-08-14 05:08:10 +00:00
|
|
|
using Sirenix.OdinInspector;
|
2025-08-13 12:21:35 +00:00
|
|
|
using QualityLevel = HighlightPlus.QualityLevel;
|
|
|
|
using HighlightPlus;
|
|
|
|
|
|
|
|
namespace DDD
|
|
|
|
{
|
|
|
|
public enum InteractionOutlineType
|
|
|
|
{
|
|
|
|
None,
|
|
|
|
Focused,
|
|
|
|
Available,
|
|
|
|
Unavailable,
|
|
|
|
Objective
|
|
|
|
}
|
|
|
|
|
2025-08-14 05:08:10 +00:00
|
|
|
[System.Serializable]
|
2025-08-13 12:21:35 +00:00
|
|
|
public struct InteractionOutlineData
|
|
|
|
{
|
|
|
|
public Color Color;
|
2025-08-14 05:08:10 +00:00
|
|
|
[Range(0.1f, 5f)]
|
2025-08-13 12:21:35 +00:00
|
|
|
public float Width;
|
2025-08-14 05:08:10 +00:00
|
|
|
[Range(0f, 1f)]
|
2025-08-13 12:21:35 +00:00
|
|
|
public float Opacity;
|
|
|
|
}
|
|
|
|
|
|
|
|
[RequireComponent(typeof(HighlightEffect))]
|
|
|
|
[RequireComponent(typeof(RestaurantInteractionComponent))]
|
|
|
|
[AddComponentMenu("DDD/Interaction/InteractableHighlight")]
|
|
|
|
public class InteractableHighlight : MonoBehaviour
|
|
|
|
{
|
2025-08-14 05:08:10 +00:00
|
|
|
[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;
|
2025-08-13 12:21:35 +00:00
|
|
|
|
2025-08-14 05:08:10 +00:00
|
|
|
// 런타임에 생성되는 딕셔너리
|
|
|
|
private Dictionary<InteractionOutlineType, InteractionOutlineData> _outlineData;
|
|
|
|
|
|
|
|
// Private runtime variables
|
|
|
|
private float _opacityMultiply = 1.0f;
|
|
|
|
private float _breathingTime;
|
2025-08-14 04:46:36 +00:00
|
|
|
private HighlightEffect _highlightComponent;
|
|
|
|
private RestaurantInteractionComponent _interactionComponent;
|
|
|
|
private IInteractor _interactor;
|
2025-08-14 05:08:10 +00:00
|
|
|
private InteractionOutlineType _lastAppliedType = InteractionOutlineType.None;
|
|
|
|
|
2025-08-13 12:21:35 +00:00
|
|
|
private void Awake()
|
|
|
|
{
|
2025-08-14 05:08:10 +00:00
|
|
|
// OutlineData 딕셔너리 초기화
|
|
|
|
InitializeOutlineData();
|
|
|
|
|
2025-08-13 12:21:35 +00:00
|
|
|
// Cache HighlightEffect
|
2025-08-14 04:46:36 +00:00
|
|
|
_highlightComponent = GetComponent<HighlightPlus.HighlightEffect>();
|
|
|
|
_interactionComponent = GetComponent<RestaurantInteractionComponent>();
|
2025-08-13 12:21:35 +00:00
|
|
|
|
2025-08-14 05:08:10 +00:00
|
|
|
// HighlightEffect 설정 적용
|
|
|
|
ApplyHighlightSettings();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void InitializeOutlineData()
|
|
|
|
{
|
|
|
|
_outlineData = new Dictionary<InteractionOutlineType, InteractionOutlineData>
|
|
|
|
{
|
|
|
|
{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;
|
2025-08-13 12:21:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private void Update()
|
|
|
|
{
|
2025-08-14 04:46:36 +00:00
|
|
|
FetchPlayerInteractorComponent();
|
|
|
|
|
2025-08-13 12:21:35 +00:00
|
|
|
var currentType = GetCurrentOutlineType();
|
2025-08-14 05:08:10 +00:00
|
|
|
_currentOutlineType = currentType; // 디버그용
|
2025-08-14 04:46:36 +00:00
|
|
|
|
2025-08-14 05:08:10 +00:00
|
|
|
if (_enableBreathingEffect)
|
|
|
|
{
|
|
|
|
UpdateBreathingEffect(currentType);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
_opacityMultiply = 1f;
|
|
|
|
_breathingTime = 0f;
|
|
|
|
}
|
2025-08-14 04:46:36 +00:00
|
|
|
|
2025-08-14 05:08:10 +00:00
|
|
|
_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;
|
|
|
|
}
|
2025-08-14 04:46:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private void FetchPlayerInteractorComponent()
|
|
|
|
{
|
|
|
|
if (_interactor == null)
|
|
|
|
{
|
|
|
|
var player = PlayerManager.Instance.GetPlayer();
|
|
|
|
_interactor = player?.GetComponent<IInteractor>();
|
|
|
|
}
|
2025-08-13 12:21:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private InteractionOutlineType GetCurrentOutlineType()
|
|
|
|
{
|
|
|
|
// interaction이 null이거나 컴포넌트가 비활성화된 경우
|
2025-08-14 04:46:36 +00:00
|
|
|
if (!_interactionComponent || !_interactionComponent.enabled)
|
2025-08-13 12:21:35 +00:00
|
|
|
return InteractionOutlineType.None;
|
|
|
|
|
|
|
|
// IInteractable 인터페이스로 캐스팅하여 상태 확인
|
2025-08-14 04:46:36 +00:00
|
|
|
if (_interactionComponent is not IInteractable interactable)
|
2025-08-13 12:21:35 +00:00
|
|
|
return InteractionOutlineType.None;
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
// 상호작용 불가능한 경우
|
2025-08-14 04:46:36 +00:00
|
|
|
if (CanExecuteInteraction() == false)
|
2025-08-13 12:21:35 +00:00
|
|
|
return InteractionOutlineType.Unavailable;
|
|
|
|
|
|
|
|
// TODO: 여기에 추가 상태 로직을 구현
|
|
|
|
// - isObjective 등의 퀘스트 상태를 체크
|
|
|
|
|
|
|
|
// 플레이어가 현재 이 오브젝트를 포커스 중인지 확인
|
|
|
|
if (IsPlayerFocusing())
|
|
|
|
{
|
|
|
|
return InteractionOutlineType.Focused;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 기본적으로 상호작용 가능한 상태
|
|
|
|
return InteractionOutlineType.Available;
|
|
|
|
}
|
|
|
|
catch
|
|
|
|
{
|
|
|
|
return InteractionOutlineType.Unavailable;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void ApplyOutlineType(InteractionOutlineType type)
|
|
|
|
{
|
|
|
|
// 같은 타입이면 불필요한 프로퍼티 세팅을 피함
|
2025-08-14 05:08:10 +00:00
|
|
|
if (_lastAppliedType == type && _enableBreathingEffect == false)
|
2025-08-13 12:21:35 +00:00
|
|
|
return;
|
|
|
|
|
2025-08-14 05:08:10 +00:00
|
|
|
_lastAppliedType = type;
|
2025-08-13 12:21:35 +00:00
|
|
|
|
2025-08-14 04:46:36 +00:00
|
|
|
if (!_highlightComponent)
|
2025-08-13 12:21:35 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
// OutlineData에서 해당 타입의 스타일 가져오기
|
2025-08-14 05:08:10 +00:00
|
|
|
if (!_outlineData.TryGetValue(type, out var data))
|
2025-08-13 12:21:35 +00:00
|
|
|
{
|
|
|
|
// 데이터가 없으면 None 타입 적용
|
2025-08-14 05:08:10 +00:00
|
|
|
data = _outlineData[InteractionOutlineType.None];
|
2025-08-13 12:21:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// HighlightEffect에 적용
|
|
|
|
if (type == InteractionOutlineType.None)
|
|
|
|
{
|
2025-08-14 04:46:36 +00:00
|
|
|
_highlightComponent.highlighted = false;
|
|
|
|
_highlightComponent.outline = 0;
|
2025-08-13 12:21:35 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2025-08-14 04:46:36 +00:00
|
|
|
_highlightComponent.highlighted = true;
|
2025-08-14 05:08:10 +00:00
|
|
|
Color color = data.Color;
|
|
|
|
// color.a = data.Opacity * _opacityMultiply;
|
|
|
|
color *= _opacityMultiply;
|
|
|
|
_highlightComponent.outlineColor = color;
|
2025-08-14 04:46:36 +00:00
|
|
|
_highlightComponent.outlineWidth = data.Width;
|
2025-08-14 05:08:10 +00:00
|
|
|
_highlightComponent.outline = 1;
|
2025-08-13 12:21:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private bool IsPlayerFocusing()
|
|
|
|
{
|
2025-08-14 04:46:36 +00:00
|
|
|
return _interactor?.GetFocusedInteractable() == _interactionComponent;
|
2025-08-13 12:21:35 +00:00
|
|
|
}
|
2025-08-14 04:46:36 +00:00
|
|
|
|
|
|
|
private bool CanExecuteInteraction()
|
|
|
|
{
|
|
|
|
if (_interactionComponent.CanInteract() == false)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
2025-08-13 12:21:35 +00:00
|
|
|
|
2025-08-14 04:46:36 +00:00
|
|
|
if (_interactor == null)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return _interactor.CanInteractTo(_interactionComponent);
|
|
|
|
}
|
2025-08-14 05:08:10 +00:00
|
|
|
|
|
|
|
// 런타임에 스타일 변경을 위한 메서드들
|
|
|
|
[Button("Refresh Outline Data")]
|
|
|
|
private void RefreshOutlineData()
|
|
|
|
{
|
|
|
|
InitializeOutlineData();
|
|
|
|
}
|
|
|
|
|
|
|
|
[Button("Apply Highlight Settings")]
|
|
|
|
private void ApplyHighlightSettingsButton()
|
|
|
|
{
|
|
|
|
ApplyHighlightSettings();
|
|
|
|
}
|
2025-08-13 12:21:35 +00:00
|
|
|
}
|
|
|
|
}
|