We have three classes:
AController
AAIController
APlayerController
where AController is the super class of the other two.
However, only AAIController provides an event OnPossess/OnUnpossess.
In my eyes this is a design flaw. as it should be for every Controller.
Not to mention that OnPosess/OnUnpossess is the only event we can implement in Blueprint.
[TABLE="class: highlight tab-size js-file-line-container"]
void AAIController::Possess(APawn* InPawn)
[TABLE="class: highlight tab-size js-file-line-container"]
{
[TABLE="class: highlight tab-size js-file-line-container"]
// don't even try possessing pending-kill pawns
[TABLE="class: highlight tab-size js-file-line-container"]
if (InPawn != nullptr && InPawn->IsPendingKill())
[TABLE="class: highlight tab-size js-file-line-container"]
{
[TABLE="class: highlight tab-size js-file-line-container"]
return;
[TABLE="class: highlight tab-size js-file-line-container"]
}
[TABLE="class: highlight tab-size js-file-line-container"]
[TABLE="class: highlight tab-size js-file-line-container"]
Super::Possess(InPawn);
[TABLE="class: highlight tab-size js-file-line-container"]
[TABLE="class: highlight tab-size js-file-line-container"]
if (GetPawn() == nullptr || InPawn == nullptr)
[TABLE="class: highlight tab-size js-file-line-container"]
{
[TABLE="class: highlight tab-size js-file-line-container"]
return;
[TABLE="class: highlight tab-size js-file-line-container"]
}
[TABLE="class: highlight tab-size js-file-line-container"]
[TABLE="class: highlight tab-size js-file-line-container"]
// no point in doing navigation setup if pawn has no movement component
[TABLE="class: highlight tab-size js-file-line-container"]
const UPawnMovementComponent* MovementComp = InPawn->GetMovementComponent();
[TABLE="class: highlight tab-size js-file-line-container"]
if (MovementComp != NULL)
[TABLE="class: highlight tab-size js-file-line-container"]
{
[TABLE="class: highlight tab-size js-file-line-container"]
UpdateNavigationComponents();
[TABLE="class: highlight tab-size js-file-line-container"]
}
[TABLE="class: highlight tab-size js-file-line-container"]
[TABLE="class: highlight tab-size js-file-line-container"]
if (PathFollowingComponent)
[TABLE="class: highlight tab-size js-file-line-container"]
{
[TABLE="class: highlight tab-size js-file-line-container"]
PathFollowingComponent->Initialize();
[TABLE="class: highlight tab-size js-file-line-container"]
}
[TABLE="class: highlight tab-size js-file-line-container"]
[TABLE="class: highlight tab-size js-file-line-container"]
if (bWantsPlayerState)
[TABLE="class: highlight tab-size js-file-line-container"]
{
[TABLE="class: highlight tab-size js-file-line-container"]
ChangeState(NAME_Playing);
[TABLE="class: highlight tab-size js-file-line-container"]
}
[TABLE="class: highlight tab-size js-file-line-container"]
[TABLE="class: highlight tab-size js-file-line-container"]
// a Pawn controlled by AI _requires_ a GameplayTasksComponent, so if Pawn
[TABLE="class: highlight tab-size js-file-line-container"]
// doesn't have one we need to create it
[TABLE="class: highlight tab-size js-file-line-container"]
if (CachedGameplayTasksComponent == nullptr)
[TABLE="class: highlight tab-size js-file-line-container"]
{
[TABLE="class: highlight tab-size js-file-line-container"]
UGameplayTasksComponent* GTComp = InPawn->FindComponentByClass<UGameplayTasksComponent>();
[TABLE="class: highlight tab-size js-file-line-container"]
if (GTComp == nullptr)
[TABLE="class: highlight tab-size js-file-line-container"]
{
[TABLE="class: highlight tab-size js-file-line-container"]
GTComp = NewObject<UGameplayTasksComponent>(InPawn, TEXT("GameplayTasksComponent"));
[TABLE="class: highlight tab-size js-file-line-container"]
GTComp->RegisterComponent();
[TABLE="class: highlight tab-size js-file-line-container"]
}
[TABLE="class: highlight tab-size js-file-line-container"]
CachedGameplayTasksComponent = GTComp;
[TABLE="class: highlight tab-size js-file-line-container"]
}
[TABLE="class: highlight tab-size js-file-line-container"]
[TABLE="class: highlight tab-size js-file-line-container"]
if (CachedGameplayTasksComponent && !CachedGameplayTasksComponent->OnClaimedResourcesChange.Contains(this, GET_FUNCTION_NAME_CHECKED(AAIController, OnGameplayTaskResourcesClaimed)))
[TABLE="class: highlight tab-size js-file-line-container"]
{
[TABLE="class: highlight tab-size js-file-line-container"]
CachedGameplayTasksComponent->OnClaimedResourcesChange.AddDynamic(this, &AAIController::OnGameplayTaskResourcesClaimed);
[TABLE="class: highlight tab-size js-file-line-container"]
[TABLE="class: highlight tab-size js-file-line-container"]
REDIRECT_OBJECT_TO_VLOG(CachedGameplayTasksComponent, this);
[TABLE="class: highlight tab-size js-file-line-container"]
}
[TABLE="class: highlight tab-size js-file-line-container"]
[TABLE="class: highlight tab-size js-file-line-container"]
OnPossess(InPawn);
}
[TABLE="class: highlight tab-size js-file-line-container"]
void APlayerController::Possess(APawn* PawnToPossess)
[TABLE="class: highlight tab-size js-file-line-container"]
{
[TABLE="class: highlight tab-size js-file-line-container"]
if (!HasAuthority())
[TABLE="class: highlight tab-size js-file-line-container"]
{
[TABLE="class: highlight tab-size js-file-line-container"]
FMessageLog("PIE").Warning(FText::Format(
[TABLE="class: highlight tab-size js-file-line-container"]
LOCTEXT("PlayerControllerPossessAuthorityOnly", "Possess function should only be used by the network authority for {0}"),
[TABLE="class: highlight tab-size js-file-line-container"]
FText::FromName(GetFName())
[TABLE="class: highlight tab-size js-file-line-container"]
));
[TABLE="class: highlight tab-size js-file-line-container"]
UE_LOG(LogPlayerController, Warning, TEXT("Trying to possess %s without network authority! Request will be ignored."), *GetNameSafe(PawnToPossess));
[TABLE="class: highlight tab-size js-file-line-container"]
return;
[TABLE="class: highlight tab-size js-file-line-container"]
}
[TABLE="class: highlight tab-size js-file-line-container"]
[TABLE="class: highlight tab-size js-file-line-container"]
if ( PawnToPossess != NULL &&
[TABLE="class: highlight tab-size js-file-line-container"]
(PlayerState == NULL || !PlayerState->bOnlySpectator) )
[TABLE="class: highlight tab-size js-file-line-container"]
{
[TABLE="class: highlight tab-size js-file-line-container"]
if (GetPawn() && GetPawn() != PawnToPossess)
[TABLE="class: highlight tab-size js-file-line-container"]
{
[TABLE="class: highlight tab-size js-file-line-container"]
UnPossess();
[TABLE="class: highlight tab-size js-file-line-container"]
}
[TABLE="class: highlight tab-size js-file-line-container"]
[TABLE="class: highlight tab-size js-file-line-container"]
if (PawnToPossess->Controller != NULL)
[TABLE="class: highlight tab-size js-file-line-container"]
{
[TABLE="class: highlight tab-size js-file-line-container"]
PawnToPossess->Controller->UnPossess();
[TABLE="class: highlight tab-size js-file-line-container"]
}
[TABLE="class: highlight tab-size js-file-line-container"]
[TABLE="class: highlight tab-size js-file-line-container"]
PawnToPossess->PossessedBy(this);
[TABLE="class: highlight tab-size js-file-line-container"]
[TABLE="class: highlight tab-size js-file-line-container"]
// update rotation to match possessed pawn's rotation
[TABLE="class: highlight tab-size js-file-line-container"]
SetControlRotation( PawnToPossess->GetActorRotation() );
[TABLE="class: highlight tab-size js-file-line-container"]
[TABLE="class: highlight tab-size js-file-line-container"]
SetPawn(PawnToPossess);
[TABLE="class: highlight tab-size js-file-line-container"]
check(GetPawn() != NULL);
[TABLE="class: highlight tab-size js-file-line-container"]
[TABLE="class: highlight tab-size js-file-line-container"]
if (GetPawn() && GetPawn()->PrimaryActorTick.bStartWithTickEnabled)
[TABLE="class: highlight tab-size js-file-line-container"]
{
[TABLE="class: highlight tab-size js-file-line-container"]
GetPawn()->SetActorTickEnabled(true);
[TABLE="class: highlight tab-size js-file-line-container"]
}
[TABLE="class: highlight tab-size js-file-line-container"]
[TABLE="class: highlight tab-size js-file-line-container"]
INetworkPredictionInterface* NetworkPredictionInterface = GetPawn() ? Cast<INetworkPredictionInterface>(GetPawn()->GetMovementComponent()) : NULL;
[TABLE="class: highlight tab-size js-file-line-container"]
if (NetworkPredictionInterface)
[TABLE="class: highlight tab-size js-file-line-container"]
{
[TABLE="class: highlight tab-size js-file-line-container"]
NetworkPredictionInterface->ResetPredictionData_Server();
[TABLE="class: highlight tab-size js-file-line-container"]
}
[TABLE="class: highlight tab-size js-file-line-container"]
[TABLE="class: highlight tab-size js-file-line-container"]
AcknowledgedPawn = NULL;
[TABLE="class: highlight tab-size js-file-line-container"]
[TABLE="class: highlight tab-size js-file-line-container"]
// Local PCs will have the Restart() triggered right away in ClientRestart (via PawnClientRestart()), but the server should call Restart() locally for remote PCs.
[TABLE="class: highlight tab-size js-file-line-container"]
// We're really just trying to avoid calling Restart() multiple times.
[TABLE="class: highlight tab-size js-file-line-container"]
if (!IsLocalPlayerController())
[TABLE="class: highlight tab-size js-file-line-container"]
{
[TABLE="class: highlight tab-size js-file-line-container"]
GetPawn()->Restart();
[TABLE="class: highlight tab-size js-file-line-container"]
}
[TABLE="class: highlight tab-size js-file-line-container"]
[TABLE="class: highlight tab-size js-file-line-container"]
ClientRestart(GetPawn());
[TABLE="class: highlight tab-size js-file-line-container"]
[TABLE="class: highlight tab-size js-file-line-container"]
ChangeState( NAME_Playing );
[TABLE="class: highlight tab-size js-file-line-container"]
if (bAutoManageActiveCameraTarget)
[TABLE="class: highlight tab-size js-file-line-container"]
{
[TABLE="class: highlight tab-size js-file-line-container"]
AutoManageActiveCameraTarget(GetPawn());
[TABLE="class: highlight tab-size js-file-line-container"]
ResetCameraMode();
[TABLE="class: highlight tab-size js-file-line-container"]
}
[TABLE="class: highlight tab-size js-file-line-container"]
UpdateNavigationComponents();
[TABLE="class: highlight tab-size js-file-line-container"]
}
}
[TABLE="class: highlight tab-size js-file-line-container"]
void AController::Possess(APawn* InPawn)
[TABLE="class: highlight tab-size js-file-line-container"]
{
[TABLE="class: highlight tab-size js-file-line-container"]
if (!HasAuthority())
[TABLE="class: highlight tab-size js-file-line-container"]
{
[TABLE="class: highlight tab-size js-file-line-container"]
FMessageLog("PIE").Warning(FText::Format(
[TABLE="class: highlight tab-size js-file-line-container"]
LOCTEXT("ControllerPossessAuthorityOnly", "Possess function should only be used by the network authority for {0}"),
[TABLE="class: highlight tab-size js-file-line-container"]
FText::FromName(GetFName())
[TABLE="class: highlight tab-size js-file-line-container"]
));
[TABLE="class: highlight tab-size js-file-line-container"]
return;
[TABLE="class: highlight tab-size js-file-line-container"]
}
[TABLE="class: highlight tab-size js-file-line-container"]
[TABLE="class: highlight tab-size js-file-line-container"]
REDIRECT_OBJECT_TO_VLOG(InPawn, this);
[TABLE="class: highlight tab-size js-file-line-container"]
[TABLE="class: highlight tab-size js-file-line-container"]
if (InPawn != NULL)
[TABLE="class: highlight tab-size js-file-line-container"]
{
[TABLE="class: highlight tab-size js-file-line-container"]
if (GetPawn() && GetPawn() != InPawn)
[TABLE="class: highlight tab-size js-file-line-container"]
{
[TABLE="class: highlight tab-size js-file-line-container"]
UnPossess();
[TABLE="class: highlight tab-size js-file-line-container"]
}
[TABLE="class: highlight tab-size js-file-line-container"]
[TABLE="class: highlight tab-size js-file-line-container"]
if (InPawn->Controller != NULL)
[TABLE="class: highlight tab-size js-file-line-container"]
{
[TABLE="class: highlight tab-size js-file-line-container"]
InPawn->Controller->UnPossess();
[TABLE="class: highlight tab-size js-file-line-container"]
}
[TABLE="class: highlight tab-size js-file-line-container"]
[TABLE="class: highlight tab-size js-file-line-container"]
InPawn->PossessedBy(this);
[TABLE="class: highlight tab-size js-file-line-container"]
SetPawn(InPawn);
[TABLE="class: highlight tab-size js-file-line-container"]
[TABLE="class: highlight tab-size js-file-line-container"]
// update rotation to match possessed pawn's rotation
[TABLE="class: highlight tab-size js-file-line-container"]
SetControlRotation( Pawn->GetActorRotation() );
[TABLE="class: highlight tab-size js-file-line-container"]
[TABLE="class: highlight tab-size js-file-line-container"]
Pawn->Restart();
[TABLE="class: highlight tab-size js-file-line-container"]
}
}
Possess() may be virtual, but it isn’t BlueprintNative or BlueprintImplementable.
(I could create a Base C++ Class for my own Controller, but I use BP as a prototype and I would like to have all features I would have in the future, too)