인터랙션 하이라이트 기능 추가, 인터랙션 인터페이스 일부 변경

This commit is contained in:
Jeonghyeon Ha 2025-08-14 13:46:36 +09:00
parent 0444b84249
commit 2d4edba36c
10 changed files with 133 additions and 61 deletions

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using UnityEngine; using UnityEngine;
namespace DDD namespace DDD
@ -15,21 +16,27 @@ public enum InteractionType : uint
public interface IInteractable public interface IInteractable
{ {
bool CanInteract(); bool CanInteract();
bool OnInteracted(IInteractor interactor, ScriptableObject interactionPayloadSo = null); bool OnInteracted(IInteractor interactor, ScriptableObject payloadSo = null);
InteractionType GetInteractionType(); InteractionType GetInteractionType();
GameObject GetInteractableGameObject(); GameObject GetInteractableGameObject();
void InitializeInteraction(InteractionType interactionType); void InitializeInteraction(InteractionType interactionType);
float GetRequiredHoldTime(); float GetRequiredHoldTime();
string GetInteractionMessageKey(); string GetInteractionMessageKey();
} }
public interface IInteractor public interface IInteractor
{ {
GameObject GetInteractorGameObject(); GameObject GetInteractorGameObject();
IInteractable GetFocusedInteractable();
bool CanSolveInteractionType(InteractionType interactionType);
bool CanInteractTo(IInteractable interactable,
ScriptableObject payloadSo = null);
} }
public interface IInteractionSolver public interface IInteractionSolver
{ {
bool ExecuteInteraction(IInteractor interactor, IInteractable interactable, ScriptableObject interactionPayloadSo = null); bool ExecuteInteraction(IInteractor interactor, IInteractable interactable, ScriptableObject payloadSo = null);
bool CanExecuteInteraction(); bool CanExecuteInteraction(IInteractor interactor = null, IInteractable interactable = null,
ScriptableObject payloadSo = null);
} }
} }

View File

@ -0,0 +1,20 @@
using UnityEngine;
namespace DDD
{
public class PlayerManager : Singleton<PlayerManager>
{
private GameObject _player;
public void RegisterPlayer(GameObject player)
{
_player = player;
Debug.Log($"Player registered: {player.name}");
}
public GameObject GetPlayer()
{
return _player ? _player : null;
}
}
}

View File

@ -4,6 +4,10 @@ namespace DDD
{ {
public class RestaurantPlayerCharacter : RestaurantCharacter public class RestaurantPlayerCharacter : RestaurantCharacter
{ {
protected override void Awake()
{
base.Awake();
PlayerManager.Instance.RegisterPlayer(gameObject);
}
} }
} }

View File

@ -45,7 +45,7 @@ protected override void OnDestroy()
private void OnInteractPerformed(InputAction.CallbackContext context) private void OnInteractPerformed(InputAction.CallbackContext context)
{ {
if (_nearestInteractable == null || CanInteract(_nearestInteractable) == false) return; if (_nearestInteractable == null || CanInteractTo(_nearestInteractable) == false) return;
float requiredHoldTime = _nearestInteractable.GetRequiredHoldTime(); float requiredHoldTime = _nearestInteractable.GetRequiredHoldTime();
@ -71,7 +71,7 @@ protected override void OnNearestInteractableChanged(IInteractable newTarget)
{ {
if (newTarget != null) if (newTarget != null)
{ {
BroadcastShowUi(newTarget, CanInteract(newTarget), 0f); BroadcastShowUi(newTarget, CanInteractTo(newTarget), 0f);
} }
else else
{ {
@ -83,7 +83,7 @@ protected override void OnInteractionHoldProgress(float ratio)
{ {
if (_interactingTarget != null) if (_interactingTarget != null)
{ {
BroadcastShowUi(_interactingTarget, CanInteract(_interactingTarget), ratio); BroadcastShowUi(_interactingTarget, CanInteractTo(_interactingTarget), ratio);
} }
} }

View File

@ -7,7 +7,11 @@ public class RestaurantCharacter : MonoBehaviour, IGameCharacter, IInteractor
{ {
[EnumToggleButtons, SerializeField] protected InteractionType _interactionType; [EnumToggleButtons, SerializeField] protected InteractionType _interactionType;
protected virtual void Awake() { } RestaurantCharacterInteraction _interactionComponent;
protected virtual void Awake()
{
_interactionComponent = GetComponent<RestaurantCharacterInteraction>();
}
protected virtual void Start() protected virtual void Start()
{ {
@ -27,7 +31,22 @@ protected virtual void Start()
public GameObject GetInteractorGameObject() public GameObject GetInteractorGameObject()
{ {
return gameObject; return _interactionComponent.GetInteractorGameObject();
}
public IInteractable GetFocusedInteractable()
{
return _interactionComponent.GetFocusedInteractable();
}
public bool CanSolveInteractionType(InteractionType interactionType)
{
return _interactionComponent.CanSolveInteractionType(interactionType);
}
public bool CanInteractTo(IInteractable interactable, ScriptableObject payloadSo = null)
{
return _interactionComponent.CanInteractTo(interactable, payloadSo);
} }
} }
} }

View File

@ -33,7 +33,7 @@ protected virtual void Update()
if (_isInteracting) if (_isInteracting)
{ {
if (_nearestInteractable != _interactingTarget || CanInteract(_interactingTarget) == false) if (_nearestInteractable != _interactingTarget || CanInteractTo(_interactingTarget) == false)
{ {
ResetInteractionState(); ResetInteractionState();
return; return;
@ -83,7 +83,7 @@ protected IInteractable GetNearestInteractable()
if (col.TryGetComponent<IInteractable>(out var interactable) == false) continue; if (col.TryGetComponent<IInteractable>(out var interactable) == false) continue;
var type = interactable.GetInteractionType(); var type = interactable.GetInteractionType();
if (ContainsSolverForType(type) == false) continue; if (CanSolveInteractionType(type) == false) continue;
float distance = Vector3.Distance(transform.position, col.transform.position); float distance = Vector3.Distance(transform.position, col.transform.position);
if (distance < closestDistance) if (distance < closestDistance)
@ -100,6 +100,17 @@ public virtual void Invoke(RestaurantInteractionEvent evt) { }
public GameObject GetInteractorGameObject() => gameObject; public GameObject GetInteractorGameObject() => gameObject;
public IInteractionSolver GetInteractionSolver(InteractionType interactionType)
{
TryGetSolverForType(interactionType, out var solver);
return solver;
}
public IInteractable GetFocusedInteractable()
{
return _nearestInteractable;
}
private bool TryGetSolverFor(IInteractable interactable, out IInteractionSolver solver) private bool TryGetSolverFor(IInteractable interactable, out IInteractionSolver solver)
{ {
solver = null; solver = null;
@ -124,7 +135,7 @@ private bool TryGetSolverForType(InteractionType type, out IInteractionSolver so
return solver != null; return solver != null;
} }
private bool ContainsSolverForType(InteractionType type) public bool CanSolveInteractionType(InteractionType type)
{ {
if (_cachedSolvers.TryGetValue(type, out var cachedSolver)) return cachedSolver != null; if (_cachedSolvers.TryGetValue(type, out var cachedSolver)) return cachedSolver != null;
@ -137,13 +148,12 @@ private bool ContainsSolverForType(InteractionType type)
return solver != null; return solver != null;
} }
protected bool CanInteract(IInteractable interactable) public bool CanInteractTo(IInteractable interactable, ScriptableObject payloadSo = null)
{ {
if (interactable == null) return false; if (interactable == null) return false;
if (interactable.CanInteract() == false) return false; if (interactable.CanInteract() == false) return false;
if (TryGetSolverFor(interactable, out var solver) == false) return false; if (TryGetSolverFor(interactable, out var solver) == false) return false;
return solver.CanExecuteInteraction(this, interactable, payloadSo);
return solver.CanExecuteInteraction();
} }
} }
} }

View File

@ -36,50 +36,61 @@ public class InteractableHighlight : MonoBehaviour
{InteractionOutlineType.None, new InteractionOutlineData() {Color = Color.clear, Width = 0.5f, Opacity = 0f}} {InteractionOutlineType.None, new InteractionOutlineData() {Color = Color.clear, Width = 0.5f, Opacity = 0f}}
}; };
private float OpacityMultiply = 1.0f; private HighlightEffect _highlightComponent;
private HighlightEffect highlight; private RestaurantInteractionComponent _interactionComponent;
private RestaurantInteractionComponent interaction; private IInteractor _interactor;
private void Awake() private void Awake()
{ {
// Cache HighlightEffect // Cache HighlightEffect
highlight = GetComponent<HighlightPlus.HighlightEffect>(); _highlightComponent = GetComponent<HighlightPlus.HighlightEffect>();
interaction = GetComponent<RestaurantInteractionComponent>(); _interactionComponent = GetComponent<RestaurantInteractionComponent>();
// highlightEffect에 alphaCutoff, constantWidth, combineMeshes, outlineQuality, outlineIndependent 등의 필수 옵션이 켜져있는지 확인 // highlightEffect에 alphaCutoff, constantWidth, combineMeshes, outlineQuality, outlineIndependent 등의 필수 옵션이 켜져있는지 확인
highlight.alphaCutOff = 0.5f; _highlightComponent.alphaCutOff = 0.5f;
highlight.combineMeshes = true; _highlightComponent.combineMeshes = true;
highlight.constantWidth = true; _highlightComponent.constantWidth = true;
highlight.outlineQuality = QualityLevel.Highest; _highlightComponent.outlineQuality = QualityLevel.Highest;
highlight.outlineIndependent = true; _highlightComponent.outlineIndependent = true;
highlight.outlineBlurPasses = 1; _highlightComponent.outlineBlurPasses = 1;
highlight.outlineSharpness = 8; _highlightComponent.outlineSharpness = 8;
} }
private void Update() private void Update()
{ {
FetchPlayerInteractorComponent();
var currentType = GetCurrentOutlineType(); var currentType = GetCurrentOutlineType();
ApplyOutlineType(currentType); ApplyOutlineType(currentType);
}
private void FetchPlayerInteractorComponent()
{
if (_interactor == null)
{
var player = PlayerManager.Instance.GetPlayer();
_interactor = player?.GetComponent<IInteractor>();
}
} }
private InteractionOutlineType GetCurrentOutlineType() private InteractionOutlineType GetCurrentOutlineType()
{ {
// interaction이 null이거나 컴포넌트가 비활성화된 경우 // interaction이 null이거나 컴포넌트가 비활성화된 경우
if (interaction == null || !interaction.enabled) if (!_interactionComponent || !_interactionComponent.enabled)
return InteractionOutlineType.None; return InteractionOutlineType.None;
// IInteractable 인터페이스로 캐스팅하여 상태 확인 // IInteractable 인터페이스로 캐스팅하여 상태 확인
var interactable = interaction as IInteractable; if (_interactionComponent is not IInteractable interactable)
if (interactable == null)
return InteractionOutlineType.None; return InteractionOutlineType.None;
try try
{ {
// 상호작용 불가능한 경우 // 상호작용 불가능한 경우
if (!interactable.CanInteract()) if (CanExecuteInteraction() == false)
return InteractionOutlineType.Unavailable; return InteractionOutlineType.Unavailable;
// TODO: 여기에 추가 상태 로직을 구현 // TODO: 여기에 추가 상태 로직을 구현
// - isHovered, isFocused 등의 상태를 체크
// - isObjective 등의 퀘스트 상태를 체크 // - isObjective 등의 퀘스트 상태를 체크
// 플레이어가 현재 이 오브젝트를 포커스 중인지 확인 // 플레이어가 현재 이 오브젝트를 포커스 중인지 확인
@ -93,7 +104,6 @@ private InteractionOutlineType GetCurrentOutlineType()
} }
catch catch
{ {
// CanInteract() 호출 중 예외 발생 시 안전하게 처리
return InteractionOutlineType.Unavailable; return InteractionOutlineType.Unavailable;
} }
} }
@ -108,7 +118,7 @@ private void ApplyOutlineType(InteractionOutlineType type)
lastAppliedType = type; lastAppliedType = type;
if (highlight == null) if (!_highlightComponent)
return; return;
// OutlineData에서 해당 타입의 스타일 가져오기 // OutlineData에서 해당 타입의 스타일 가져오기
@ -121,35 +131,35 @@ private void ApplyOutlineType(InteractionOutlineType type)
// HighlightEffect에 적용 // HighlightEffect에 적용
if (type == InteractionOutlineType.None) if (type == InteractionOutlineType.None)
{ {
highlight.highlighted = false; _highlightComponent.highlighted = false;
highlight.outline = 0; _highlightComponent.outline = 0;
} }
else else
{ {
highlight.highlighted = true; _highlightComponent.highlighted = true;
highlight.outlineColor = data.Color; _highlightComponent.outlineColor = data.Color;
highlight.outlineWidth = data.Width; _highlightComponent.outlineWidth = data.Width;
highlight.outline = data.Opacity * OpacityMultiply; _highlightComponent.outline = data.Opacity * opacityMultiply;
} }
} }
private bool IsPlayerFocusing() private bool IsPlayerFocusing()
{ {
// 방법 1: 싱글톤 패턴의 플레이어 매니저 사용 return _interactor?.GetFocusedInteractable() == _interactionComponent;
// if (PlayerManager.Instance != null)
// {
// return PlayerManager.Instance.CurrentFocusTarget == gameObject;
// }
// 방법 3: 정적 참조를 통한 현재 포커스 대상 확인
// if (InteractionSystem.CurrentFocusedObject == gameObject)
// {
// return true;
// }
return false;
} }
private bool CanExecuteInteraction()
{
if (_interactionComponent.CanInteract() == false)
{
return false;
}
if (_interactor == null)
{
return false;
}
return _interactor.CanInteractTo(_interactionComponent);
}
} }
} }

View File

@ -13,14 +13,14 @@ public bool CanInteract()
return true; return true;
} }
public bool OnInteracted(IInteractor interactor, ScriptableObject interactionPayloadSo = null) public bool OnInteracted(IInteractor interactor, ScriptableObject payloadSo = null)
{ {
if (CanInteract() == false) if (CanInteract() == false)
{ {
return false; return false;
} }
bool interactionResult = RestaurantInteractionEvents.RestaurantInteraction.RequestInteraction(interactor.GetInteractorGameObject(), bool interactionResult = RestaurantInteractionEvents.RestaurantInteraction.RequestInteraction(interactor.GetInteractorGameObject(),
GetInteractableGameObject(), GetInteractionType(), interactionPayloadSo, true); GetInteractableGameObject(), GetInteractionType(), payloadSo, true);
return interactionResult; return interactionResult;
} }

View File

@ -4,7 +4,7 @@ namespace DDD
{ {
public class RestaurantManagementUiEventSolver : MonoBehaviour, IInteractionSolver public class RestaurantManagementUiEventSolver : MonoBehaviour, IInteractionSolver
{ {
public bool ExecuteInteraction(IInteractor interactor, IInteractable interactable, ScriptableObject interactionPayloadSo = null) public bool ExecuteInteraction(IInteractor interactor, IInteractable interactable, ScriptableObject payloadSo = null)
{ {
if (CanExecuteInteraction() == false) return false; if (CanExecuteInteraction() == false) return false;
@ -14,7 +14,8 @@ public bool ExecuteInteraction(IInteractor interactor, IInteractable interactabl
return true; return true;
} }
public bool CanExecuteInteraction() public bool CanExecuteInteraction(IInteractor interactor = null, IInteractable interactable = null,
ScriptableObject payloadSo = null)
{ {
GameFlowState currentGameFlowState = GameFlowManager.Instance.GameFlowDataSo.CurrentGameState; GameFlowState currentGameFlowState = GameFlowManager.Instance.GameFlowDataSo.CurrentGameState;
return currentGameFlowState == GameFlowState.ReadyForRestaurant; return currentGameFlowState == GameFlowState.ReadyForRestaurant;

View File

@ -17,7 +17,7 @@ private async Task Initialize()
_restaurantManagementSo = await AssetManager.LoadAsset<RestaurantManagementSo>(DataConstants.RestaurantManagementSo); _restaurantManagementSo = await AssetManager.LoadAsset<RestaurantManagementSo>(DataConstants.RestaurantManagementSo);
} }
public bool ExecuteInteraction(IInteractor interactor, IInteractable interactable, ScriptableObject interactionPayloadSo = null) public bool ExecuteInteraction(IInteractor interactor, IInteractable interactable, ScriptableObject payloadSo = null)
{ {
if (CanExecuteInteraction() == false) return false; if (CanExecuteInteraction() == false) return false;
@ -25,7 +25,8 @@ public bool ExecuteInteraction(IInteractor interactor, IInteractable interactabl
return true; return true;
} }
public bool CanExecuteInteraction() public bool CanExecuteInteraction(IInteractor interactor = null, IInteractable interactable = null,
ScriptableObject payloadSo = null)
{ {
GameFlowState currentGameFlowState = GameFlowManager.Instance.GameFlowDataSo.CurrentGameState; GameFlowState currentGameFlowState = GameFlowManager.Instance.GameFlowDataSo.CurrentGameState;
return currentGameFlowState == GameFlowState.ReadyForRestaurant && _restaurantManagementSo.IsOpenable(); return currentGameFlowState == GameFlowState.ReadyForRestaurant && _restaurantManagementSo.IsOpenable();