Verify if player arrive at location

Hi everyone, I have a little issue with my main character, I have set up a movement mechanic where if you click on a object, it will move to it or interact with it depending on the type of actor it is, based on these, I store a FVector of the mouse click position and change it to PlayerController->GetHitResultUnderCursorByChannel(), which returns the hit.position and the hit.actor, after that, I use the following code in the Tick function to move the character to that position and change it’s looking rotation to it:

void AMyPawn::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	PlayerInput.Sanitize();

	// First look in the direction the Player want's to move to
	FVector DesiredMovementDirection = PlayerInput.GetMovementLocation();

	if (!DesiredMovementDirection.IsNearlyZero() && DesiredMovementDirection != FVector::ZeroVector)
	{
		//Target aquired, time to attack it
		FRotator DirectionToTarget = (DesiredMovementDirection - PlayerDirection->GetComponentLocation()).GetSafeNormal().Rotation();		

		PlayerDirection->SetWorldRotation(FRotator(0.0f, DirectionToTarget.Yaw, 0.0f));

		FVector MovementDirection = PlayerDirection->GetForwardVector();
		FVector Pos = GetActorLocation();
		Pos.X += MovementDirection.X * (PlayerInput.IsDoubleClicked() ? PlayerMovementSpeed * 1.45f : PlayerMovementSpeed) * DeltaTime;
		Pos.Y += MovementDirection.Y * (PlayerInput.IsDoubleClicked() ? PlayerMovementSpeed * 1.45f : PlayerMovementSpeed) * DeltaTime;

		SetActorLocation(Pos);

		Pos = GetActorLocation();

		if (FVector::PointsAreNear(Pos, DesiredMovementDirection, 1.5f))
		{
			PlayerInput.MoveTo(FVector::ZeroVector);
		}
	}    
}

The issue is that once the character has arrived into the location clicked on the screen which is saved inside PlayerInput.GetMovementLocation(), I did a small verification to see if the current location was equal or nearly equal to the PlayerInput.GetMovementLocation() if that was true, it was supposed to set the value in PlayerInput.GetMovementLocation() to FVector::ZeroVector, but it never goes inside that validation, I did it in multiple ways, but I couldn’t get it to work correctly, so I want to ask you guys to see if you have any right ideas on how to verify if the character is right on top or near the clicked location that is stored in PlayerInput.GetMovementLocation()

Here is a Gif showing one of the issues I found with this, If I don’t have the UE4 Editor windows active at the moment, the character moves quite weird even though it has already reached it’s destination.

[GIF Link][1]

The other Issue occurs for the same reason has I have described before, the character reaches it’s position but since it doesn’t enter the if to clear the movement location, the player starts dancing in the same position constantly.

Thank you, and I hope that you guys have some ideas on how to solve this issue.

Hey WoflAlvein,

You can try something along the lines of:

void GAME::Tick( float DeltaTime )
{
    if( TargetLocation != FVector::ZeroVector )
    {
        // 96.f is arbitrary. You can change it to find what works well. 0.f probably wont work well
        if( ( TargetLoation - GetActorLocation( ) ).Size( ) < 96.f )
        {
            // Target reached
        }
    }     
    Super::Tick( DeltaTime );
}

Hi, thank you, that did the trick partially I also had to set the Z value to 0 since I’m not really interested in comparing it with the target Location at this moment, but I have an issue where I see a big difference between the transform and the actual location of the clicked instance, is there anyway to translate the clicked location into real world values so there values match a little more precisely?

here is an example of what is happening: Example

Oh, I understand now. Yes there is. That is what GetHitResultUnderCursor( ) is doing. The FHitResult passed into GetHitResultUnderCursor( ) will return a Hit.ImpactPoint. ImpactPoint being the world location of where the mouse was clicked.

I used the Third Person Template to mock something up:

[TopDownCharacter.h]

#pragma once
#include "GameFramework/Character.h"
#include "TopDownCharacter.generated.h"

UCLASS(config=Game)
class ATopDownCharacter : public ACharacter
{
	GENERATED_BODY()

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
	class USpringArmComponent* CameraBoom;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
	class UCameraComponent* FollowCamera;
public:
	ATopDownCharacter();
	virtual void PossessedBy( AController *In ) override;
	virtual void Tick( float DeltaTime ) override;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Camera)
	float BaseTurnRate;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Camera)
	float BaseLookUpRate;

	FORCEINLINE class USpringArmComponent* GetCameraBoom() const { return CameraBoom; }
	FORCEINLINE class UCameraComponent* GetFollowCamera() const { return FollowCamera; }

protected:

	/** User left clicked */
	void LeftClick( );
	
	/** Character reached click location */
	void ReachedLocation( FVector Reached );

	/** Sets a position in world space to move Character to */
	void SetNewMoveDestination(const FVector DestLocation);

	/** Setup input for user */
	virtual void SetupPlayerInputComponent(class UInputComponent* InputComponent) override;

	/** Character is moving to Destination (LastWorldMouseLocation) */
	bool bTraveling;

	/** Last point in world space where user clicked */
	FVector LastWorldMouseLocation;
};

[TopDownCharacter.cpp]

#include "TopDown.h"
#include "TopDownCharacter.h"

ATopDownCharacter::ATopDownCharacter()
{
	PrimaryActorTick.bCanEverTick = true;
	GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);

	BaseTurnRate = 45.f;
	BaseLookUpRate = 45.f;

	bUseControllerRotationPitch = false;
	bUseControllerRotationYaw = false;
	bUseControllerRotationRoll = false;

	GetCharacterMovement()->bOrientRotationToMovement = true; 	
	GetCharacterMovement()->RotationRate = FRotator(0.0f, 540.0f, 0.0f); 

	CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
	CameraBoom->SetupAttachment();

	FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
	FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName); 
	FollowCamera->bUsePawnControlRotation = false; 
}

// When this Character is taken control of by a Controller
void ATopDownCharacter::PossessedBy(AController *In)
{
	APlayerController *InController = Cast<APlayerController>( In );
	if( InController )
	{
		InController->bShowMouseCursor = true;
	}

	Super::PossessedBy( In );
}

// On frame
void ATopDownCharacter::Tick(float DeltaTime)
{
	if( (GetActorLocation( ) - LastWorldMouseLocation ).Size( ) < 128.f && bTraveling )
	{
		ReachedLocation( LastWorldMouseLocation );
		bTraveling = false;
	}
	Super::Tick( DeltaTime );
}

// Reached target location
void ATopDownCharacter::ReachedLocation( FVector Reached )
{
	UE_LOG( LogTemp, Warning, TEXT("Reached: %f %f %f"), Reached.X, Reached.Y, Reached.Z );
}

// Setup input (remember to match these Axis and Action in the Input settings)
void ATopDownCharacter::SetupPlayerInputComponent(class UInputComponent* InputComponent)
{
	check(InputComponent);
	InputComponent->BindAction("LeftClick", IE_Pressed, this, &ATopDownCharacter::LeftClick );	
}

// Input left click
void ATopDownCharacter::LeftClick()
{
	APlayerController *RealController = Cast<APlayerController>(GetController());
	if( RealController )
	{
		FHitResult Hit;
		RealController->GetHitResultUnderCursor( ECC_Visibility, false, Hit );
		LastWorldMouseLocation = Hit.ImpactPoint;
		bTraveling = true;

		SetNewMoveDestination( LastWorldMouseLocation );
	}
}

// Move to destination, using NavSystem (Level must have a NavMeshBoundsVolume covering walkable space)
void ATopDownCharacter::SetNewMoveDestination(const FVector DestLocation)
{
	if( GetController( ) )
	{
		UNavigationSystem* const NavSys = GetWorld()->GetNavigationSystem();
		float const Distance = FVector::Dist(DestLocation, GetActorLocation());

		if( NavSys && ( Distance > 120.0f ) )
		{
			NavSys->SimpleMoveToLocation(GetController( ), DestLocation);
		}
	}
}

Hi, I see that this code will always show me the exact point it reached but how should I change the Transform or the touched Location to match the same World or Screen value?

I don’t understand what you mean by that. Change the transform of what? And what do you mean by the same world or screen location?

Maybe it’ll be easier to understand if you tell me what your goal is with this.

Yes, of course, Sorry I didn’t explain myself properly.

Let’s say the Player is in the Following Position (0, 0, 0) and then I Click in the screen at position (10, 10, 0), but the system then has to transform this into world location and it ends up into something like (100, 100, 0) and the player starts to move into that location but for the player that location relates to a value of (75, 75, 0) so it reaches that point and it’s location is a little bit different that the one of (75, 75, 0) it could be something like (30, 30, 0) so what I wasn’t is to make both of the values be a little bit more of the same so it really finds the place.

This are some screenshots off what I mean:

Here is the Original Position of the player:

Here is the Position of the player and the Move To Direction, as you can see they differ a lot:

So when I Activate this part of the code that verifies the positions and stops the player movement

{
		FVector Pos = GetActorLocation();
		FVector Target = TargetLocation;
		Target.Z = 0;
		Pos.Z = 0;

		// If I'm near the target location, then I Don't need to move to perform some actions
		if ((Target - Pos).Size() < 50.0f)
		{
			PlayerInput.MoveTo(FVector::ZeroVector);
		}
	}

The Player ends up in a position that is a little bit farther away than it should be, in this last picture I touched the rock that is in the lower left and as you can see it ended quite far than the rock position, and if I lower the value of 50, It wont detect the position correctly

To break it down, this is the data you have.

MouseLocation: A screen space (X, Y) value that represents the 2D pane you can draw a UI to.
TargetLocation: World space( X, Y, Z ) value that represents the 3D “game” space where 3D assets can be rendered.

The difference between the MouseLocation and the TargetLocation is the Z, which is what GetHitResultUnderCursor( ) will convert to. It does this by taking MouseLocation (X,Y) and converting it into World Space ( called a Deproject ) and then it will run a Trace from the direction the camera is facing by a default value of 100000.f cm. If this trace hits any object that matches what you passed into the GetHitResultUnderCursor( ) function, it will give you the FHitResult with the data of that trace.

Now, when it comes to your player not reaching the exact location you pressed. This is normal. It is very difficult (really impossible) to always land on the exact position calculated, which is why this code works:

if( (GetActorLocation( ) - LastWorldMouseLocation ).Size( ) < 128.f )

This is essentially saying, if the distance between the character and the world mouse location (the hit result from the trace which was converted from your left click / touch position on the 2D screen space into 3D world space) is less than 128.f cm, it is close enough to say the player has reached that spot. Again, this is not exact. This is a variance of a 128 cm of reaching the exact location that was converted from your touch / left click location.

Lastly, I still don’t know what your goal is. Is it just to left click on the screen and move the character to the world space under the click? If so, you should look at the Top down template or just duplicate the code above.

Hi.

Sorry I’m just trying to proximate the player to the touched position has much has I can already move it and make him look at the Touched Location, but when I do the

if ((TargetLocation - GetActorLocation()).Size() < 50.0f)

The player still stops quite a way from the target position that was clicked has can be seen in the third picture of my last comment, I clicked the rock but it stopped quite a distance away from it, even tough I take away the Z value from both of the position before I do the test, since I’m not interested in the Z value.

Keep lowering 50.0f until it becomes an issue. You may be able to get closer than that but moving to the exact position will probably not happen.

To clarify:

  • You need to factor the Z when converting 2D screen space into 3D world space.
  • You do not need the Z when using Size( ) if your play space is a flat ground. (MouseLocation.Z and TargetLocation.Z are the same on a flat surface)
  • You will need to factor in Z if you do not have a flat play space, as the distance of those two positions has a different Z value, factoring into the Size( ) calculation.