I’m working on a first person game that has objects in the world that you can “use” once you are close to them and facing them. Currently I am doing this by checking for overlap of a box that is attached to the camera with the actors in the world that inherit from my “useable” class.
I’d like to display an icon on the screen, similar to Resident Evil 7 or Soma, that alerts the player that they are able to interact with the object from a distance. I currently have text that just appears on the screen from the debug log.
What is the right way to display a 2d icon on top of the object? I’d like to change my class to have a UPROPERTY that you pass in which will be the 2d icon to display, which would be a texture I suppose.
Thanks!
Attached is an example picture from RE7, and my code for my player.
#include “MainCharacter.h”
#include “GameFramework/CharacterMovementComponent.h”
#include “Components/CapsuleComponent.h”
#include “Components/InputComponent.h”
#include “Camera/CameraComponent.h”
#include “Engine/World.h”
#include “Components/BoxComponent.h”
#include “ReactToUseInterface.h”
AMainCharacter::AMainCharacter()
{
// change the capsule component size
GetCapsuleComponent()->InitCapsuleSize(15, 80);
// create camera
FirstPersonCameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("FirstPersonCamera"));
FirstPersonCameraComponent->SetupAttachment(GetCapsuleComponent());
FirstPersonCameraComponent->RelativeLocation = FVector(0.f, 0.f, 165.f - 80.f);
FirstPersonCameraComponent->bUsePawnControlRotation = true;
// create box to detect overlap of useable actors and attach it to the camera
BoxComponentDetectUseableActors = CreateDefaultSubobject<UBoxComponent>(TEXT("DetectUseableBox"));
BoxComponentDetectUseableActors->InitBoxExtent(FVector(30, 15, 30));
BoxComponentDetectUseableActors->SetupAttachment(FirstPersonCameraComponent);
BoxComponentDetectUseableActors->RelativeLocation = FVector(30.f, 0.f, 0.f);
BoxComponentDetectUseableActors->OnComponentBeginOverlap.AddDynamic(this, &AMainCharacter::OnOverlapBegin);
BoxComponentDetectUseableActors->OnComponentEndOverlap.AddDynamic(this, &AMainCharacter::OnOverlapEnd);
// adjust movement speeds
GetCharacterMovement()->MaxWalkSpeed = 150;
GetCharacterMovement()->NavAgentProps.bCanCrouch = true;
GetCharacterMovement()->MaxWalkSpeedCrouched = 100;
ActiveObject = nullptr;
bZoomingIn = false;
PrimaryActorTick.bCanEverTick = true;
ZoomFactor = 0;
}
void AMainCharacter::BeginPlay()
{
Super::BeginPlay();
}
void AMainCharacter::CrouchOn() {
Crouch();
}
void AMainCharacter::CrouchOff() {
UnCrouch();
}
void AMainCharacter::MoveForward(float input)
{
FRotator Rotation = GetControlRotation();
Rotation.Pitch = 0;
FVector MovementDirection = FRotationMatrix(Rotation).GetScaledAxis(EAxis::X);
AddMovementInput(MovementDirection, input);
}
void AMainCharacter::MoveRight(float input)
{
FRotator Rotation = GetControlRotation();
Rotation.Pitch = 0;
FVector MovementDirection = FRotationMatrix(Rotation).GetScaledAxis(EAxis::Y);
AddMovementInput(MovementDirection, input);
}
void AMainCharacter::OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult &SweepResult) {
if (OtherActor && OtherActor->GetClass()->ImplementsInterface(UReactToUseInterface::StaticClass())) {
if (GEngine) GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::White, TEXT(“Found usable!”));
ActiveObject = OtherActor;
}
}
void AMainCharacter::OnOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex) {
if (ActiveObject != nullptr) {
if (GEngine) GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::White, TEXT(“Discarded usable.”));
ActiveObject = nullptr;
}
}
void AMainCharacter::Tick(float DeltaTime)
{
if (bZoomingIn) ZoomFactor += DeltaTime / .5f;
else ZoomFactor -= DeltaTime / .25f;
ZoomFactor = FMath::Clamp<float>(ZoomFactor, 0.0f, 1.0f);
FirstPersonCameraComponent->FieldOfView = FMath::Lerp<float>(90.0f, 30.0f, ZoomFactor);
}
void AMainCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
InputComponent->BindAxis("MoveForward", this, &AMainCharacter::MoveForward);
InputComponent->BindAxis("MoveRight", this, &AMainCharacter::MoveRight);
InputComponent->BindAxis("Turn", this, &AMainCharacter::AddControllerYawInput);
InputComponent->BindAxis("LookUp", this, &AMainCharacter::AddControllerPitchInput);
InputComponent->BindAction("Use", IE_Pressed, this, &AMainCharacter::UseObject);
InputComponent->BindAction("Crouch", IE_Pressed, this, &AMainCharacter::CrouchOn);
InputComponent->BindAction("Crouch", IE_Released, this, &AMainCharacter::CrouchOff);
InputComponent->BindAction("Zoom", IE_Pressed, this, &AMainCharacter::ZoomIn);
InputComponent->BindAction("Zoom", IE_Released, this, &AMainCharacter::ZoomOut);
}
void AMainCharacter::UseObject() {
/*
// This is code for when you trace to an actor to use it. It didn’t work well as the tolerance for looking at the object is
// exact.
FHitResult HitResult(ForceInit);
FVector StartTrace = FirstPersonCameraComponent->GetComponentLocation();
FVector ForwardVector = FirstPersonCameraComponent->GetForwardVector();
FVector EndTrace = (ForwardVector * 100.f) + StartTrace;
FCollisionQueryParams TraceParams(ForceInit);
if (GetWorld()->LineTraceSingleByChannel(HitResult, StartTrace, EndTrace, ECC_Visibility, TraceParams)) {
ActiveObject = HitResult.GetActor();
if (ActiveObject != nullptr) {
if (ActiveObject->GetClass()->ImplementsInterface(UReactToUseInterface::StaticClass())) {
IReactToUseInterface *ReactingObject = Cast<IReactToUseInterface>(ActiveObject);
ReactingObject->ReactToUse();
}
}
}*/
if (ActiveObject != nullptr) {
if (ActiveObject->GetClass()->ImplementsInterface(UReactToUseInterface::StaticClass())) {
IReactToUseInterface *ReactingObject = Cast<IReactToUseInterface>(ActiveObject);
ReactingObject->ReactToUse();
}
}
}
void AMainCharacter::ZoomIn() {
bZoomingIn = true;
}
void AMainCharacter::ZoomOut() {
bZoomingIn = false;
}