Skip to content

CPP Project

Jared Taylor edited this page Mar 22, 2025 · 16 revisions

Important

It is expected you have completed Setup New Project and PushPawn Setup

C++ Setup

Build.cs

Add "PushPawn" to your PrivateDependencyModuleNames or PublicDependencyModuleNames.

Private is sufficient for the purpose of this tutorial, but if you want to extend your own abilities then you'll want public.

Implement Push Interfaces

Tip

This guide enables PushPawn for everything
If you want only AI character to push only Players, your Player Character implements IPusheeInstigator and your AI character implements IPusherTarget

class AMyCharacter : public ACharacter
	, public IPusheeInstigator  // PUSHPAWN - Look for Pushers to push us, and apply their push ability
	, public IPusherTarget	    // PUSHPAWN - Respond to Pushees that we push, and hand them our push ability

Ability Handling

Add a property containing the ability you want to push others with:

UPROPERTY(EditDefaultsOnly, Category=PushPawn)
TSubclassOf<UGameplayAbility> PushAbilityToGrant;

And a property containing the ability you want to scan for pushers with:

UPROPERTY(EditDefaultsOnly, Category=PushPawn)
TSubclassOf<UGameplayAbility> PushPawnScanAbility;

In the constructor, default to the C++ ability:

AMyCharacter::AMyCharacter()
{
	PushAbilityToGrant = UPushPawn_Action::StaticClass();
	PushPawnScanAbility = UPushPawn_Scan::StaticClass();
        // ...
}

Tip

This can later be changed to your derived ability

Give Scan Ability

Declare a handle we can use to reference the scan ability later:

FGameplayAbilitySpecHandle PushScanAbilityHandle;

Override:

virtual void PossessedBy(AController* NewController) override;
virtual void UnPossessed() override;
void AMyCharacter::PossessedBy(AController* NewController)
{
	Super::PossessedBy(NewController);

	if (HasAuthority() && NewController)
	{
		// Grant the PushPawn scan ability. NOTE: This probably isn't the best way to get the ASC
		if (UAbilitySystemComponent* ASC = GetComponentByClass<UAbilitySystemComponent>())
		{
			PushScanAbilityHandle = ASC->K2_GiveAbility(PushPawnScanAbility);
		}
	}
}

void AMyCharacter::UnPossessed()
{
	if (HasAuthority() && PushScanAbilityHandle.IsValid())
	{
		// Remove the PushPawn scan ability. NOTE: This probably isn't the best way to get the ASC
		if (UAbilitySystemComponent* ASC = GetComponentByClass<UAbilitySystemComponent>())
		{
			ASC->ClearAbility(PushScanAbilityHandle);
		}
	}
	Super::UnPossessed();
}

Warning

Do not try to activate the push ability, it will handle this itself

Interface Helper Functions

Example function for testing if we can be pushed, or push. Most projects will implement this in some manner:

bool IsDead() const { return false; }

If you want to be able to pause scanning at any time, add the delegate and helper function:

UFUNCTION(BlueprintCallable, Category=PushPawn)
void PausePushPawnScan(bool bPause)
{
	// Pause scanning for performance optimization
	// Relevant Wiki: https://github.com/Vaei/PushPawn/wiki/Performance#large-character-counts
	OnPushPawnScanPaused.ExecuteIfBound(bPause);
}
FOnPushPawnScanPaused OnPushPawnScanPaused;

Interface Implementation

Implement interfaces:

/* IPusheeInstigator Interface */
virtual bool IsPushable() const override { return !IsPendingKillPending() && !IsDead(); }
virtual bool CanBePushedBy(const AActor* PusherActor) const override { return /* !PusherCharacter->IsDead() */ true; }
virtual float GetPusheeStrengthScalar() const override { return 1.f; }
virtual bool GetPusheeStrengthOverride(float& Strength) const override { return false; }
virtual FVector GetPusheeAcceleration() const override;
virtual FVector GetPusheeVelocity() const override { return GetVelocity(); }
virtual bool IsPusheeMovingOnGround() const override;
virtual FCollisionShape GetPusheeCollisionShape(FQuat& ShapeRotation) const override;
virtual FOnPushPawnScanPaused* GetPushPawnScanPausedDelegate() override { return &OnPushPawnScanPaused; }
/* ~IPusheeInstigator Interface */

/* IPusherTarget Interface */
virtual void GatherPushOptions(const FPushQuery& PushQuery, FPushOptionBuilder& OptionBuilder) override;
virtual void CustomizePushEventData(const FGameplayTag& PushEventTag, FGameplayEventData& InOutEventData) override {}
virtual bool IsPushCapable() const override { return !IsPendingKillPending() && !IsDead(); }
virtual bool CanPushPawn(const AActor* PusheeActor) const override { return /* !PusheeCharacter->IsDead() */ true; }
virtual float GetPusherStrengthScalar() const override { return 1.f; }
virtual bool GetPusherStrengthOverride(float& Strength) const override { return false; }
/* ~IPusherTarget Interface */

Define remaining functions:

FVector AMyCharacter::GetPusheeAcceleration() const
{
	return GetCharacterMovement() ? GetCharacterMovement()->GetCurrentAcceleration() : FVector::ZeroVector;
}

bool AMyCharacter::IsPusheeMovingOnGround() const
{
	return GetCharacterMovement() ? GetCharacterMovement()->IsMovingOnGround() : false;
}

FCollisionShape AMyCharacter::GetPusheeCollisionShape(FQuat& ShapeRotation) const
{
	return UPushStatics::GetDefaultPusheeCollisionShape(this, ShapeRotation, EPushCollisionType::Capsule, GetRootComponent());
}

void AMyCharacter::GatherPushOptions(const FPushQuery& PushQuery, FPushOptionBuilder& OptionBuilder)
{
	UPushStatics::GatherPushOptions(PushAbilityToGrant, this, PushQuery, OptionBuilder);
}

Assign Abilities

We assigned native ability classes, but there are derived abilities that are fully setup for you.

Open your character blueprint and assign the included GA_PushPawn_Action instead, and then the GA_DemoPushPawn_Scan ability we created earlier.

UnrealEditor-Win64-DebugGame_2025-03-20_17-33-23

Caution

Do not accidentally assign the versions with _BP suffixes, they have complete functionality implementation in BP and do not perform well

Testing

You can Play-in-Editor and it should now work! Run into another Pawn and watch as you push each other away.

If not, carry on to the Troubleshooting section.

Clone this wiki locally