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.Actions;
|
||||
using UnityEngine;
|
||||
@ -6,160 +5,167 @@
|
||||
namespace DDD
|
||||
{
|
||||
/// <summary>
|
||||
/// IAiMovement를 이용해 인터랙션 타겟(주로 RestaurantInteractionComponent가 붙은 오브젝트)으로 이동하는 액션.
|
||||
/// - 타겟은 우선 블랙보드(IRestaurantCustomerBlackboard)에서 가져오고, 없으면 Interactor의 FocusedInteractable에서 가져옵니다.
|
||||
/// - 타겟에 RestaurantInteractionComponent가 있다면 가장 가까운 InteractionPoint를 목적지로 사용합니다.
|
||||
/// IAiMovement를 이용해 인터랙션 타겟으로 이동하는 액션
|
||||
/// </summary>
|
||||
public class MoveToInteractionTarget : Opsive.BehaviorDesigner.Runtime.Tasks.Actions.Action
|
||||
public class MoveToInteractionTarget : Action
|
||||
{
|
||||
[Header("Target")]
|
||||
[Tooltip("타겟에 RestaurantInteractionComponent가 있을 때 InteractionPoints를 사용해 가장 가까운 지점으로 이동합니다.")]
|
||||
[SerializeField] private bool _useInteractionPoints = true;
|
||||
[Header("Target Settings")]
|
||||
[Tooltip("InteractionPoints를 사용해 가장 가까운 지점으로 이동")]
|
||||
[SerializeField] private bool useInteractionPoints = true;
|
||||
[Tooltip("타겟이 없을 때 즉시 실패할지 여부")]
|
||||
[SerializeField] private bool _failIfNoTarget = true;
|
||||
[SerializeField] private bool failIfNoTarget = true;
|
||||
|
||||
[Header("Movement")]
|
||||
[Tooltip("목적지에 도달했다고 간주할 거리")]
|
||||
[SerializeField] private float _stoppingDistance = 0.5f;
|
||||
[Tooltip("이동 중 목적지를 재계산하는 주기(초). 0 이하면 재계산하지 않음")]
|
||||
[SerializeField] private float _repathInterval = 0.5f;
|
||||
[Header("Movement Settings")]
|
||||
[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;
|
||||
private IAiMovement movement;
|
||||
private float repathTimer;
|
||||
private Vector3 currentDestination;
|
||||
private bool isMoving;
|
||||
private GameObject cachedTarget;
|
||||
|
||||
public override void OnStart()
|
||||
{
|
||||
_movement = gameObject.GetComponentInParent<IAiMovement>();
|
||||
_repathTimer = 0f;
|
||||
_started = false;
|
||||
_resolvedTarget = ResolveTargetFromContext();
|
||||
movement = gameObject.GetComponentInParent<IAiMovement>();
|
||||
repathTimer = 0f;
|
||||
isMoving = false;
|
||||
cachedTarget = null;
|
||||
}
|
||||
|
||||
public override TaskStatus OnUpdate()
|
||||
{
|
||||
if (_movement == null)
|
||||
{
|
||||
if (movement == null)
|
||||
return TaskStatus.Failure;
|
||||
|
||||
var target = GetTarget();
|
||||
if (target == null)
|
||||
return failIfNoTarget ? TaskStatus.Failure : TaskStatus.Success;
|
||||
|
||||
if (ShouldUpdateDestination())
|
||||
{
|
||||
currentDestination = CalculateDestination(target);
|
||||
StartOrUpdateMovement();
|
||||
}
|
||||
|
||||
// 매 프레임 타겟이 갱신될 수 있으므로 재해결 (필요 시 비용 줄일 수 있음)
|
||||
_resolvedTarget = _resolvedTarget ?? ResolveTargetFromContext();
|
||||
return CheckMovementCompletion();
|
||||
}
|
||||
|
||||
if (_resolvedTarget == null)
|
||||
public override void OnEnd()
|
||||
{
|
||||
StopMovement();
|
||||
cachedTarget = null;
|
||||
}
|
||||
|
||||
private GameObject GetTarget()
|
||||
{
|
||||
// 캐시된 타겟이 유효하면 재사용
|
||||
if (IsValidTarget(cachedTarget))
|
||||
return cachedTarget;
|
||||
|
||||
// 블랙보드에서 타겟 검색
|
||||
cachedTarget = gameObject.GetComponentInParent<IAISharedBlackboard>()
|
||||
?.GetCurrentInteractionTarget();
|
||||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
var distanceSqr = (point - agentPosition).sqrMagnitude;
|
||||
if (distanceSqr < minDistanceSqr)
|
||||
{
|
||||
if (!_movement.TryMoveToPosition(_currentDestination))
|
||||
{
|
||||
return TaskStatus.Failure;
|
||||
}
|
||||
_movement.EnableMove();
|
||||
_movement.PlayMove();
|
||||
_started = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 이동 중 목적지 갱신 시도
|
||||
_movement.TryMoveToPosition(_currentDestination);
|
||||
minDistanceSqr = distanceSqr;
|
||||
nearestPoint = point;
|
||||
}
|
||||
}
|
||||
|
||||
// 도달 판정
|
||||
var sqrDist = (GetAgentPosition() - _currentDestination).sqrMagnitude;
|
||||
if (sqrDist <= _stoppingDistance * _stoppingDistance || _movement.HasReachedDestination())
|
||||
return nearestPoint;
|
||||
}
|
||||
|
||||
private void StartOrUpdateMovement()
|
||||
{
|
||||
if (!isMoving)
|
||||
{
|
||||
_movement.StopMove();
|
||||
_movement.DisableMove();
|
||||
if (movement.TryMoveToPosition(currentDestination))
|
||||
{
|
||||
movement.EnableMove();
|
||||
movement.PlayMove();
|
||||
isMoving = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
movement.TryMoveToPosition(currentDestination);
|
||||
}
|
||||
}
|
||||
|
||||
private TaskStatus CheckMovementCompletion()
|
||||
{
|
||||
var distanceSqr = (GetAgentPosition() - currentDestination).sqrMagnitude;
|
||||
var stoppingDistanceSqr = stoppingDistance * stoppingDistance;
|
||||
|
||||
if (distanceSqr <= stoppingDistanceSqr || movement.HasReachedDestination())
|
||||
{
|
||||
StopMovement();
|
||||
return TaskStatus.Success;
|
||||
}
|
||||
|
||||
return TaskStatus.Running;
|
||||
}
|
||||
|
||||
public override void OnEnd()
|
||||
private void StopMovement()
|
||||
{
|
||||
// 액션 종료 시 이동 중지(안전장치)
|
||||
if (_movement != null)
|
||||
if (movement != null && isMoving)
|
||||
{
|
||||
_movement.StopMove();
|
||||
_movement.DisableMove();
|
||||
movement.StopMove();
|
||||
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