AI 비헤이비어 트리 액션 및 컨디셔널 구현
RestaurantOrderAvailable, StartRestaurantOrder, MoveToInteractionTarget
This commit is contained in:
parent
8f56d99bf7
commit
4ef63ec9a9
BIN
Assets/_DDD/_Addressables/AI/Customer/Subtree/CustomerDefault.asset
(Stored with Git LFS)
BIN
Assets/_DDD/_Addressables/AI/Customer/Subtree/CustomerDefault.asset
(Stored with Git LFS)
Binary file not shown.
3
Assets/_DDD/_Scripts/AI.meta
Normal file
3
Assets/_DDD/_Scripts/AI.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c31792c3491f44878a9a5e8ee59504cf
|
||||
timeCreated: 1755770768
|
3
Assets/_DDD/_Scripts/AI/Common.meta
Normal file
3
Assets/_DDD/_Scripts/AI/Common.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 49c654aa3aa94cb9928e2d161cad789a
|
||||
timeCreated: 1755770768
|
15
Assets/_DDD/_Scripts/AI/Common/IAISharedBlackboard.cs
Normal file
15
Assets/_DDD/_Scripts/AI/Common/IAISharedBlackboard.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace DDD
|
||||
{
|
||||
/// <summary>
|
||||
/// 공용 AI 블랙보드 인터페이스.
|
||||
/// - 다양한 캐릭터 AI에서 공통으로 참조하는 현재 인터랙션 타겟만 정의합니다.
|
||||
/// - 필요 시 키-값 확장을 고려하되, 현재는 최소 요구만 충족합니다.
|
||||
/// </summary>
|
||||
public interface IAISharedBlackboard
|
||||
{
|
||||
void SetCurrentInteractionTarget(GameObject targetGameObject);
|
||||
GameObject GetCurrentInteractionTarget();
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 62510fba6cb047419ca463dc523ae536
|
||||
timeCreated: 1755770768
|
3
Assets/_DDD/_Scripts/RestaurantCharacter/AI/Common.meta
Normal file
3
Assets/_DDD/_Scripts/RestaurantCharacter/AI/Common.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cfc8e456b2134c4a87b9fcd0d385cf1d
|
||||
timeCreated: 1755767029
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ab824a41c52d4cca8cafb1fc96d5d8e7
|
||||
timeCreated: 1755769401
|
@ -0,0 +1,165 @@
|
||||
using System;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks.Actions;
|
||||
using UnityEngine;
|
||||
|
||||
namespace DDD
|
||||
{
|
||||
/// <summary>
|
||||
/// IAiMovement를 이용해 인터랙션 타겟(주로 RestaurantInteractionComponent가 붙은 오브젝트)으로 이동하는 액션.
|
||||
/// - 타겟은 우선 블랙보드(IRestaurantCustomerBlackboard)에서 가져오고, 없으면 Interactor의 FocusedInteractable에서 가져옵니다.
|
||||
/// - 타겟에 RestaurantInteractionComponent가 있다면 가장 가까운 InteractionPoint를 목적지로 사용합니다.
|
||||
/// </summary>
|
||||
public class MoveToInteractionTarget : Opsive.BehaviorDesigner.Runtime.Tasks.Actions.Action
|
||||
{
|
||||
[Header("Target")]
|
||||
[Tooltip("타겟에 RestaurantInteractionComponent가 있을 때 InteractionPoints를 사용해 가장 가까운 지점으로 이동합니다.")]
|
||||
[SerializeField] private bool _useInteractionPoints = true;
|
||||
[Tooltip("타겟이 없을 때 즉시 실패할지 여부")]
|
||||
[SerializeField] private bool _failIfNoTarget = true;
|
||||
|
||||
[Header("Movement")]
|
||||
[Tooltip("목적지에 도달했다고 간주할 거리")]
|
||||
[SerializeField] private float _stoppingDistance = 0.5f;
|
||||
[Tooltip("이동 중 목적지를 재계산하는 주기(초). 0 이하면 재계산하지 않음")]
|
||||
[SerializeField] private float _repathInterval = 0.5f;
|
||||
|
||||
private IAiMovement _movement;
|
||||
private float _repathTimer;
|
||||
private Vector3 _currentDestination;
|
||||
private bool _started;
|
||||
private GameObject _resolvedTarget;
|
||||
|
||||
public override void OnStart()
|
||||
{
|
||||
_movement = gameObject.GetComponentInParent<IAiMovement>();
|
||||
_repathTimer = 0f;
|
||||
_started = false;
|
||||
_resolvedTarget = ResolveTargetFromContext();
|
||||
}
|
||||
|
||||
public override TaskStatus OnUpdate()
|
||||
{
|
||||
if (_movement == null)
|
||||
{
|
||||
return TaskStatus.Failure;
|
||||
}
|
||||
|
||||
// 매 프레임 타겟이 갱신될 수 있으므로 재해결 (필요 시 비용 줄일 수 있음)
|
||||
_resolvedTarget = _resolvedTarget ?? ResolveTargetFromContext();
|
||||
|
||||
if (_resolvedTarget == null)
|
||||
{
|
||||
if (_failIfNoTarget) return TaskStatus.Failure;
|
||||
return TaskStatus.Success;
|
||||
}
|
||||
|
||||
// 타겟이 파괴되었는지 확인
|
||||
if (_resolvedTarget == null || (_resolvedTarget as UnityEngine.Object) == null)
|
||||
{
|
||||
return TaskStatus.Failure;
|
||||
}
|
||||
|
||||
// 목적지 계산 및 이동 시작/유지
|
||||
_repathTimer -= Time.deltaTime;
|
||||
if (!_started || _repathInterval > 0f && _repathTimer <= 0f)
|
||||
{
|
||||
_currentDestination = GetBestDestination(_resolvedTarget);
|
||||
_repathTimer = _repathInterval;
|
||||
|
||||
if (!_started)
|
||||
{
|
||||
if (!_movement.TryMoveToPosition(_currentDestination))
|
||||
{
|
||||
return TaskStatus.Failure;
|
||||
}
|
||||
_movement.EnableMove();
|
||||
_movement.PlayMove();
|
||||
_started = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 이동 중 목적지 갱신 시도
|
||||
_movement.TryMoveToPosition(_currentDestination);
|
||||
}
|
||||
}
|
||||
|
||||
// 도달 판정
|
||||
var sqrDist = (GetAgentPosition() - _currentDestination).sqrMagnitude;
|
||||
if (sqrDist <= _stoppingDistance * _stoppingDistance || _movement.HasReachedDestination())
|
||||
{
|
||||
_movement.StopMove();
|
||||
_movement.DisableMove();
|
||||
return TaskStatus.Success;
|
||||
}
|
||||
|
||||
return TaskStatus.Running;
|
||||
}
|
||||
|
||||
public override void OnEnd()
|
||||
{
|
||||
// 액션 종료 시 이동 중지(안전장치)
|
||||
if (_movement != null)
|
||||
{
|
||||
_movement.StopMove();
|
||||
_movement.DisableMove();
|
||||
}
|
||||
_resolvedTarget = null;
|
||||
}
|
||||
|
||||
private GameObject ResolveTargetFromContext()
|
||||
{
|
||||
// 1) 공용 블랙보드에서 가져오기
|
||||
var sharedBlackboard = gameObject.GetComponentInParent<IAISharedBlackboard>();
|
||||
var bbTarget = sharedBlackboard?.GetCurrentInteractionTarget();
|
||||
if (bbTarget != null) return bbTarget;
|
||||
|
||||
// 2) Interactor의 포커싱 대상에서 가져오기
|
||||
var interactor = gameObject.GetComponentInParent<IInteractor>();
|
||||
var focused = interactor?.GetFocusedInteractable();
|
||||
if (focused != null)
|
||||
{
|
||||
return focused.GetInteractableGameObject();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Vector3 GetAgentPosition()
|
||||
{
|
||||
return _movement != null ? _movement.CurrentPosition : transform.position;
|
||||
}
|
||||
|
||||
private Vector3 GetBestDestination(GameObject target)
|
||||
{
|
||||
if (!_useInteractionPoints)
|
||||
{
|
||||
return target.transform.position;
|
||||
}
|
||||
|
||||
if (target.TryGetComponent<RestaurantInteractionComponent>(out var ric))
|
||||
{
|
||||
var points = ric.GetInteractionPoints();
|
||||
if (points != null && points.Length > 0)
|
||||
{
|
||||
var from = GetAgentPosition();
|
||||
float bestSqr = float.MaxValue;
|
||||
Vector3 best = target.transform.position;
|
||||
for (int i = 0; i < points.Length; i++)
|
||||
{
|
||||
var p = points[i];
|
||||
var sqr = (p - from).sqrMagnitude;
|
||||
if (sqr < bestSqr)
|
||||
{
|
||||
bestSqr = sqr;
|
||||
best = p;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
}
|
||||
|
||||
return target.transform.position;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d97cd08353334cb698807d4b526d01b6
|
||||
timeCreated: 1755769413
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6f1cc55df1da4604a2a7f445527710de
|
||||
timeCreated: 1755765247
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6777cf4d7cf4408fafe4bc9097c32b01
|
||||
timeCreated: 1755767888
|
@ -0,0 +1,60 @@
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks.Actions;
|
||||
using UnityEngine;
|
||||
|
||||
namespace DDD
|
||||
{
|
||||
public class StartRestaurantOrder : Action
|
||||
{
|
||||
[Tooltip("상호작용할 RestaurantOrderType")]
|
||||
[SerializeField] private RestaurantOrderType _targetOrderType = RestaurantOrderType.Wait;
|
||||
[Tooltip("실제 상호작용 가능 여부를 확인하고 수행합니다")]
|
||||
[SerializeField] private bool _requireCanInteract = true;
|
||||
[Tooltip("성공 시 블랙보드에 현재 인터랙션 대상을 등록합니다")]
|
||||
[SerializeField] private bool _registerOnBlackboard = true;
|
||||
|
||||
public override TaskStatus OnUpdate()
|
||||
{
|
||||
// TODO : 아래 타겟 찾기가 RestaurantOrderAvailable과 동일해야 함, 동일하면 중복될 필요 없으니 스태틱 유틸 함수정도로 만들어서 공유하기.
|
||||
// 레스토랑 주문 인터랙션 후보를 가져옴
|
||||
TaskStatus targetSearchSuccess = RestaurantOrderAvailable.FindAvailableOrderInteractable(_requireCanInteract, _targetOrderType, out var
|
||||
outInteractable);
|
||||
if (targetSearchSuccess == TaskStatus.Failure)
|
||||
{
|
||||
return TaskStatus.Failure;
|
||||
}
|
||||
|
||||
// TODO : 아래 상호작용 수행 로직이 우리 프로젝트의 권장하는 방식이 아님. 플레이어가 오브젝트에 인터랙션하는 것과 비슷한 흐름으로 NPC가 오브젝트에 인터랙션하게 만들 것.
|
||||
// 상호작용 수행: 액션이 붙은 에이전트를 Interactor로 사용
|
||||
var interactor = gameObject.GetComponentInParent<IInteractor>();
|
||||
if (interactor == null)
|
||||
{
|
||||
return TaskStatus.Failure;
|
||||
}
|
||||
|
||||
var interacted = outInteractable.OnInteracted(interactor);
|
||||
if (!interacted)
|
||||
{
|
||||
return TaskStatus.Failure;
|
||||
}
|
||||
|
||||
if (_registerOnBlackboard)
|
||||
{
|
||||
// 공용 블랙보드 우선
|
||||
var shared = gameObject.GetComponentInParent<IAISharedBlackboard>();
|
||||
if (shared != null)
|
||||
{
|
||||
shared.SetCurrentInteractionTarget(outInteractable.gameObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 하위 호환: 고객 전용 블랙보드 지원
|
||||
var customerBb = gameObject.GetComponentInParent<IRestaurantCustomerBlackboard>();
|
||||
customerBb?.SetCurrentInteractionTarget(outInteractable.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
return TaskStatus.Success;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 84b6c26acd1e41afa6b07ed4c6caf860
|
||||
timeCreated: 1755767930
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c4f20081a3974c08b60b9efdbe1568a7
|
||||
timeCreated: 1755765256
|
@ -0,0 +1,73 @@
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks.Conditionals;
|
||||
using UnityEngine;
|
||||
using DDD;
|
||||
|
||||
namespace DDD
|
||||
{
|
||||
public class RestaurantOrderAvailable : Conditional
|
||||
{
|
||||
[Tooltip("검사할 RestaurantOrderType")]
|
||||
[SerializeField] private RestaurantOrderType _targetOrderType = RestaurantOrderType.Wait;
|
||||
[Tooltip("인터랙션 가능 여부까지 확인할지 선택")]
|
||||
[SerializeField] private bool _checkCanInteract = true;
|
||||
|
||||
public RestaurantOrderType TargetOrderType
|
||||
{
|
||||
get => _targetOrderType;
|
||||
set => _targetOrderType = value;
|
||||
}
|
||||
|
||||
public bool CheckCanInteract
|
||||
{
|
||||
get => _checkCanInteract;
|
||||
set => _checkCanInteract = value;
|
||||
}
|
||||
|
||||
public override TaskStatus OnUpdate()
|
||||
{
|
||||
TaskStatus targetSearchSuccess = FindAvailableOrderInteractable(_checkCanInteract, _targetOrderType, out var
|
||||
outInteractable);
|
||||
return targetSearchSuccess;
|
||||
|
||||
}
|
||||
|
||||
public static TaskStatus FindAvailableOrderInteractable(bool checkCanInteract, RestaurantOrderType targetOrderType, out RestaurantInteractionComponent outInteractable)
|
||||
{
|
||||
outInteractable = null;
|
||||
|
||||
var environmentState = RestaurantState.Instance?.EnvironmentState;
|
||||
if (environmentState == null)
|
||||
{
|
||||
return TaskStatus.Failure;
|
||||
}
|
||||
|
||||
var interactables = environmentState.GetInteractablesByType(InteractionType.RestaurantOrder);
|
||||
|
||||
foreach (var interactable in interactables)
|
||||
{
|
||||
// 서브시스템에서 RestaurantOrderType을 가져와 비교
|
||||
outInteractable = interactable as RestaurantInteractionComponent;
|
||||
if (outInteractable == null) continue;
|
||||
if (!outInteractable.TryGetSubsystemObject<RestaurantOrderType>(out var subsystem)) continue;
|
||||
|
||||
if (subsystem.GetInteractionSubsystemType() == targetOrderType)
|
||||
{
|
||||
// CheckCanInteract이 false면 타입만 맞으면 성공
|
||||
if (!checkCanInteract)
|
||||
{
|
||||
return TaskStatus.Success;
|
||||
}
|
||||
|
||||
// CheckCanInteract이 true면 실제 인터랙션 가능 여부까지 확인
|
||||
if (interactable.CanInteract())
|
||||
{
|
||||
return TaskStatus.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return TaskStatus.Failure;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bef354fd7cae4273b4cb4e7ff9e270f5
|
||||
timeCreated: 1755765519
|
@ -0,0 +1,39 @@
|
||||
using Opsive.BehaviorDesigner.Runtime;
|
||||
using UnityEngine;
|
||||
|
||||
namespace DDD
|
||||
{
|
||||
public class RestaurantCustomerBlackboardComponent : MonoBehaviour, IRestaurantCustomerBlackboard, IAISharedBlackboard
|
||||
{
|
||||
private Subtree _subtree;
|
||||
private GameObject _currentInteractionTarget;
|
||||
|
||||
public void InitializeWithBehaviorTree(Subtree subtree)
|
||||
{
|
||||
_subtree = subtree;
|
||||
if (_subtree != null)
|
||||
{
|
||||
_subtree.SetVariableValue(nameof(RestaurantCustomerBlackboardKey.SelfGameObject), gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetCustomerData(CustomerData inCustomerData)
|
||||
{
|
||||
if (_subtree == null) return;
|
||||
_subtree.SetVariableValue(nameof(RestaurantCustomerBlackboardKey.CustomerData), inCustomerData);
|
||||
}
|
||||
|
||||
public void SetCurrentInteractionTarget(GameObject targetGameObject)
|
||||
{
|
||||
_currentInteractionTarget = targetGameObject;
|
||||
if (_subtree == null) return;
|
||||
_subtree.SetVariableValue(nameof(RestaurantCustomerBlackboardKey.CurrentInteractionTarget), targetGameObject);
|
||||
}
|
||||
|
||||
public GameObject GetCurrentInteractionTarget()
|
||||
{
|
||||
// 캐시 우선 반환. 필요 시 Subtree에서 직접 조회하도록 확장 가능.
|
||||
return _currentInteractionTarget;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
using Opsive.BehaviorDesigner.Runtime;
|
||||
using UnityEngine;
|
||||
|
||||
namespace DDD
|
||||
{
|
||||
public class RestaurantCustomerBlackboardComponent : MonoBehaviour, IRestaurantCustomerBlackboard
|
||||
{
|
||||
private Subtree _subtree;
|
||||
|
||||
public void InitializeWithBehaviorTree(Subtree subtree)
|
||||
{
|
||||
_subtree = subtree;
|
||||
_subtree.SetVariableValue(nameof(RestaurantCustomerBlackboardKey.SelfGameObject), gameObject);
|
||||
}
|
||||
|
||||
public void SetCustomerData(CustomerData inCustomerData)
|
||||
{
|
||||
_subtree.SetVariableValue(nameof(RestaurantCustomerBlackboardKey.CustomerData), inCustomerData);;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +1,18 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace DDD
|
||||
{
|
||||
public enum RestaurantCustomerBlackboardKey
|
||||
{
|
||||
SelfGameObject,
|
||||
CustomerData,
|
||||
CurrentInteractionTarget,
|
||||
}
|
||||
|
||||
public interface IRestaurantCustomerBlackboard
|
||||
{
|
||||
void SetCustomerData(CustomerData inCustomerData);
|
||||
void SetCurrentInteractionTarget(GameObject targetGameObject);
|
||||
GameObject GetCurrentInteractionTarget();
|
||||
}
|
||||
}
|
@ -3,7 +3,6 @@
|
||||
|
||||
namespace DDD
|
||||
{
|
||||
[Flags]
|
||||
public enum RestaurantOrderType : uint
|
||||
{
|
||||
Wait = 0u,
|
||||
@ -35,11 +34,9 @@ public bool CanInteract()
|
||||
|
||||
public bool OnInteracted(IInteractor interactor, ScriptableObject payloadSo = null)
|
||||
{
|
||||
if (GetInteractionSubsystemType() == RestaurantOrderType.Wait)
|
||||
{
|
||||
// DO WAIT CUSTOMER
|
||||
}
|
||||
|
||||
// 간단한 상태 전이: 현재 상태에서 다음 상태로 이동
|
||||
var prev = currentRestaurantOrderType;
|
||||
currentRestaurantOrderType = GetNextState(prev);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -52,5 +49,19 @@ public RestaurantOrderType GetInteractionSubsystemType()
|
||||
{
|
||||
return currentRestaurantOrderType;
|
||||
}
|
||||
|
||||
private RestaurantOrderType GetNextState(RestaurantOrderType state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case RestaurantOrderType.Wait: return RestaurantOrderType.Reserved;
|
||||
case RestaurantOrderType.Reserved: return RestaurantOrderType.Order;
|
||||
case RestaurantOrderType.Order: return RestaurantOrderType.Serve;
|
||||
case RestaurantOrderType.Serve: return RestaurantOrderType.Busy;
|
||||
case RestaurantOrderType.Busy: return RestaurantOrderType.Dirty;
|
||||
case RestaurantOrderType.Dirty: return RestaurantOrderType.Wait;
|
||||
default: return RestaurantOrderType.Wait;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -30,14 +30,31 @@ public class RestaurantInteractionComponent : MonoBehaviour, IInteractable, IInt
|
||||
|
||||
private Dictionary<InteractionType, IInteractionSubsystemObject> _subsystems = new();
|
||||
|
||||
private void Start()
|
||||
private void OnEnable()
|
||||
{
|
||||
// Register this interactable to environment state
|
||||
var environmentState = RestaurantState.Instance?.EnvironmentState;
|
||||
environmentState?.RegisterInteractable(this);
|
||||
|
||||
if (autoInitialize)
|
||||
{
|
||||
InitializeInteraction(_interactionType);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
var environmentState = RestaurantState.Instance?.EnvironmentState;
|
||||
environmentState?.UnregisterInteractable(this);
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
// 보수적으로 Start에서도 등록 시도 (OnEnable 시점에 EnvironmentState가 없었을 경우 대비)
|
||||
var environmentState = RestaurantState.Instance?.EnvironmentState;
|
||||
environmentState?.RegisterInteractable(this);
|
||||
}
|
||||
|
||||
private static IEnumerable GetAllInteractionTypes()
|
||||
{
|
||||
return System.Enum.GetValues(typeof(InteractionType))
|
||||
|
@ -21,5 +21,54 @@ public class RestaurantEnvironmentState : ScriptableObject
|
||||
{
|
||||
public List<RestaurantPropLocation> Props = new List<RestaurantPropLocation>();
|
||||
public List<RestaurantPropLocation> Objects = new List<RestaurantPropLocation>();
|
||||
|
||||
// 인터랙션 가능한 객체(IInteractable)를 관리하기 위한 리스트 (런타임 전용)
|
||||
private readonly List<IInteractable> _registeredInteractables = new List<IInteractable>();
|
||||
|
||||
/// <summary>
|
||||
/// 인터랙션 가능한 객체를 등록합니다
|
||||
/// </summary>
|
||||
public void RegisterInteractable(IInteractable interactable)
|
||||
{
|
||||
if (interactable == null) return;
|
||||
if (_registeredInteractables.Contains(interactable)) return;
|
||||
_registeredInteractables.Add(interactable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 인터랙션 가능한 객체를 해제합니다
|
||||
/// </summary>
|
||||
public void UnregisterInteractable(IInteractable interactable)
|
||||
{
|
||||
if (interactable == null) return;
|
||||
_registeredInteractables.Remove(interactable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 특정 InteractionType에 해당하는 인터랙션 객체들을 반환합니다
|
||||
/// </summary>
|
||||
public List<IInteractable> GetInteractablesByType(InteractionType interactionType)
|
||||
{
|
||||
var result = new List<IInteractable>();
|
||||
// null 또는 Destroyed 오브젝트 정리
|
||||
_registeredInteractables.RemoveAll(item => item == null || (item as UnityEngine.Object) == null);
|
||||
foreach (var interactable in _registeredInteractables)
|
||||
{
|
||||
if (interactable.GetInteractionType() == interactionType)
|
||||
{
|
||||
result.Add(interactable);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 모든 등록된 인터랙션 객체들을 반환합니다
|
||||
/// </summary>
|
||||
public List<IInteractable> GetAllInteractables()
|
||||
{
|
||||
_registeredInteractables.RemoveAll(item => item == null || (item as UnityEngine.Object) == null);
|
||||
return new List<IInteractable>(_registeredInteractables);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user