SimpleShooter Lesson 210 -- nullptr passed

Working in the “KillEmAllGameMode” on the Win Condition.

When I was (incorrectly) using the methodology from ToonTanks to check for nullptr and return from the function if true:

if (!Pointer) { return; }

My Win Condition wouldn’t show up when killing one or more AI. I debugged this by adding a Log before the return statement and discovering that a nullptr is being passed into the function for each AI Controller.

It seems that, even though we don’t like nullptrs due to their undefined behavior, we’re intentionally using one in this case to tell us that we’ve won the game:

void AKillEmAllGameMode::PawnKilled(APawn* PawnKilled)
    {
        Super::PawnKilled(PawnKilled);

        APlayerController* PlayerController = Cast<APlayerController>(PawnKilled->GetController());
        if (PlayerController)
        {
            AvengersEndgame(false);
        }
        
        for (AShooterAIController* AIController : TActorRange<AShooterAIController>(GetWorld()))
        {
            if (!AIController->IsDead()) { return; }   
        }

        AvengersEndgame(true);
}

What I’m reading here is that PawnKilled is the APawn pointer instance (as passed by our ShooterCharacter class) that “died” (Health == 0). In my code, if the Player’s Pawn is passed and we try to get the controller, it returns a valid pointer (i.e. the player’s controller isn’t getting removed from the game). However, when the AI Pawn is passed and we try to get the controller, it returns a nullptr. I’m assuming that this is because Unreal is garbage collecting (if misusing the term, please excuse) and removing the AI Controller before we can “get” it.

This obviously doesn’t affect the function of the… function… though, as we never need to use the Pawn’s Controller when it is a nullptr.

If we were coding a similar function or a game for an employer and being managed by Senior Devs, would they allow this? Or would it be considered bad practice? This course has done such a great job of drilling into me “NO NULLPTR!” that, when I finally figured out what my bug was, I was surprised that we were actually using nullptrs for functionality.

Thanks for everything GameDevTV – have learned a lot!

Specifically it’s dereferencing them* that results in UB.

If you’re talking about this line

APlayerController* PlayerController = Cast<APlayerController>(PawnKilled->GetController());

Then that is incorrect, it returns nullptr because when an AI pawn is passed in its controller is not a player controller. And that’s the expected behaviour.


* I actually found out recently that’s not actually 100% accurate, it’s specifically the conversion to a rvalue.

int* value = nullptr;
*value;

This snippet doesn’t actually trigger UB. As the value is discarded it never undergoes lvalue-to-rvalue conversion.
Compiled and ran with -fsanitize=undefined and an extra example
https://godbolt.org/z/f4nfor

1 Like

Thank you for the breakdown Dan.

Sounds like I need to dive further into casting. To clarify, the cast is doing the check whether or not the controller that’s passed is APlayerController or not, correct?

Yes although that’s kind of a side-effect of the cast. Cast is a dynamic cast which is used to treat the type of the pointer to a pointer of a different type. If the object isn’t actually of that type then the function will return nullptr.

Example

APawn* Pawn = Cast<APawn>(GetOwner());

If the owner isn’t in fact an APawn then this cast will fail and return nullptr.