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

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.Collections.Generic;
using UnityEngine;
namespace DDD
@ -15,21 +16,27 @@ public enum InteractionType : uint
public interface IInteractable
{
bool CanInteract();
bool OnInteracted(IInteractor interactor, ScriptableObject interactionPayloadSo = null);
bool OnInteracted(IInteractor interactor, ScriptableObject payloadSo = null);
InteractionType GetInteractionType();
GameObject GetInteractableGameObject();
void InitializeInteraction(InteractionType interactionType);
float GetRequiredHoldTime();
string GetInteractionMessageKey();
}
public interface IInteractor
{
GameObject GetInteractorGameObject();
IInteractable GetFocusedInteractable();
bool CanSolveInteractionType(InteractionType interactionType);
bool CanInteractTo(IInteractable interactable,
ScriptableObject payloadSo = null);
}
public interface IInteractionSolver
{
bool ExecuteInteraction(IInteractor interactor, IInteractable interactable, ScriptableObject interactionPayloadSo = null);
bool CanExecuteInteraction();
bool ExecuteInteraction(IInteractor interactor, IInteractable interactable, ScriptableObject payloadSo = null);
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
{
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)
{
if (_nearestInteractable == null || CanInteract(_nearestInteractable) == false) return;
if (_nearestInteractable == null || CanInteractTo(_nearestInteractable) == false) return;
float requiredHoldTime = _nearestInteractable.GetRequiredHoldTime();
@ -71,7 +71,7 @@ protected override void OnNearestInteractableChanged(IInteractable newTarget)
{
if (newTarget != null)
{
BroadcastShowUi(newTarget, CanInteract(newTarget), 0f);
BroadcastShowUi(newTarget, CanInteractTo(newTarget), 0f);
}
else
{
@ -83,7 +83,7 @@ protected override void OnInteractionHoldProgress(float ratio)
{
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;
protected virtual void Awake() { }
RestaurantCharacterInteraction _interactionComponent;
protected virtual void Awake()
{
_interactionComponent = GetComponent<RestaurantCharacterInteraction>();
}
protected virtual void Start()
{
@ -27,7 +31,22 @@ protected virtual void Start()
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 (_nearestInteractable != _interactingTarget || CanInteract(_interactingTarget) == false)
if (_nearestInteractable != _interactingTarget || CanInteractTo(_interactingTarget) == false)
{
ResetInteractionState();
return;
@ -83,7 +83,7 @@ protected IInteractable GetNearestInteractable()
if (col.TryGetComponent<IInteractable>(out var interactable) == false) continue;
var type = interactable.GetInteractionType();
if (ContainsSolverForType(type) == false) continue;
if (CanSolveInteractionType(type) == false) continue;
float distance = Vector3.Distance(transform.position, col.transform.position);
if (distance < closestDistance)
@ -99,7 +99,18 @@ protected IInteractable GetNearestInteractable()
public virtual void Invoke(RestaurantInteractionEvent evt) { }
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)
{
solver = null;
@ -124,7 +135,7 @@ private bool TryGetSolverForType(InteractionType type, out IInteractionSolver so
return solver != null;
}
private bool ContainsSolverForType(InteractionType type)
public bool CanSolveInteractionType(InteractionType type)
{
if (_cachedSolvers.TryGetValue(type, out var cachedSolver)) return cachedSolver != null;
@ -137,13 +148,12 @@ private bool ContainsSolverForType(InteractionType type)
return solver != null;
}
protected bool CanInteract(IInteractable interactable)
public bool CanInteractTo(IInteractable interactable, ScriptableObject payloadSo = null)
{
if (interactable == null) return false;
if (interactable.CanInteract() == false) return false;
if (TryGetSolverFor(interactable, out var solver) == false) return false;
return solver.CanExecuteInteraction();
return solver.CanExecuteInteraction(this, interactable, payloadSo);
}
}
}

View File

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

View File

@ -4,7 +4,7 @@ namespace DDD
{
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;
@ -14,7 +14,8 @@ public bool ExecuteInteraction(IInteractor interactor, IInteractable interactabl
return true;
}
public bool CanExecuteInteraction()
public bool CanExecuteInteraction(IInteractor interactor = null, IInteractable interactable = null,
ScriptableObject payloadSo = null)
{
GameFlowState currentGameFlowState = GameFlowManager.Instance.GameFlowDataSo.CurrentGameState;
return currentGameFlowState == GameFlowState.ReadyForRestaurant;

View File

@ -17,7 +17,7 @@ private async Task Initialize()
_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;
@ -25,7 +25,8 @@ public bool ExecuteInteraction(IInteractor interactor, IInteractable interactabl
return true;
}
public bool CanExecuteInteraction()
public bool CanExecuteInteraction(IInteractor interactor = null, IInteractable interactable = null,
ScriptableObject payloadSo = null)
{
GameFlowState currentGameFlowState = GameFlowManager.Instance.GameFlowDataSo.CurrentGameState;
return currentGameFlowState == GameFlowState.ReadyForRestaurant && _restaurantManagementSo.IsOpenable();