
오늘은 큰 거 없이 플레이어가 조종하는 폰의 움직임을 제한해보려고 하는데
바로 월드의 네비게이션 메시가 있는 높은 위치에서 낮은 위치로 이동을 못 시키게 하고 싶다(말하자면 낙하 방지?)
지금 상황을 보자

폰이 이동하고자 하는 곳에 지면이 없어도 그냥 이동하고 Falling 상태로 바뀌어 아래로 떨어지게 된다
물론 이렇게 설계한 많은 게임이 있겠지만 로스트아크에서는 이렇지 않다
그래서 현재 폰을 이동시키고 있는 함수를 보면
void ALKPlayerController::OnSetDestinationTriggered()
{
FHitResult Hit;
bool bHitSuccessful = false;
bHitSuccessful = GetHitResultUnderCursor(ECollisionChannel::ECC_Visibility, true, Hit);
if (bHitSuccessful)
{
Destination = Hit.Location;
}
APawn* ControlledPawn = GetPawn();
if (ControlledPawn != nullptr)
{
FVector CurrentLocation = ControlledPawn->GetActorLocation();
FVector WorldDirection = (Destination - CurrentLocation).GetSafeNormal();
ControlledPawn->AddMovementInput(WorldDirection, 1.0, false);
}
}
오른쪽 마우스를 눌렀거나 누르고 있을 때 호출되는 함수인 OnSetDestinationTriggered
GetHitResultUnderCursor 함수를 통해 마우스 커서 아래의 충돌을 했는지 체크하고 충돌 결과를 FHitResult 구조체에 충돌 정보를 저장한다 ECollisionChannel::ECC_Visibility는 충돌 채널을 지정하는데 커서 아래에 있는 것들 중 눈에 보이는 것들의 충돌을 확인할 때 사용bTraceComplex의 값인데, 정교한 충돌검사를 할 지를 나타낸다. 오브젝트의 복잡한 모양과 충돌을 고려할 때 사용bHitSuccessful이 true로 설정되면(충돌이 일어났으면) 이동할 곳의 위치를 가져온다GetPawn을 통해 컨트롤러가 빙의한 폰의 정보를 가져오고 현재 위치를 구해 방향 벡터를 구해 정규화해준다AddMovementInput를 통해 방향 벡터로 이동을 실행한다아마 AddMovementInput 함수가 그냥 앞에 길이 없더라도 이동을 시키는 것 같다. 한 번 살펴보자
void APawn::AddMovementInput(FVector WorldDirection, float ScaleValue, bool bForce /*=false*/)
{
UPawnMovementComponent* MovementComponent = GetMovementComponent();
if (MovementComponent)
{
MovementComponent->AddInputVector(WorldDirection * ScaleValue, bForce);
}
else
{
Internal_AddMovementInput(WorldDirection * ScaleValue, bForce);
}
}
AddInputVector 함수를 통해 이동시킨다Internal_AddMovementInput로 이동시킴ScaleValue 매개변수는 아마 얼만큼 빨리 움직일지에 대한 변수인 것 같다. 음수라면 이동하고자 하는 방향의 반대로 움직이는 것으로 알고 있다bForce 매개변수는 움직임을 강제할 지에 대한 변수임현재 조작하고 있는 폰은 캐릭터 무브먼트 컴포넌트가 있으니 AddInputVector 함수를 알아보자
void UPawnMovementComponent::AddInputVector(FVector WorldAccel, bool bForce /*=false*/)
{
if (PawnOwner)
{
PawnOwner->Internal_AddMovementInput(WorldAccel, bForce);
}
}
Internal_AddMovementInput 함수를 다시 호출한다void APawn::Internal_AddMovementInput(FVector WorldAccel, bool bForce /*=false*/)
{
if (bForce || !IsMoveInputIgnored())
{
ControlInputVector += WorldAccel;
}
}
ControlInputVector에 가속도를 더해준다ControlInputVector의 값만 바뀌었는데 폰이 움직인다고?
이는 값만 바꼈다고 움직이는 것이 아니라 시간이 흐르면서 입력 값이 축적되어 언리얼 시스템에서 자동으로 그 값을 폰의 이동에 사용한다는 말인것 같다
그러니까 결국 AddMovementInput 함수로 이동시켜서 최종적으로 폰이 이동할 벡터값을 업데이트해 이동시킨다는 말인 것 같다
그래서 이 함수를 쓰면 네비게이션 메시가 있어도 소용이 없다는 뜻으로 해석했다
사실 해결이라기 보기는 어려운데(확신이 없고 아닌 것 같음 ㅠㅠ) 어쨌든 내가 의도한 바로 실행은 되니까,,,
이전에 있던 문제가 생기는 이유는 마우스를 꾹 누르고 있을 때이다. 그러니까 OnSetDestinationTriggered 함수가 계속 호출이 되는 상황인 것
마우스를 한 번만 눌렀을 때는? 클릭한 곳에 길이 없으면 돌아서 간다(네비게이션 경로로 길을 찾아 이동함)
그 말은 즉 OnSetDestinationReleased 이 함수가 호출될 때는 내가 의도한 방향대로 폰이 움직인다
이 함수에서 쓴 이동 함수는 UAIBlueprintHelperLibrary::SimpleMoveToLocation(this, Destination); 이것인데 함수를 분석해보자
AIBlueprintHelperLibrary 클래스에서 제공하는 유틸리티 이동 함수인데 주로 AI 관련 작업을 위해 설계된 클래스라고 한다
AI Controller도 결국 Controller를 상속받기 때문에 플레이어 캐릭터라도 못 사용할건 없어서 사용해보자
UNavigationSystemV1* NavSys = Controller ? FNavigationSystem::GetCurrent<UNavigationSystemV1>(Controller->GetWorld()) : nullptr;
if (NavSys == nullptr || Controller == nullptr || Controller->GetPawn() == nullptr)
{
... // 에러 로그
return;
}
UPathFollowingComponent* PFollowComp = InitNavigationControl(*Controller);
if (PFollowComp == nullptr)
{
...
return;
}
if (!PFollowComp->IsPathFollowingAllowed())
{
...
return;
}
const bool bAlreadyAtGoal = PFollowComp->HasReached(GoalLocation, EPathFollowingReachMode::OverlapAgent);
// script source, keep only one move request at time
if (PFollowComp->GetStatus() != EPathFollowingStatus::Idle)
{
PFollowComp->AbortMove(*NavSys, FPathFollowingResultFlags::ForcedScript | FPathFollowingResultFlags::NewRequest
, FAIRequestID::AnyRequest, bAlreadyAtGoal ? EPathFollowingVelocityMode::Reset : EPathFollowingVelocityMode::Keep);
}
// script source, keep only one move request at time
if (PFollowComp->GetStatus() != EPathFollowingStatus::Idle)
{
PFollowComp->AbortMove(*NavSys, FPathFollowingResultFlags::ForcedScript | FPathFollowingResultFlags::NewRequest);
}
if (bAlreadyAtGoal)
{
PFollowComp->RequestMoveWithImmediateFinish(EPathFollowingResult::Success);
}
else
{
const FVector AgentNavLocation = Controller->GetNavAgentLocation();
const ANavigationData* NavData = NavSys->GetNavDataForProps(Controller->GetNavAgentPropertiesRef(), AgentNavLocation);
if (NavData)
{
FPathFindingQuery Query(Controller, *NavData, AgentNavLocation, GoalLocation);
FPathFindingResult Result = NavSys->FindPathSync(Query);
if (Result.IsSuccessful())
{
PFollowComp->RequestMove(FAIMoveRequest(GoalLocation), Result.Path);
}
else if (PFollowComp->GetStatus() != EPathFollowingStatus::Idle)
{
PFollowComp->RequestMoveWithImmediateFinish(EPathFollowingResult::Invalid);
}
}
}
RequestMove를 통해 폰에게 지정된 경로로 이동하도록 지시이렇게 함수를 찾아보니까 지정된 경로로만 폰이 움직이게 하는 함수인 것을 알 수 있다
이제 기존에 쓰이던 이동 함수를 위에서 봤던 함수로 대체해보자
void ALKPlayerController::OnSetDestinationTriggered()
{
...
UAIBlueprintHelperLibrary::SimpleMoveToLocation(this, Destination);
}
빌드하고 실행해보면

이제 클릭한 곳으로 가기 위한 경로를 찾는다!
사실 SimpleMoveToLocation 함수는 네비게이션 경로를 찾는 과정이 있다보니 이게 마우스를 누르는 동안 계속 경로를 찾는 것과 마찬가지가 된다
틱마다 경로를 찾으면? 과부하가 걸리겠지...
하지만 일단 지금은 마땅한 방법이 생각나지 않으니,,, 이대로 두고 나중에 언리얼 잘하시는 개발자님을 찾아 방법을 한번 토론해보고 싶다 ㅠㅠ