C++ ACharacter overlapping different kinds of AActors - Observer pattern?

Dear experts,

I am a beginner and trying to set up a 3D Jump’n’Run game.
I have a character that runs through the map and is collecting actors such as cones, cubes, etc.

In my “ConesCharacter.cpp” I setup an “OnOverlapBegin” method (I followed https://unrealcpp.com/on-overlap-begin). So my Character is running around, overlapping actors which
disappear and a counter counts and a HUD shows the counter value - please see attached screenshot.



void AConesCharacter::OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
    //let's get an instance of MyGameInstance
    UMyGameInstance *MGI = Cast<UMyGameInstance>(GetGameInstance());

    if (OtherActor && (OtherActor != this) && OtherComp){

        FName Name = OtherActor->GetClass()->GetFName();

        //we make sure the "ConeCounter" value is equal to what is in MyGameInstance; otherwise
        //when we switch levels "ConeCounter" starts from 0 again
        ConeCounter = MGI->InterLevelConeCountValue;

        if (Name == "Cone_BP_C") {
            ConeCounter++;
        }

        //update the user widget with "ConeCounter" value
        if (LocTextControl != nullptr) {
            LocTextControl->SetText(FText::FromString(FString::FromInt(this->GetConeCounterValue())));

        }
        //let's save the new "ConeCounter" value to our MyGameInstance

        if (MGI)
        {
            MGI->InterLevelConeCountValue = ConeCounter;
            UE_LOG(LogTemp, Warning, TEXT("cone count value: %i"), MGI->InterLevelConeCountValue);
        }
        //now if our character overlaps the power up mesh (yellow key) we switch to the next level
        if (Name == "Pl_PowerUp_03_C") {
            SwapLevel();
        }
    }
}


You see in the overlap method there is too much going on: For example I check if I am overlapping a cone


if (Name == "Cone_BP_C") 

or another object


if (Name == "Pl_PowerUp_03_C")

. I update the user widget counter:


if (LocTextControl != nullptr) {
            LocTextControl->SetText(FText::FromString(FString::FromInt(this->GetConeCounterValue())));

        }

Question:
How do I check what which kind of actor is overlapped? Would an observer pattern help? Do I make the overlapped actors delegates or my ConeCharacter a delegate? Do I update the
User widget counter from the PlayerController or leave it in the Overlap method of the ConeCharacter?

Thank you for any useful information.

Best regards, Peter

You could create a base class for your collectables with a type enum, cast to your collectable type on overlap and read the enum.

You can avoid being concerned about the type by giving the collectables a common base class or interface function and calling it.

for example


auto * Collectable = Cast<ACollectable> OverlapTarget;

if (Collectable)
{
  Collectable->HandleOverlap();
}

Thank you both for your help with this. I got one thing to work. I avoided the Enums as I am not familiar with these at all yet. I created the Collectable class as follows:


// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Collectable.generated.h"

UCLASS()
class CONES_API ACollectable : public AActor
{
    GENERATED_BODY()

public:    
    // Sets default values for this actor's properties
    ACollectable();

    UFUNCTION(BlueprintCallable, Category = "Collectable")
        virtual void OnOverlap(AActor* OverlappedActor, AActor* OtherActor);

protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;
};



// Fill out your copyright notice in the Description page of Project Settings.
#include "Collectable.h"

// Sets default values
ACollectable::ACollectable()
{
     // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = true;
}

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

void ACollectable::OnOverlap(AActor* OverlappedActor, AActor* OtherActor)
{

}

My Cone.h inherits from ACollectable


UCLASS()
class CONES_API ACone : public ACollectable
{
    GENERATED_BODY()

public:    
    // Sets default values for this actor's properties
    ACone();

    virtual void OnOverlap(AActor* OverlappedActor, AActor* OtherActor) override;
    //FConeDelegate OnOverlapEvent;

};

And my Cone.cpp implements the OnOverlap method and when I play the game the cone gets destroyed, that works fine:


#include "Cone.h"

// Sets default values
ACone::ACone()
{
     // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = true;
    OnActorBeginOverlap.AddDynamic(this, &ACone::OnOverlap);
}

void ACone::OnOverlap(AActor* OverlappedActor, AActor* OtherActor)
{
    Super::OnOverlap(OverlappedActor, OtherActor);
    Destroy();
}

But I am struggling with my Character. In ConesCharacter.h I set a reference to ACollectable:


class ACollectable* Collectable;

In the method “void AConesCharacter::OnOverlapBegin” I mentioned in my original post I am now calling:


Collectable = Cast<ACone>(Collectable);
        if (Collectable) {
            Collectable->OnOverlap(Collectable,this);
            UE_LOG(LogTemp, Warning, TEXT("collectable on overlap called"));
        }

But my UE_LOG is never called. It seems that the “Collectable” object does not exist at all. But why not? What am I doing wrong?
Is “void AConesCharacter::OnOverlapBegin” the issue?

Thank you again for any ideas on this.

Best regards, Peter

Do you mean



Collectable = Cast<ACollectable*>(**OtherActor**);


?

Hi Cowboy,

Thank you, that was a good hint and I modified that call so now it tells me it is overlapping a cone:


        auto* CollectItem = Cast<ACollectable>(OtherActor);

        if (CollectItem) {
            CollectItem->OnOverlap(this, CollectItem);
            FString name = CollectItem->GetName();
            UE_LOG(LogTemp, Warning, TEXT("I overlapped a %s"),*name);
        }


Log:
LogTemp: Warning: I overlapped a Cone_BP_9
LogTemp: Warning: I overlapped a Cone_BP2_5
LogTemp: Warning: I overlapped a Cone_BP_2
LogTemp: Warning: I overlapped a Cone_BP_3


I have to set up other actors I can overlap (triangles, cubes, etc). Depending on the overlapped actor different events must be fired. I have to think how I will do that, maybe Enums would be the best way to go like “Telimaktar” had proposed.

Best regards, Peter

Another thing to consider is storing your counters in gamestate. Then have the collectable handle updating the relevant counter itself. Your UI will read the counters from your gamestate, you just need to trigger refreshes for your UI widgets to display the updated value.

Hello Telimaktar,

Thank you for your input again. You are right, currently I count the overlapped actors in my Character class but then save it to the GameInstance “UMyGameInstance” because that is the only way I saw to carry the value over to the next map/level.
I have to see how I can make “Collectable.cpp” count that value.

However, I do not know if I should open a new thread for this but I am struggling with the Enums. In my

Collectable.h I have


UENUM(BlueprintType)
enum class ECollectablesEnum : uint8 {
    Enum_Cone UMETA(DisplayName = "Cone"),
    Enum_Cone_Bouncing UMETA(DisplayName = "ConeBouncing")
};

UCLASS()
class CONES_API ACollectable : public AActor
{
...
private:
    UPROPERTY(VisibleAnywhere, Category = "Enum")
        ECollectablesEnum CollectablesEnum;


Collectable.cpp



ECollectablesEnum ACollectable::GetCollectablesEnum() const
{
    return CollectablesEnum;
}


ConesCharacter.cpp



void AConesCharacter::OnOverlapBegin(.....){

auto* CollectItem = Cast<ACollectable>(OtherActor);

        if (CollectItem) {
            CollectItem->OnOverlap(this, CollectItem);
            FString name = CollectItem->GetName();
            UE_LOG(LogTemp, Warning, TEXT("I overlapped a %s"),*name);

            auto EnumName = CollectItem->GetCollectablesEnum();

        }

....
}


But my “auto EnumName” is empty. I tried following this A new, community-hosted Unreal Engine Wiki - Announcements and Releases - Unreal Engine Forums but I am getting lost.
And how do I test for the Enums? I wanted to avoid an “if” or “switch” statement. How do I know what Collectable I overlapped?

Thank you again for any ideas on this.

Best regards, Peter

You are possibly not setting the enum, try setting them in the Constructor or in the Blueprint for your Collectable type.

Constructor:

ACollectable:ACollectable() : Super() {
CollectableType = ECollectableEnum::Part;
}

Blueprint
Move your variable to public: and change the UPROPERTY to UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = “Collectable”)
then you can change it in your Blueprint which then should have a value when you test it on overlap.

Hello Telimaktar,

Reading the Enums only works when I set them in the derived classes of ACollectable(), for example:


ACone::ACone()
{
    OnActorBeginOverlap.AddDynamic(this, &ACone::OnOverlap);
    CollectablesEnum = ECollectablesEnum::Enum_Cone;
}

I might be doing something wrong. You mentioned earlier that I can somehow cast from the Collectable object to the actual Cone for example. I will try around but I am pretty
happy if it works like this.

Best regards, Peter

So if you’re creating a class per collectable you can roll like you have, just add a public method to access the enum value.
You shouldn’t need to cast to Cone, the Idea is to cast to Collectable since all collectables share this class. You can implement a virtual method that you override in Cone and other collectables to perform custom logic for each if required.

You would cast in the overlap method:

auto collectable = Cast<ACollectable>(OtherActor);
if(collectable){ //dostuff
collectable->CustomOverridenMethod();
}

What is the reason for creating a class per collectable? Is there custom logic to each one?

Hello Telimaktar,

sorry for my late answer and thank you again for your input. Yes, there is custom logic to everyone, a cube could trigger different events than a cone or a sphere
for example. I have to look at the casting again.
However looking through the whole thread I will rework quite a bit I think, move some logic to GameMode for example. I also started a video tutorial by Tom Looman, I feel like
I need to catch up on quite some things related to the basic setup.

Best regards, Peter