How do you get player cursor from component or widget in C++

I am working on an inventory system and want to drop the item where the cursor is in an isometric view.

So I have an InventoryComponent.h that I have mounted on the player pawn with a drop item method:

bool UInventory::DropItem(const int32 Index, UInventory* Inventory)
{
	UE_LOG(LogTemp, Warning, TEXT("DropItem: Removed item from inventory"));
	const AFarmingCharacter* Owner = Cast<AFarmingCharacter>(GetOwner());	
	if (!Owner)
	{	
		UE_LOG(LogTemp, Warning, TEXT("DropItem: Owner is null"));
		return false;
	}

	AFarmingPlayerController* FarmingPlayerController = Cast<AFarmingPlayerController>(Owner->GetController());
	if (!FarmingPlayerController)
	{
		UE_LOG(LogTemp, Warning, TEXT("DropItem: Owner is not a FarmingPlayerController"));
		return false;
	}
	
	const FHitResult HitResult = FarmingPlayerController->GetCursorWorldLocation();	
	// Debug trace from the owner camera to the player pawn
	DrawDebugLine(GetWorld(), Owner->GetCameraBoom()->GetComponentLocation(), Owner->GetActorLocation()	, FColor::Red, false, 60.0f, 0, 1.0f);

	const FVector Start = HitResult.TraceStart;
	const FVector End = HitResult.TraceEnd;

	if (HitResult.bBlockingHit)
	{
		UE_LOG(LogTemp, Warning, TEXT("DropItem: did not hit anything"));
		return false;
	}

	if (GetOwnerRole() == ROLE_Authority)
	{
		DropItem(Index, Inventory, Start, End);
		return true;
	} else
	{
		Server_DropItem(Index, Inventory, Start, End);
		return true;
	}
}

bool UInventory::DropItem(const int32 Index, UInventory* Inventory, FVector Start, FVector End)
{
	if (GetOwnerRole() == ROLE_Authority)
	{
		UE_LOG(LogTemp, Warning, TEXT("DropItem as server"));
		if (Inventory)
		{
			UE_LOG(LogTemp, Warning, TEXT("DropItem: Inventory found"));
			FItemData ItemData = Inventory->GetItem(Index);
			if (Inventory->RemoveItem(Index, 1))
			{
				UE_LOG(LogTemp, Warning, TEXT("DropItem: HitResult.TraceStart: %s, HitResult.ImpactPoint: %s"), *Start.ToString(), *End.ToString());

				FHitResult HitResult;
				FCollisionQueryParams Params;
				
				GetWorld()->LineTraceSingleByChannel(HitResult, Start, End, ECollisionChannel::ECC_Visibility, Params);
				
				// Debug trace
				DrawDebugLine(GetWorld(), HitResult.TraceStart, HitResult.ImpactPoint, FColor::Blue, false, 2.0f, 0, 1.0f);

				UE_LOG(LogTemp, Warning, TEXT("DropItem: HitResult.TraceStart: %s, HitResult.ImpactPoint: %s"), *HitResult.TraceStart.ToString(), *HitResult.ImpactPoint.ToString());

				// If the hit from the client and server are the same within a certain tolerance, we assume that the client and server are dropping on the same place
				if (HitResult.bBlockingHit && Start.Distance(Start, HitResult.TraceStart) < 10.0f && End.Distance(End, HitResult.TraceEnd) < 10.0f)
				{
					UE_LOG(LogTemp, Warning, TEXT("DropItem: HitResult.bBlockingHit is true"));
					FVector HitLocation = HitResult.ImpactPoint;
					HitLocation.Z = HitResult.ImpactPoint.Z + 10.0f;
					
					if (UWorld* World = GetWorld())
					{
						World->SpawnActor<AItem>(ItemData.ItemClass, HitLocation, FRotator::ZeroRotator);
						return true;
					}
					else
					{
						UE_LOG(LogTemp, Warning, TEXT("DropItem: World is null"));
					}
				}
				else
				{
					UE_LOG(LogTemp, Warning, TEXT("DropItem: HitResult.bBlockingHit is false"));
				}
	
			}
			else
			{
				UE_LOG(LogTemp, Warning, TEXT("DropItem: Failed to remove item from inventory"));
			}
		}
		else
		{
			UE_LOG(LogTemp, Warning, TEXT("DropItem: Inventory not found"));
		}
	}
	else
	{
		Server_DropItem(Index, Inventory, Start, End);
		return true;
	}

	UE_LOG(LogTemp, Warning, TEXT("DropItem: Failed to drop item"));
	return false;
}

bool UInventory::Server_DropItem_Validate(const int32 Index, UInventory* Inventory, FVector Start, FVector End)
{
	return true;
}

void UInventory::Server_DropItem_Implementation(const int32 Index, UInventory* Inventory, FVector Start, FVector End)
{
	DropItem(Index, Inventory, Start, End);
}

In InventoryComponent.h

protected:
	UFUNCTION(BlueprintCallable, Category = "Inventory")
	bool DropItem(const int32 Index, UInventory* Inventory);
	bool DropItem(const int32 Index, UInventory* Inventory, FVector Start, FVector End);

	UFUNCTION(Server, Reliable, WithValidation)
	void Server_DropItem(const int32 Index, UInventory* Inventory, FVector Start, FVector End);

AFarmingPlayerController.h

FHitResult AFarmingPlayerController::GetCursorWorldLocation()
{
	FVector2D ScreenLocation;
	FHitResult Hit;

	// Get the mouse position in screen space
	if (GetMousePosition(ScreenLocation.X, ScreenLocation.Y))
	{
		FVector WorldLocation, WorldDirection;
		DeprojectScreenPositionToWorld(ScreenLocation.X, ScreenLocation.Y, WorldLocation, WorldDirection);

		const FVector TraceStart = WorldLocation;
		const FVector TraceEnd = TraceStart + (WorldDirection * 10000.0f);

		// Perform a line trace to get the hit result
		GetWorld()->LineTraceSingleByChannel(Hit, TraceStart, TraceEnd, ECC_Visibility);

		// Debug trace
		DrawDebugLine(GetWorld(), TraceStart, TraceEnd, FColor::Blue, false, 2.0f, 0, 1.0f);
		UE_LOG(LogTemp, Warning, TEXT("GetCursorWorldLocation: Hit.TraceStart: %s, Hit.ImpactPoint: %s"), *Hit.TraceStart.ToString(), *Hit.ImpactPoint.ToString());
	}
	else
	{
		UE_LOG(LogTemp, Warning, TEXT("GetCursorWorldLocation: Unable to get mouse position"));
	}

	return Hit;
}

While if we use a Key input like “N” to test if the player controller method works, we can get to be called correctly. With a test method like this:

void AFarmingCharacter::Test() const
{
	// Additional logging to debug the issue
	AFarmingPlayerController* FarmingPlayerController = Cast<AFarmingPlayerController>(GetController());
	
	const FHitResult HitResult = FarmingPlayerController->GetCursorWorldLocation();
	UE_LOG(LogTemp, Warning, TEXT("Test: HitResult.TraceStart: %s, HitResult.ImpactPoint: %s"), *HitResult.TraceStart.ToString(), *HitResult.ImpactPoint.ToString());
}

Where I am struggeling now is to actually do the same on a drop event from UMG:

How I set the player pawn on the dropped UMG (the inventory main screen)

On the UMG widget that created the drag operation (the inventory item)

Each inventory item widget is created from a method on the inventory grid

Which is called from the Player pawn and passed the replicated Inventory component

Where would I start to look for what might be the issue here?

Why not just create a static library to handle this and call it from with in UMG?
.h

#pragma once

#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "InventoryFunctionLibrary.generated.h"

UCLASS()
class YOUR_API UInventoryFunctionLibrary : public UBlueprintFunctionLibrary
{
	GENERATED_BODY()
	
	UFUNCTION(BlueprintCallable)
	static FVector GetPosition(ETraceTypeQuery channel);

};

.cpp

#include "InventoryFunctionLibrary.h"
#include "Kismet/GameplayStatics.h"

FVector UInventoryFunctionLibrary::GetPosition(ETraceTypeQuery channel)
{
	FVector2D ScreenLocation;
	UWorld* world = GEngine->GameViewport->GetWorld();
	APlayerController* pc = UGameplayStatics::GetPlayerController(world, 0);
	FHitResult res;

	pc->GetHitResultUnderCursorByChannel(channel, true, res);
	return res.Location;	
}

this will return the hit results under the cursor but it can be called anywhere inside of your project.

Seems like the issue is probably that I try to call these methods, regardless where I put them, the player pawn, the controller or in an BlueprintFunctionLibrary from an OnDrop even in an UMG.

The UMG then seems incapable of getting the correct mouse position even if we can cast to the correct controller or pawn.

Could it be I need to get the value another way then? Constantly update it somewhere, so the OnDrop can just read the already updated value or something similar?

I don’t do much in blueprints, but I’m guessing it has to do with UMG DPI pixels and actual screen pixels being different. Remember that UMG scales to screen space so has slightly different coordinates.

In C++ there is “UWidgetLayoutLibrary::GetMousePositionScaledByDPI()” which we use to convert mouse cursor location to UMG HUD location. I assume blueprints have it as well.

We also use “UWidgetLayoutLibrary::ProjectWorldLocationToWidgetPosition()” in radars / minimaps.

Header

#pragma once

#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "CustomLayoutLibrary.generated.h"

UCLASS()
class YOUR_API UCustomLayoutLibrary : public UBlueprintFunctionLibrary
{
	GENERATED_BODY()
	
	UFUNCTION(BlueprintCallable)
	static FVector2D GetMousePosition();
};

Cpp

#include "CustomLayoutLibrary.h"
#include "Blueprint/WidgetLayoutLibrary.h"
#include "Kismet/GameplayStatics.h"

FVector2D UCustomLayoutLibrary::GetMousePosition()
{
	UWorld* world = GEngine->GameViewport->GetWorld();
	FVector2D mousePos = FVector2D();
	APlayerController* PC = UGameplayStatics::GetPlayerController(world, 0);
	if (UWidgetLayoutLibrary::GetMousePositionScaledByDPI( PC , mousePos.X, mousePos.Y)) {
		return mousePos;
	}
	return FVector2D();
}

Add “UMG” to dependency modules in your build file

Gets cursor coordinates from blueprints and UMG


2

Works with no problems. Normally I don’t bind directly but for a quick test it shows up correctly

1 Like