Interaction System Help

Im trying to make an interaction system for my game and i cant seem to get it work:
#pragma once

include “CoreMinimal.h”
include “GameFramework/Character.h”
include “GameFramework/CharacterMovementComponent.h”
include “PlayerCharacter.generated.h”

class IInteract_Interface; // Forward declaration
class AActor; // Forward declaration

UCLASS()
class FIRSTGAME_API APlayerCharacter : public ACharacter
{
GENERATED_BODY()

public:
// Sets default values for this character’s properties
APlayerCharacter();

protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
float SprintSpeed = 525.f;
float WalkSpeed = 250.f;
float InteractionRange = 200.f;
float InteractCheckInterval = 0.4f;
FTimerHandle InteractCheckTimer;

private:
IInteract_Interface* CurrentInteractableInterface;
AActor* CurrentInteractableActor;

public:
// Called every frame
virtual void Tick(float DeltaTime) override;

// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

protected:

UPROPERTY(EditAnywhere)
class UCameraComponent* Camera; // Creates a camera class (Object) called "Camera" 

UPROPERTY(EditAnywhere, Category = "Camera Shake")
TSubclassOf<class UCameraShakeBase> IdleCameraShake;

UPROPERTY(EditAnywhere, Category = "Camera Shake")
TSubclassOf<class UCameraShakeBase> WalkCameraShake;

UPROPERTY(EditAnywhere, Category = "Camera Shake")
TSubclassOf<class UCameraShakeBase> SprintCameraShake;

void UpdateCameraShakeBasedOnSpeed();


void MoveForward(float InputValue); 
void MoveRight(float InputValue); 

void Turn(float InputValue); 
void LookUp(float InputValue); 

void StartSprint();
void StopSprint();

void Interact();
void CheckForInteractable();

};

this is my PlayerCharacter.h file

include “FirstGame/Player/PlayerCharacter.h”
include “Camera/CameraComponent.h” // Includes the Camera class created in PlayerCharacter.h
include “Kismet/GameplayStatics.h”
include “FirstGame/Interaction/Interact_Interface.h”
include “DrawDebugHelpers.h”

// Sets default values when unreal engine is opened
APlayerCharacter::APlayerCharacter()
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don’t need it.
PrimaryActorTick.bCanEverTick = true;

Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("Player Camera")); // Creates a camera called "Player Camera" using the class created in PlayerCharacter.h
Camera->SetupAttachment(RootComponent); // Attaches the camera to the player
Camera->bUsePawnControlRotation = true;

IdleCameraShake = nullptr;
WalkCameraShake = nullptr;
SprintCameraShake = nullptr;
CurrentInteractableInterface = nullptr;

}

// Called when the game starts or when spawned
void APlayerCharacter::BeginPlay()
{
Super::BeginPlay();

GetWorldTimerManager().SetTimer(InteractCheckTimer, this, &APlayerCharacter::CheckForInteractable, InteractCheckInterval, true);

}

// Called every frame
void APlayerCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);

UpdateCameraShakeBasedOnSpeed();

}

// Called to bind functionality to input
void APlayerCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);

PlayerInputComponent->BindAxis("MoveForward", this, &APlayerCharacter::MoveForward); 
PlayerInputComponent->BindAxis("MoveRight", this, &APlayerCharacter::MoveRight); 

PlayerInputComponent->BindAxis("Turn", this, &APlayerCharacter::Turn); 
PlayerInputComponent->BindAxis("LookUp", this, &APlayerCharacter::LookUp); 

PlayerInputComponent->BindAction("Sprint", IE_Pressed, this, &APlayerCharacter::StartSprint);
PlayerInputComponent->BindAction("Sprint", IE_Released, this, &APlayerCharacter::StopSprint);

PlayerInputComponent->BindAction("Interact", IE_Pressed, this, &APlayerCharacter::Interact);

}

void APlayerCharacter::UpdateCameraShakeBasedOnSpeed() // Camera Shake For Player Movement Based On Speed
{
APlayerController* PlayerController = UGameplayStatics::GetPlayerController(this, 0);

FVector CharacterVelocity = GetCharacterMovement()->Velocity;
float CharacterSpeed = CharacterVelocity.Size();

float IdleSpeedThreshold = 100.0f;
float WalkSpeedThreshold = 300.0f;
float SprintSpeedThreshold = 550.f;

TSubclassOf<UCameraShakeBase> CameraShakeToPlay = nullptr;

if (CharacterSpeed < IdleSpeedThreshold)
{
	CameraShakeToPlay = IdleCameraShake;
}
else if (CharacterSpeed < WalkSpeedThreshold)
{
	CameraShakeToPlay = WalkCameraShake;
}
else if (CharacterSpeed < SprintSpeedThreshold)
{
	CameraShakeToPlay = SprintCameraShake;
}

if (PlayerController && CameraShakeToPlay)
{
	PlayerController->ClientStartCameraShake(CameraShakeToPlay);
}

}

void APlayerCharacter::MoveForward(float InputValue) // Defines what the MoveForward function created in PlayerCharacter.h does
{
FVector ForwardDirection = GetActorForwardVector(); // Gets direction player is facing
AddMovementInput(ForwardDirection, InputValue); // Adds movement input forward / backward
}

void APlayerCharacter::MoveRight(float InputValue) // Defines what the MoveRight function created in PlayerCharcter.h does
{
FVector RightDirection = GetActorRightVector(); // Gets direction of players right side
AddMovementInput(RightDirection, InputValue); // Adds movement input right / left
}

void APlayerCharacter::Turn(float InputValue) // Defines what the Turn Function created in PlayerCharacter.h does
{
AddControllerYawInput(InputValue); // Turns Camera
}

void APlayerCharacter::LookUp(float InputValue) // Defines what the LookUp function created in PlayerCharacter.h does
{
AddControllerPitchInput(InputValue); // Turns Camera
}

void APlayerCharacter::StartSprint() // Starts Sprint
{
UCharacterMovementComponent* PlayerCharacterMovement = GetCharacterMovement();
if (PlayerCharacterMovement) {
PlayerCharacterMovement->MaxWalkSpeed = SprintSpeed;
}

}

void APlayerCharacter::StopSprint() // Stops Sprint
{

UCharacterMovementComponent* PlayerCharacterMovement = GetCharacterMovement();
if (PlayerCharacterMovement) {
	PlayerCharacterMovement->MaxWalkSpeed = WalkSpeed;
}	

}

void APlayerCharacter::Interact() // Interacts
{
if (CurrentInteractableInterface)
{
UE_LOG(LogTemp, Warning, TEXT(“INTERACT - CurrentInteractableInterface is valid”));
IInteract_Interface* Interactable = Cast<IInteract_Interface>(CurrentInteractableActor);
if (Interactable)
{
UE_LOG(LogTemp, Warning, TEXT(“INTERACT - CurrentInteractableActor is valid”));
// Call the Interact function on the interactable item
Interactable->Interact(this);
}
else
{
UE_LOG(LogTemp, Warning, TEXT(“INTERACT - CurrentInteractableActor is NOT valid”));
}
}
else
{
UE_LOG(LogTemp, Warning, TEXT(“INTERACT - CurrentInteractableInterface is NOT valid”));
}
}

void APlayerCharacter::CheckForInteractable()
{
FVector Start = Camera->GetComponentLocation();
FVector ForwardVector = Camera->GetForwardVector();
FVector End = Start + (ForwardVector * InteractionRange);

FHitResult HitResult;
FCollisionQueryParams TraceParams(FName(TEXT("InteractTrace")), false, this);

// Check if the previously interacted object is still in focus, and hide its prompt if not
if (CurrentInteractableActor)
{
	UE_LOG(LogTemp, Warning, TEXT("CURRENTINTERACTABLE"));
	IInteract_Interface* Interactable = Cast<IInteract_Interface>(CurrentInteractableActor);
	if (Interactable)
	{
		Interactable->HideInteractionPrompt();
		UE_LOG(LogTemp, Warning, TEXT("HIDEPROMPT"));
	}
	CurrentInteractableActor = nullptr;
}

if (GetWorld()->LineTraceSingleByChannel(HitResult, Start, End, ECC_Visibility, TraceParams))
{
	// Check if the hit actor implements the InteractableInterface
	AActor* HitActor = HitResult.GetActor();
	UE_LOG(LogTemp, Warning, TEXT("Hit Actor: %s"), *HitActor->GetName());
	UE_LOG(LogTemp, Warning, TEXT("Hit Location: %s"), *HitResult.ImpactPoint.ToString());
	IInteract_Interface* Interactable = Cast<IInteract_Interface>(HitActor);
	if (Interactable)
	{
		// Display interaction prompt
		Interactable->ShowInteractionPrompt();
		CurrentInteractableActor = HitActor; // Update the current interactable object
		UE_LOG(LogTemp, Warning, TEXT("SHOWPROMPT"));
	}
}

}

this is my PlayerCharacter.cpp file where most of the system is taking place
#pragma once

include “CoreMinimal.h”
include “UObject/Interface.h”
include “Interact_Interface.generated.h”

class APlayerCharacter; // Forward declaration

// This class does not need to be modified.
UINTERFACE(MinimalAPI)
class UInteract_Interface : public UInterface
{
GENERATED_BODY()
};

/**

  • Interface for interactable objects.
    */
    class FIRSTGAME_API IInteract_Interface
    {
    GENERATED_BODY()

public:
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = “Interaction”)
void ShowInteractionPrompt();

UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Interaction")
void Interact(APlayerCharacter* PlayerCharacter);

UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Interaction")
void HideInteractionPrompt();

};

and this is my Interact_Interface.h file

the Interact_Interface.cpp file only contains a header to the .h file
at the moment i can add the interface to actors and use the 3 functions i created in blueprints Showprompt, hideprompt, and interact. But none of the functions run and i dont think the system is actually detecting the interface but i could be wrong.
If anyone knows where i went wrong please let me know it would be a life saver!

When working with CPP+BP interfaces, avoid using Cast and avoid calling interface functions directly.

For testing interface, use

if (HitActor->Implements<UInteract_Interface>())
{
    //...
}

Store only the AActor* (or UObject*) in a variable, you don’t need a pointer to the interface.

When calling interface function, use

IInteract_Interface::Execute_ShowInteractionPrompt(HitActor);

IInteract_Interface::Execute_Interact(CurrentInteractableActor);

Yeah that fixed the problem, thanks man, but now it crashes when i interact and constantly calls show/hide prompt when looking at the object

void APlayerCharacter::Interact() // Interacts
{
if (CurrentInteractableActor)
{
if (CurrentInteractableActor->Implements<UInteract_Interface>())
{
IInteract_Interface::Execute_Interact(CurrentInteractableActor, this);
}
else
{

	}
}
else
{
	UE_LOG(LogTemp, Warning, TEXT("INTERACT - CurrentInteractableActor is NOT valid"));
}

}

void APlayerCharacter::CheckForInteractable()
{
FVector Start = Camera->GetComponentLocation();
FVector ForwardVector = Camera->GetForwardVector();
FVector End = Start + (ForwardVector * InteractionRange);

FHitResult HitResult;
FCollisionQueryParams TraceParams(FName(TEXT("InteractTrace")), false, this);

// Check if the previously interacted object is still in focus, and hide its prompt if not
if (CurrentInteractableActor)
{
	UE_LOG(LogTemp, Warning, TEXT("CURRENTINTERACTABLE"));
	if (CurrentInteractableActor->Implements<UInteract_Interface>())
	{
		IInteract_Interface::Execute_HideInteractionPrompt(CurrentInteractableActor);
		UE_LOG(LogTemp, Warning, TEXT("HIDEPROMPT"));
	}
	CurrentInteractableActor = nullptr;
}

if (GetWorld()->LineTraceSingleByChannel(HitResult, Start, End, ECC_Visibility, TraceParams))
{
	// Check if the hit actor implements the InteractableInterface
	AActor* HitActor = HitResult.GetActor();
	UE_LOG(LogTemp, Warning, TEXT("Hit Actor: %s"), *HitActor->GetName());
	UE_LOG(LogTemp, Warning, TEXT("Hit Location: %s"), *HitResult.ImpactPoint.ToString());
	if (HitActor->Implements<UInteract_Interface>())
	{
		// Display interaction prompt
		IInteract_Interface::Execute_ShowInteractionPrompt(HitActor);
		CurrentInteractableActor = HitActor; // Update the current interactable object
		UE_LOG(LogTemp, Warning, TEXT("SHOWPROMPT"));
	}
}

}

i tried using do once before but i dont know how to properly use it, it seems to form an infinite loop when interacting.

Your function CheckForInteractable is hiding the prompt unconditionally, then the trace is showing it again, so those constant calls are normal, you have a bit of a logic issue there.
The logic should be more like this :

void APlayerCharacter::CheckForInteractable()
{
    LineTrace(HitResult, ...);
    AActor* HitActor = HitResult.GetActor();

    // If nothing changed, do nothing
    if (HitActor == CurrentInteractableActor)
        return;

    // Else, we are not looking at the shown actor anymore
    if (CurrentInteractableActor)
    {
        IInterface::Execute_HidePrompt(CurrentInteractableActor);
        CurrentInteractableActor = nullptr;
    }

    // Are we looking at something interactable
    if (HitActor && HitActor->Implements<UInterface>())
    {
        CurrentInteractableActor = HitActor;
        IInterface::Execute_ShowPrompt(CurrentInteractableActor);
    }
}

Regarding the crash, I don’t know, depends on your code.
Are you calling back to Character->Interact from the interactable actor ?
Otherwise, the crash stack trace should help figure out what is looping.

Okay thanks, im still quite new to c++ but im hoping to learn more!

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.