AI 액션 정리: LookAtInteractionTarget 껍데기/골격 추가 및 MoveToInteractionTarget 경로 재계산/정지 처리 안정화
This commit is contained in:
parent
71fbd60719
commit
f8122d8c70
@ -0,0 +1,140 @@
|
|||||||
|
using Opsive.BehaviorDesigner.Runtime.Tasks;
|
||||||
|
using Opsive.BehaviorDesigner.Runtime.Tasks.Actions;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace DDD
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 인터랙션 타겟을 바라보도록 시각(Spine/애니메이션) 컴포넌트에 위임하는 액션의 껍데기/골격.
|
||||||
|
/// 실제 회전/스파인 제어 로직은 별도의 Visual 컴포넌트(예: Spine/Animation Controller)에서 구현하십시오.
|
||||||
|
/// </summary>
|
||||||
|
public class LookAtInteractionTarget : Action
|
||||||
|
{
|
||||||
|
[Header("Target Settings")]
|
||||||
|
[Tooltip("InteractionPoints를 사용해 가장 적절한 지점을 바라봄")]
|
||||||
|
[SerializeField] private bool useInteractionPoints = true;
|
||||||
|
[Tooltip("타겟이 없을 때 즉시 실패할지 여부")]
|
||||||
|
[SerializeField] private bool failIfNoTarget = true;
|
||||||
|
|
||||||
|
[Header("Update Settings")]
|
||||||
|
[Tooltip("프레임마다 갱신하여 지속적으로 바라볼지 (Running 반환) 여부. 비활성화 시 1회만 시도하고 성공 처리")]
|
||||||
|
[SerializeField] private bool continuousUpdate = true;
|
||||||
|
|
||||||
|
// Visual 전용 컴포넌트(나중 구현)를 위한 최소 인터페이스
|
||||||
|
// 실제 구현은 Spine/애니메이션 제어 컴포넌트에서 이 인터페이스를 구현하세요.
|
||||||
|
public interface ILookAtVisual
|
||||||
|
{
|
||||||
|
// 초기 시작 시도. 성공 여부를 반환할 수 있으나, 본 액션은 성공/실패에 민감하지 않습니다.
|
||||||
|
bool TryBeginLookAt(Vector3 worldPosition);
|
||||||
|
// 매 프레임 갱신 시 호출됩니다.
|
||||||
|
void UpdateLookAt(Vector3 worldPosition);
|
||||||
|
// 액션 종료 시 호출됩니다.
|
||||||
|
void EndLookAt();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ILookAtVisual visual;
|
||||||
|
private GameObject cachedTarget;
|
||||||
|
private bool isLooking;
|
||||||
|
private Vector3 currentLookPosition;
|
||||||
|
|
||||||
|
public override void OnStart()
|
||||||
|
{
|
||||||
|
visual = gameObject.GetComponentInParent<ILookAtVisual>();
|
||||||
|
cachedTarget = null;
|
||||||
|
isLooking = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override TaskStatus OnUpdate()
|
||||||
|
{
|
||||||
|
var target = GetTarget();
|
||||||
|
if (target == null)
|
||||||
|
{
|
||||||
|
if (isLooking)
|
||||||
|
{
|
||||||
|
// 타겟이 사라졌다면 정리
|
||||||
|
visual?.EndLookAt();
|
||||||
|
isLooking = false;
|
||||||
|
}
|
||||||
|
return failIfNoTarget ? TaskStatus.Failure : TaskStatus.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentLookPosition = CalculateLookPosition(target);
|
||||||
|
|
||||||
|
if (!isLooking)
|
||||||
|
{
|
||||||
|
visual?.TryBeginLookAt(currentLookPosition);
|
||||||
|
isLooking = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
visual?.UpdateLookAt(currentLookPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 연속 업데이트면 Running, 아니면 1회만 시도 후 Success 반환
|
||||||
|
return continuousUpdate ? TaskStatus.Running : TaskStatus.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnEnd()
|
||||||
|
{
|
||||||
|
if (isLooking)
|
||||||
|
{
|
||||||
|
visual?.EndLookAt();
|
||||||
|
isLooking = false;
|
||||||
|
}
|
||||||
|
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 static bool IsValidTarget(GameObject target) => target != null && target;
|
||||||
|
|
||||||
|
private Vector3 CalculateLookPosition(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)
|
||||||
|
return target.transform.position;
|
||||||
|
|
||||||
|
// 가장 가까운 상호작용 지점 선택 (MoveTo와 동일한 기준)
|
||||||
|
var agentPos = transform.position;
|
||||||
|
var nearest = target.transform.position;
|
||||||
|
var minSqr = float.MaxValue;
|
||||||
|
foreach (var p in points)
|
||||||
|
{
|
||||||
|
var d = (p - agentPos).sqrMagnitude;
|
||||||
|
if (d < minSqr)
|
||||||
|
{
|
||||||
|
minSqr = d;
|
||||||
|
nearest = p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nearest;
|
||||||
|
}
|
||||||
|
|
||||||
|
return target.transform.position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a973b52fbfe64f8981b2a1d33864d2eb
|
||||||
|
timeCreated: 1755771294
|
@ -17,7 +17,7 @@ public class MoveToInteractionTarget : Action
|
|||||||
|
|
||||||
[Header("Movement Settings")]
|
[Header("Movement Settings")]
|
||||||
[Tooltip("목적지 도달 거리")]
|
[Tooltip("목적지 도달 거리")]
|
||||||
[SerializeField] private float stoppingDistance = 0.5f;
|
[SerializeField] private float stoppingDistance = 0.01f;
|
||||||
[Tooltip("목적지 재계산 주기(초), 0 이하면 비활성화")]
|
[Tooltip("목적지 재계산 주기(초), 0 이하면 비활성화")]
|
||||||
[SerializeField] private float repathInterval = 0.5f;
|
[SerializeField] private float repathInterval = 0.5f;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user