Checking if object is null doesn't seem to work - C++ Course L152


#include "PawnTurret.h"
#include "PawnBase.h"
#include "GameFramework/Actor.h"
#include "GameFramework/PlayerController.h"
#include "Kismet/GameplayStatics.h"

void APawnTurret::BeginPlay()
{
    // Super::BeginPlay();

    PlayerPawn = Cast<APawnTank>(UGameplayStatics::GetPlayerPawn(this, 0)); // casting automatically creates reference...
    GetWorld()->GetTimerManager().SetTimer(FireRateTimerHandle, this, &APawnTurret::CheckFireCondition, FireRate, true);
}

void APawnTurret::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);
    // UE_LOG(LogTemp, Warning, TEXT("In APawnTurret::Tick(%d)"), DeltaTime);
}

void APawnTurret::HandleDestruction()
{
    UE_LOG(LogTemp, Warning, TEXT("in APawnTurret::HandleDestruction()"));
    Destroy();
}

void APawnTurret::CheckFireCondition()
{
    UE_LOG(LogTemp, Warning, TEXT("In CheckFireCondition()"));

    APlayerController * PlayerControllerRef = GetWorld()->GetFirstPlayerController();

    if (!PlayerPawn)  // this checks if the player at index 0, which is THE player, is null (dead: removed from game) or not                             
    {
        UE_LOG(LogTemp, Warning, TEXT("There is NO PlayerPawn"));
        return;
    } 
    else
    {
        UE_LOG(LogTemp, Warning, TEXT("There IS a PlayerPawn"));
    }

    if (!PlayerControllerRef)
    {
        UE_LOG(LogTemp, Warning, TEXT("There is NO PlayerController"));
        return;
    }
    else
    {
        UE_LOG(LogTemp, Warning, TEXT("There IS a PlayerController"));
    }

    if (FVector::Dist(PlayerPawn->GetActorLocation(), GetActorLocation()) > FireRange)  // player is out of range
    {
        return;
    }              
 FVector PlayerLocation = PlayerControllerRef->GetPawn()->GetActorLocation();    // this is line 59
    RotateTurret(PlayerLocation);
    Fire();
}



/////////////////////////////////////////////////////////////////


void AProjectileBase::OnHit(UPrimitiveComponent* HitComp, AActor * OtherActor, UPrimitiveComponent * OtherComp, FVector NormalImpulse, const FHitResult& Hit)
										
{
	UE_LOG(LogTemp, Warning, TEXT("in ProjectileBase::OnHit()"));
	AActor * MyOwner = GetOwner();
	if (!MyOwner) return;
	if (OtherActor && OtherActor != this && OtherActor != MyOwner)
	{
		UGameplayStatics::ApplyDamage(OtherActor, Damage, MyOwner->GetInstigatorController(), this, DamageType);
		
		Destroy();  // get rid of the projectile once it's hit
	}
}

////////////////////////////////////////////////////////////////////////////////

void AToonTanksGameMode::ActorDied(AActor * DeadActor)
{
    UE_LOG(LogTemp, Warning, TEXT("A Pawn died"));
    if (DeadActor == PlayerTank)
    {

        PlayerTank->HandleDestruction(); 
        HandleGameOver(false);
    }
    else if (APawnTurret * DestroyedTurret = Cast<APawnTurret>(DeadActor))  
   
    {
        DestroyedTurret->HandleDestruction();
        if (--TargetTurrets == 0)
        {
            HandleGameOver(true);
        }
    }
}

Assume that everything else about the code is exactly as done in the videos up this point. HandleDestruction only prints something, and calls Destroy(). HandleGameOver(false) only prints something, and runs GameOver(false). So, the game continues to Tick after the Game Over message etc.

When the player dies, Unreal crashes with :

[2021.08.22-22.41.36:214][321]LogTemp: Warning: In CheckFireCondition()
[2021.08.22-22.41.36:214][321]LogTemp: Warning: There IS a PlayerPawn
[2021.08.22-22.41.36:214][321]LogTemp: Warning: There IS a PlayerController
[2021.08.22-22.41.36:215][321]LogTemp: Warning: in ProjectileBase::BeginPlay()
[2021.08.22-22.41.36:548][341]LogTemp: Warning: in ProjectileBase::OnHit()
[2021.08.22-22.41.36:717][351]LogTemp: Warning: In CheckFireCondition()
[2021.08.22-22.41.36:717][351]LogTemp: Warning: There IS a PlayerPawn
[2021.08.22-22.41.36:717][351]LogTemp: Warning: There IS a PlayerController
[2021.08.22-22.41.36:718][351]LogTemp: Warning: in ProjectileBase::BeginPlay()
[2021.08.22-22.41.37:048][371]LogTemp: Warning: in ProjectileBase::OnHit()
[2021.08.22-22.41.37:048][371]LogTemp: Warning: A Pawn died
[2021.08.22-22.41.37:048][371]LogTemp: Warning: in PawnTank::HandleDestruction()
[2021.08.22-22.41.37:048][371]LogTemp: Error: In HandleGameOver(false)
[2021.08.22-22.41.37:048][371]LogBlueprintUserMessages: [BP_ToonTanksGameMode_C_0] in Event Game OVER
[2021.08.22-22.41.37:215][381]LogTemp: Warning: In CheckFireCondition()
[2021.08.22-22.41.37:215][381]LogTemp: Warning: There IS a PlayerPawn
[2021.08.22-22.41.37:215][381]LogTemp: Warning: There IS a PlayerController
[2021.08.22-22.41.39:516][381]LogWindows: Error: === Critical error: ===
[2021.08.22-22.41.39:516][381]LogWindows: Error:
Unhandled Exception: EXCEPTION_ACCESS_VIOLATION reading address 0x0000000000000148
[2021.08.22-21.44.02:530][ 37]LogWindows: Error:
[2021.08.22-21.44.02:530][ 37]LogWindows: Error: [Callstack] 0x00007fffee345ca9 UE4Editor-ToonTanks-7439.dll!APawnTurret::CheckFireCondition() [C:\Users\Paul\Documents\Unreal Projects\ToonTanks\Source\ToonTanks\PawnTurret.cpp:59]

(this is the only relevant log information I decided to show, cuz idk if there’s a way to separately attach the log such that the length of the post doesn’t spill over the 64000 or w/e)

… which I’m assuming is a null pointer? It always happens at the line in APawnTurret::CheckFireCondition where the FVector PlayerLocation is initialized, which here is line 59. This line both calls upon the PlayerControllerRef and calls GetPawn() on it, after passing over those conditionals. This means that, even after the projectile hits the tank and damages it enough
to kill it and Destroy() is called on the PlayerPawn, both of those conditionals pass: !PlayerPawn is false and PlayerPawn is true meaning PlayerPawn has a non-null value, and !PlayerController is false and PlayerController is true meaning PlayerController is also non-null.

So… what exactly does Destroy() do? Because apparently it does not set the destroyed pawn to null. I could understand that there would still be a PlayerController floating around somewhere, but I thought that after running Destroy() on the PlayerPawn, it would become null?

Followup question would be, if Destroy() does not in fact set the destroyed Pawn equal to null, is there supposed to be some boolean bIsDead to check if the PlayerPawn is dead? (that you would set to True in the HandleDestruction() method)? And if so, why couldn’t it just be in the Github commit for the lecture, so that I don’t have to go back through the video/s to see exactly what he writes in his code? (I mean, I could go ahead and implement a bIsDead boolean myself, but I just wanna be sure that my solutions are as elegant and close to what’s in the lectures as possible, cuz I’m new to this…plus a lot of the times I don’t find what he says to be as helpful in understanding what’s going on, as just reading the correct code and formulating my own understanding of it… what I’m saying is basically having all the correct code on the Github would besuper helpful for beginners).

At a guess, line 59, GetPawn is returning null too.
It’s bad practice to chain access to calls unless you can be certain all prior calls will succeed. In this case, GetPawn is probably being called after the player is destroyed and so it fails.

A better solution would be to do something like the following:

APawn* Pawn = PlayerControllerRef->GetPawn()
if ( Pawn != nullptr )
{
    RotateTurret(Pawn->GetActorLocation());
    Fire();
}

Checking PlayerPawn and then calling GetPawn isn’t enough since PlayerPawn could still have a reference to a memory location but GetPawn could return null. In fact, you could even remove these lines as a result.

    if (!PlayerPawn)  // this checks if the player at index 0, which is THE player, is null (dead: removed from game) or not                             
    {
        UE_LOG(LogTemp, Warning, TEXT("There is NO PlayerPawn"));
        return;
    } 
    else
    {
        UE_LOG(LogTemp, Warning, TEXT("There IS a PlayerPawn"));
    }

I hope this helps

1 Like

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