Refactored MoveToInteractionTarget
This commit is contained in:
parent
4ef63ec9a9
commit
71fbd60719
@ -1,4 +1,3 @@
|
|||||||
using System;
|
|
||||||
using Opsive.BehaviorDesigner.Runtime.Tasks;
|
using Opsive.BehaviorDesigner.Runtime.Tasks;
|
||||||
using Opsive.BehaviorDesigner.Runtime.Tasks.Actions;
|
using Opsive.BehaviorDesigner.Runtime.Tasks.Actions;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
@ -6,160 +5,167 @@
|
|||||||
namespace DDD
|
namespace DDD
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// IAiMovement를 이용해 인터랙션 타겟(주로 RestaurantInteractionComponent가 붙은 오브젝트)으로 이동하는 액션.
|
/// IAiMovement를 이용해 인터랙션 타겟으로 이동하는 액션
|
||||||
/// - 타겟은 우선 블랙보드(IRestaurantCustomerBlackboard)에서 가져오고, 없으면 Interactor의 FocusedInteractable에서 가져옵니다.
|
|
||||||
/// - 타겟에 RestaurantInteractionComponent가 있다면 가장 가까운 InteractionPoint를 목적지로 사용합니다.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class MoveToInteractionTarget : Opsive.BehaviorDesigner.Runtime.Tasks.Actions.Action
|
public class MoveToInteractionTarget : Action
|
||||||
{
|
{
|
||||||
[Header("Target")]
|
[Header("Target Settings")]
|
||||||
[Tooltip("타겟에 RestaurantInteractionComponent가 있을 때 InteractionPoints를 사용해 가장 가까운 지점으로 이동합니다.")]
|
[Tooltip("InteractionPoints를 사용해 가장 가까운 지점으로 이동")]
|
||||||
[SerializeField] private bool _useInteractionPoints = true;
|
[SerializeField] private bool useInteractionPoints = true;
|
||||||
[Tooltip("타겟이 없을 때 즉시 실패할지 여부")]
|
[Tooltip("타겟이 없을 때 즉시 실패할지 여부")]
|
||||||
[SerializeField] private bool _failIfNoTarget = true;
|
[SerializeField] private bool failIfNoTarget = true;
|
||||||
|
|
||||||
[Header("Movement")]
|
[Header("Movement Settings")]
|
||||||
[Tooltip("목적지에 도달했다고 간주할 거리")]
|
[Tooltip("목적지 도달 거리")]
|
||||||
[SerializeField] private float _stoppingDistance = 0.5f;
|
[SerializeField] private float stoppingDistance = 0.5f;
|
||||||
[Tooltip("이동 중 목적지를 재계산하는 주기(초). 0 이하면 재계산하지 않음")]
|
[Tooltip("목적지 재계산 주기(초), 0 이하면 비활성화")]
|
||||||
[SerializeField] private float _repathInterval = 0.5f;
|
[SerializeField] private float repathInterval = 0.5f;
|
||||||
|
|
||||||
private IAiMovement _movement;
|
private IAiMovement movement;
|
||||||
private float _repathTimer;
|
private float repathTimer;
|
||||||
private Vector3 _currentDestination;
|
private Vector3 currentDestination;
|
||||||
private bool _started;
|
private bool isMoving;
|
||||||
private GameObject _resolvedTarget;
|
private GameObject cachedTarget;
|
||||||
|
|
||||||
public override void OnStart()
|
public override void OnStart()
|
||||||
{
|
{
|
||||||
_movement = gameObject.GetComponentInParent<IAiMovement>();
|
movement = gameObject.GetComponentInParent<IAiMovement>();
|
||||||
_repathTimer = 0f;
|
repathTimer = 0f;
|
||||||
_started = false;
|
isMoving = false;
|
||||||
_resolvedTarget = ResolveTargetFromContext();
|
cachedTarget = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override TaskStatus OnUpdate()
|
public override TaskStatus OnUpdate()
|
||||||
{
|
{
|
||||||
if (_movement == null)
|
if (movement == null)
|
||||||
{
|
|
||||||
return TaskStatus.Failure;
|
return TaskStatus.Failure;
|
||||||
|
|
||||||
|
var target = GetTarget();
|
||||||
|
if (target == null)
|
||||||
|
return failIfNoTarget ? TaskStatus.Failure : TaskStatus.Success;
|
||||||
|
|
||||||
|
if (ShouldUpdateDestination())
|
||||||
|
{
|
||||||
|
currentDestination = CalculateDestination(target);
|
||||||
|
StartOrUpdateMovement();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 매 프레임 타겟이 갱신될 수 있으므로 재해결 (필요 시 비용 줄일 수 있음)
|
return CheckMovementCompletion();
|
||||||
_resolvedTarget = _resolvedTarget ?? ResolveTargetFromContext();
|
|
||||||
|
|
||||||
if (_resolvedTarget == null)
|
|
||||||
{
|
|
||||||
if (_failIfNoTarget) return TaskStatus.Failure;
|
|
||||||
return TaskStatus.Success;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 타겟이 파괴되었는지 확인
|
public override void OnEnd()
|
||||||
if (_resolvedTarget == null || (_resolvedTarget as UnityEngine.Object) == null)
|
|
||||||
{
|
{
|
||||||
return TaskStatus.Failure;
|
StopMovement();
|
||||||
|
cachedTarget = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 목적지 계산 및 이동 시작/유지
|
private GameObject GetTarget()
|
||||||
_repathTimer -= Time.deltaTime;
|
|
||||||
if (!_started || _repathInterval > 0f && _repathTimer <= 0f)
|
|
||||||
{
|
{
|
||||||
_currentDestination = GetBestDestination(_resolvedTarget);
|
// 캐시된 타겟이 유효하면 재사용
|
||||||
_repathTimer = _repathInterval;
|
if (IsValidTarget(cachedTarget))
|
||||||
|
return cachedTarget;
|
||||||
|
|
||||||
if (!_started)
|
// 블랙보드에서 타겟 검색
|
||||||
{
|
cachedTarget = gameObject.GetComponentInParent<IAISharedBlackboard>()
|
||||||
if (!_movement.TryMoveToPosition(_currentDestination))
|
?.GetCurrentInteractionTarget();
|
||||||
{
|
|
||||||
return TaskStatus.Failure;
|
if (IsValidTarget(cachedTarget))
|
||||||
|
return cachedTarget;
|
||||||
|
|
||||||
|
// Interactor의 포커스된 타겟 검색
|
||||||
|
var interactor = gameObject.GetComponentInParent<IInteractor>();
|
||||||
|
var focusedInteractable = interactor?.GetFocusedInteractable();
|
||||||
|
cachedTarget = focusedInteractable?.GetInteractableGameObject();
|
||||||
|
|
||||||
|
return cachedTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsValidTarget(GameObject target) =>
|
||||||
|
target != null && target;
|
||||||
|
|
||||||
|
private bool ShouldUpdateDestination()
|
||||||
|
{
|
||||||
|
repathTimer -= Time.deltaTime;
|
||||||
|
return !isMoving || (repathInterval > 0f && repathTimer <= 0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vector3 CalculateDestination(GameObject target)
|
||||||
|
{
|
||||||
|
repathTimer = repathInterval;
|
||||||
|
|
||||||
|
if (!useInteractionPoints)
|
||||||
|
return target.transform.position;
|
||||||
|
|
||||||
|
return target.TryGetComponent<RestaurantInteractionComponent>(out var ric)
|
||||||
|
? GetNearestInteractionPoint(ric)
|
||||||
|
: target.transform.position;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vector3 GetNearestInteractionPoint(RestaurantInteractionComponent ric)
|
||||||
|
{
|
||||||
|
var points = ric.GetInteractionPoints();
|
||||||
|
if (points == null || points.Length == 0)
|
||||||
|
return ric.transform.position;
|
||||||
|
|
||||||
|
var agentPosition = GetAgentPosition();
|
||||||
|
var nearestPoint = ric.transform.position;
|
||||||
|
var minDistanceSqr = float.MaxValue;
|
||||||
|
|
||||||
|
foreach (var point in points)
|
||||||
|
{
|
||||||
|
var distanceSqr = (point - agentPosition).sqrMagnitude;
|
||||||
|
if (distanceSqr < minDistanceSqr)
|
||||||
|
{
|
||||||
|
minDistanceSqr = distanceSqr;
|
||||||
|
nearestPoint = point;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nearestPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartOrUpdateMovement()
|
||||||
|
{
|
||||||
|
if (!isMoving)
|
||||||
|
{
|
||||||
|
if (movement.TryMoveToPosition(currentDestination))
|
||||||
|
{
|
||||||
|
movement.EnableMove();
|
||||||
|
movement.PlayMove();
|
||||||
|
isMoving = true;
|
||||||
}
|
}
|
||||||
_movement.EnableMove();
|
|
||||||
_movement.PlayMove();
|
|
||||||
_started = true;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// 이동 중 목적지 갱신 시도
|
movement.TryMoveToPosition(currentDestination);
|
||||||
_movement.TryMoveToPosition(_currentDestination);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 도달 판정
|
private TaskStatus CheckMovementCompletion()
|
||||||
var sqrDist = (GetAgentPosition() - _currentDestination).sqrMagnitude;
|
|
||||||
if (sqrDist <= _stoppingDistance * _stoppingDistance || _movement.HasReachedDestination())
|
|
||||||
{
|
{
|
||||||
_movement.StopMove();
|
var distanceSqr = (GetAgentPosition() - currentDestination).sqrMagnitude;
|
||||||
_movement.DisableMove();
|
var stoppingDistanceSqr = stoppingDistance * stoppingDistance;
|
||||||
|
|
||||||
|
if (distanceSqr <= stoppingDistanceSqr || movement.HasReachedDestination())
|
||||||
|
{
|
||||||
|
StopMovement();
|
||||||
return TaskStatus.Success;
|
return TaskStatus.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
return TaskStatus.Running;
|
return TaskStatus.Running;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnEnd()
|
private void StopMovement()
|
||||||
{
|
{
|
||||||
// 액션 종료 시 이동 중지(안전장치)
|
if (movement != null && isMoving)
|
||||||
if (_movement != null)
|
|
||||||
{
|
{
|
||||||
_movement.StopMove();
|
movement.StopMove();
|
||||||
_movement.DisableMove();
|
movement.DisableMove();
|
||||||
}
|
isMoving = false;
|
||||||
_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;
|
private Vector3 GetAgentPosition() =>
|
||||||
}
|
movement?.CurrentPosition ?? transform.position;
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user