단순히 AI 의 움직임에 있어서 MoveTo Task를 이용해 AI 가 해당 방향으로 움직이면서 자연스럽게 Rotate를 할 수 있도록 Simple Parallel - Move To, Rotate To Location 이런 식으로 활용을 했음.

그러나 중간에 Obstacle 이 있게 되면 Path가 자연스럽게 꺾이게 되는데 AI는 목적지 만을 향해 Rotate가 되기 때문에 부자연스럽게 보임.
UBTService_RotateToMovement::UBTService_RotateToMovement()
{
NodeName = "Rotate To Movement Direction";
bNotifyTick = true;
bNotifyBecomeRelevant = false;
InterpSpeed = 5.0f;
}
void UBTService_RotateToMovement::TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
Super::TickNode(OwnerComp, NodeMemory, DeltaSeconds);
AAIController* AIController = OwnerComp.GetAIOwner();
if (!AIController) return;
AMonster* Monster = Cast<AMonster>(AIController->GetPawn());
if (!Monster) return;
UCharacterMovementComponent* MovementComp = Monster->GetCharacterMovement();
if (!MovementComp) return;
// No rotation when stationary
const FVector MonsterVelocity = MovementComp->Velocity;
if (MonsterVelocity.SizeSquared() < KINDA_SMALL_NUMBER) return;
// Movement direction → Rotate
FRotator TargetRotation = MonsterVelocity.GetSafeNormal().Rotation();
TargetRotation.Roll = 0.0f;
// Interpolation
FRotator NewRotation = FMath::RInterpTo(Monster->GetActorRotation(), TargetRotation, DeltaSeconds, InterpSpeed);
Monster->SetActorRotation(NewRotation);
}
if (MonsterVelocity.SizeSquared() < KINDA_SMALL_NUMBER) return;이동방향을 향해 회전하는 것은 고쳐졌으나 다른 부자연스러움이 생겼음.
✨ 부자연스러웠던 이유 분석
👉 따라서 Monster 의 Tick에서 회전을 적용하도록 트러블 슈팅
예상되는 문제는 조금 더 비용이 비싸져서 최적화 문제가 일어날 것 같았음
그래서 조건을 빡빡하게 해서 최대한 조건 내에서만 Tick이 이루어지도록 로직 설정
void AMonster::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
if (TargetActor)
{
if (!IsAnimMontagePlaying())
{
if (GetMonsterState() == EMonsterState::Flee)
{
RotateToMovementForward(DeltaTime);
}
else
{
RotateToTarget(DeltaTime);
}
}
}
else
{
RotateToMovementForward(DeltaTime);
}
}
void AMonster::RotateToTarget(float DeltaTime)
{
FVector MonsterLocation = GetActorLocation();
FVector TargetLocation = TargetActor->GetActorLocation();
FVector DirectionToTarget = (TargetLocation - MonsterLocation).GetSafeNormal();
FRotator MonsterCurrentRotation = GetActorRotation();
FRotator TargetToRotation = DirectionToTarget.Rotation();
TargetToRotation.Roll = 0.0f;
float InterpSpeed = 6.0f;
FRotator NewRotation = FMath::RInterpTo(MonsterCurrentRotation, TargetToRotation, DeltaTime, InterpSpeed);
SetActorRotation(NewRotation);
}
void AMonster::RotateToMovementForward(float DeltaTime)
{
FVector Velocity = GetVelocity();
if (Velocity.SizeSquared() > KINDA_SMALL_NUMBER)
{
FRotator CurrentRotation = GetActorRotation();
FRotator TargetRotation = Velocity.GetSafeNormal().Rotation();
TargetRotation.Roll = 0.f;
float InterpSpeed = 6.0f;
GetMonsterState() == EMonsterState::Investigate ? InterpSpeed = 15.0f : InterpSpeed = 6.0f;
FRotator NewRotation = FMath::RInterpTo(CurrentRotation, TargetRotation, DeltaTime, InterpSpeed);
SetActorRotation(NewRotation);
}
}
Rotation이 비교적 자연스럽게 잘 적용된 모습을 볼 수 있다.

