[Lost Kingdom] 개발일지 - 2

조재훈·2024년 3월 14일
post-thumbnail

개발일지 - 2 (2024.03.14)

개요

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

폰이 이동하고자 하는 곳에 지면이 없어도 그냥 이동하고 Falling 상태로 바뀌어 아래로 떨어지게 된다
물론 이렇게 설계한 많은 게임이 있겠지만 로스트아크에서는 이렇지 않다

폰의 이동

OnSetDestinationTriggered

그래서 현재 폰을 이동시키고 있는 함수를 보면

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

  1. GetHitResultUnderCursor 함수를 통해 마우스 커서 아래의 충돌을 했는지 체크하고 충돌 결과를 FHitResult 구조체에 충돌 정보를 저장한다
    • ECollisionChannel::ECC_Visibility는 충돌 채널을 지정하는데 커서 아래에 있는 것들 중 눈에 보이는 것들의 충돌을 확인할 때 사용
    • 두 번째 매개변수는 bTraceComplex의 값인데, 정교한 충돌검사를 할 지를 나타낸다. 오브젝트의 복잡한 모양과 충돌을 고려할 때 사용
      • 지형이 복잡할 수 있으므로 true로 설정했다
  2. bHitSuccessful이 true로 설정되면(충돌이 일어났으면) 이동할 곳의 위치를 가져온다
  3. 폰을 움직이기 위해 GetPawn을 통해 컨트롤러가 빙의한 폰의 정보를 가져오고 현재 위치를 구해 방향 벡터를 구해 정규화해준다
  4. 그리고 폰의 멤버 함수인 AddMovementInput를 통해 방향 벡터로 이동을 실행한다

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);
	}
}
  1. 폰의 MovementComponent를 가져온다. 폰이 어떻게 움직일지는 MovementComponent의 유무에 따라 갈림
    1-1. 있다면 MovementComponent의 AddInputVector 함수를 통해 이동시킨다
    1-2. 없다면 Internal_AddMovementInput로 이동시킴
  • ScaleValue 매개변수는 아마 얼만큼 빨리 움직일지에 대한 변수인 것 같다. 음수라면 이동하고자 하는 방향의 반대로 움직이는 것으로 알고 있다
  • bForce 매개변수는 움직임을 강제할 지에 대한 변수임
  • 이동하는 각 함수에 전달하는 첫 번째 매개변수는 이동 방향 * 속도를 해서 가속도를 전달해주는 것 같다

AddInputVector

현재 조작하고 있는 폰은 캐릭터 무브먼트 컴포넌트가 있으니 AddInputVector 함수를 알아보자

void UPawnMovementComponent::AddInputVector(FVector WorldAccel, bool bForce /*=false*/)
{
	if (PawnOwner)
	{
		PawnOwner->Internal_AddMovementInput(WorldAccel, bForce);
	}
}
  • 폰의 소유자가 있는지 확인하고 있다면 Internal_AddMovementInput 함수를 다시 호출한다

Internal_AddMovementInput

void APawn::Internal_AddMovementInput(FVector WorldAccel, bool bForce /*=false*/)
{
	if (bForce || !IsMoveInputIgnored())
	{
		ControlInputVector += WorldAccel;
	}
}
  • bForce가 true이거나 이동 입력을 허용하면 if문 내부가 실행이 됨
  • ControlInputVector에 가속도를 더해준다

ControlInputVector의 값만 바뀌었는데 폰이 움직인다고?

이는 값만 바꼈다고 움직이는 것이 아니라 시간이 흐르면서 입력 값이 축적되어 언리얼 시스템에서 자동으로 그 값을 폰의 이동에 사용한다는 말인것 같다

이 때까지의 요약

그러니까 결국 AddMovementInput 함수로 이동시켜서 최종적으로 폰이 이동할 벡터값을 업데이트해 이동시킨다는 말인 것 같다

그래서 이 함수를 쓰면 네비게이션 메시가 있어도 소용이 없다는 뜻으로 해석했다

해결?

사실 해결이라기 보기는 어려운데(확신이 없고 아닌 것 같음 ㅠㅠ) 어쨌든 내가 의도한 바로 실행은 되니까,,,

이전에 있던 문제가 생기는 이유는 마우스를 꾹 누르고 있을 때이다. 그러니까 OnSetDestinationTriggered 함수가 계속 호출이 되는 상황인 것

마우스를 한 번만 눌렀을 때는? 클릭한 곳에 길이 없으면 돌아서 간다(네비게이션 경로로 길을 찾아 이동함)

그 말은 즉 OnSetDestinationReleased 이 함수가 호출될 때는 내가 의도한 방향대로 폰이 움직인다

이 함수에서 쓴 이동 함수는 UAIBlueprintHelperLibrary::SimpleMoveToLocation(this, Destination); 이것인데 함수를 분석해보자

void UAIBlueprintHelperLibrary::SimpleMoveToLocation(AController* Controller, const FVector& GoalLocation)

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 함수는 네비게이션 경로를 찾는 과정이 있다보니 이게 마우스를 누르는 동안 계속 경로를 찾는 것과 마찬가지가 된다

틱마다 경로를 찾으면? 과부하가 걸리겠지...

하지만 일단 지금은 마땅한 방법이 생각나지 않으니,,, 이대로 두고 나중에 언리얼 잘하시는 개발자님을 찾아 방법을 한번 토론해보고 싶다 ㅠㅠ

profile
나태지옥

0개의 댓글