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")]
|
||||
[Tooltip("목적지 도달 거리")]
|
||||
[SerializeField] private float stoppingDistance = 0.5f;
|
||||
[SerializeField] private float stoppingDistance = 0.01f;
|
||||
[Tooltip("목적지 재계산 주기(초), 0 이하면 비활성화")]
|
||||
[SerializeField] private float repathInterval = 0.5f;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user