Sleepless nights - why score won't update

Hi UE4-ers, I’m totally stumped as to why my score won’t update. I’ve created an Actor class which includes code to detect when a character collides with it. When collision takes place, a score should decrement from the previous score. I’m then sending the score to the AMyHUD class, where a function is called to display the current score. I initialise the score to 10, but on each hit of the Actor, the score remains at 9. I’ve blueprinted the Actor class and placed about 5 blueprint instances into the world. What the **** is going on?

MyActor.h


UCLASS()
class SIDESCROLLER1_API AMyActor : public AActor
{
	GENERATED_BODY()
protected:
	**int32 nLivesRemaining;**
public:	
	// Sets default values for this actor's properties
	AMyActor(const FObjectInitializer &objectInit);

	// Called when the game starts or when spawned
	virtual void BeginPlay() override;
	
	// Called every frame
	virtual void Tick( float DeltaSeconds ) override;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = collider)USphereComponent *pSphere;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = collider)UStaticMeshComponent *pMesh;
	
	UFUNCTION(BlueprintNativeEvent, Category = collider)
		void OnHit(AActor *OtherActor, UPrimitiveComponent *OtherComponent, int32 OtherBodyIndex, bool bFromSweep, const FHitResult &SweepResult);
	
	
};

Here’s my MyActor.cpp


AMyActor::AMyActor(const FObjectInitializer &objectInit):Super(objectInit)
{
 	// 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;
	pSphere = objectInit.CreateDefaultSubobject<USphereComponent>(this, TEXT("sphere"));
	RootComponent = pSphere;
	pSphere->bGenerateOverlapEvents = true;

	pSphere->OnComponentBeginOverlap.AddDynamic(this, &AMyActor::OnHit);

	pSphere->SetSphereRadius(80);

	pMesh = objectInit.CreateAbstractDefaultSubobject<UStaticMeshComponent>(this, TEXT("Mesh"));
	pMesh->AttachTo(pSphere);
	**nLivesRemaining = 10;**
	
}

MyHUD.h



UCLASS()
class SIDESCROLLER1_API AMyHUD : public AHUD
{
	GENERATED_BODY()
public:
	AMyHUD();
	****[COLOR="#FF0000"]void PrintScores(int32 lives);**[/COLOR]**
};


Here is the implementation of the OnHit Function in MyActor.cpp showing the decrement of nLivesRemaining, and a call to the function to display in MyHUD.cpp



void AMyActor::OnHit_Implementation(AActor *OtherActor, UPrimitiveComponent *OtherComponent, int32 OtherBodyIndex, bool bFromSweep, const FHitResult &SweepResult)
{
	if (Cast<AsideScroller1Character>(OtherActor) == nullptr)
	{
		return;
	}
	this->Destroy();
	**this->nLivesRemaining-=1;**
	APlayerController *pPlayerController = GetWorld()->GetFirstPlayerController();
	if (pPlayerController) {
		AMyHUD *hud = Cast<AMyHUD>(pPlayerController->GetHUD());
		**hud->PrintScores(nLivesRemaining);**
	}
}


The Implementation of PrintScores in MyHUD.cpp



void AMyHUD::PrintScores(**int32 lives**)
{
	FString Message;
	if (lives < 0) {
		Message = "You have No more lives remaining";
		GEngine->AddOnScreenDebugMessage(0, 5.f, FColor::Cyan, FString(Message));
	}
	else
	{
		GEngine->AddOnScreenDebugMessage(0, 5.f, FColor::Cyan, FString::Printf(TEXT("You have %d lives remaining"), **lives**));
	}
}


It all works fine (decrements on each hit) if there is only one instance of the blueprinted class. However when I place say 5 into the world the score just remains at 9.
any help is very much appreciated. Thanks

nLivesRemaining should be in your hud.

The way it looks, you have 5 actors, each have nLivesRemaining at 10.

When an actor collides, 1 is subtracted from it’s own lives of 10, passes 9 to the hud, and then is destroyed.
When another actor collides, 1 is subtracted from it’s own lives of 10, passes 9 to the hud, and then is destroyed.

Put the lives variable in 1 place (perhaps the hud) so there’s only 1 version of it.

Hi Juice-Tin, and thank you for your reply. I have the nLivesRemaining variable declared in the MyActor.h file as protected in scope. Are you saying that I should remove it and declare it in the AMyHUD.h instead? I’m not sure how I’d implement changes in the OnHit function to update the variable declared in the HUD.

Exactly. And you would then decrease it for example via hud->nLivesRemaining -= 1;

I would go a little further though and implement OnHit in your characterclass and give your character the nRemainingLives-variable. Seems a little more straightforward to me :slight_smile:

Thanks HammelGammel - It crashes - terribly. I created three functions in the HUD class, and call these functions from within the AMyActor class after getting a reference to the AMyHUD class. See code below:

The AMyActor.cpp Class



void AMyActor::OnHit_Implementation(AActor *OtherActor, UPrimitiveComponent *OtherComponent, int32 OtherBodyIndex, bool bFromSweep, const FHitResult &SweepResult)
{
	if (Cast<AsideScroller1Character>(OtherActor) == nullptr)
	{
		return;
	}
	this->Destroy();
	
	APlayerController *pPlayerController = GetWorld()->GetFirstPlayerController();
	if (pPlayerController) {
		AMyHUD *hud = Cast<AMyHUD>(pPlayerController->GetHUD());
	
		hud->DecrementScore();
		hud->PrintScores();
	}
}


The AMyHUD.cpp class - the PrintScores function hasn’t changed from the original. I’ve set the initial value of nLivesRemaining = 10 in the AMyHUD constructor function:




void AMyHUD::DecrementScore() {
	nLivesRemaining -= 1;
}




AMyHUD::AMyHUD()
{
	nLivesRemaining = 10;
}

void AMyHUD::DecrementScore() {
	nLivesRemaining -= 1;
}

void AMyHUD::PrintScores()
{
	FString Message;
	if (nLivesRemaining < 0) {
		Message = "You have No more lives remaining";
		GEngine->AddOnScreenDebugMessage(0, 5.f, FColor::Cyan, FString(Message));
	}
	else
	{
		GEngine->AddOnScreenDebugMessage(0, 5.f, FColor::Cyan, FString::Printf(TEXT("You have %d lives remaining"), nLivesRemaining));
	}
}


Here it is crashing…

Your pointer to AMyHUD is invalid. Do you check its not null before using it? No you do not! :slight_smile:

Seriously, NEVER use a pointer without that check.

I’d not put the nLivesRemaining on the character btw… because that is going to be destroyed. The best place to put this is in the game mode class. Hook the actor destroyed event and if any of your important objects get destroyed decrement the nLivesRemaining count and respawn the actor there.

Game mode is basically the place where you’re game logic is meant to be. So its ideal for this kind of thing.

Good call, zoombapup - I can’t believe that I missed the AMyHUD pointer check. Okay, so I’ll connect up the collision to the GameMode class. I got the same problem when I declared a pointer to the GameMode - CRASH…

I’m just thinking about how I would go about doing this. Do I do something like this? :



AsideScroller1GameMode::AsideScroller1GameMode()
{
	//...
        livesLeft = 10;
}


Then in the Tick function write



void AsideScroller1GameMode::Tick(float DeltaSeconds)
{
    AMyActor *pMyActor = Cast<AMyActor>(UGameplayStatics::GetPlayerPawn(this, 0));
    if(pMyActor)
    {
        pMyActor->DecrementLivesRemaining(livesLeft);
    }
}