Coding problem using a combination of C++ and BP

Hello friends,

So, a bit of info to explain the problem I’m having - I’m making a turn-based combat prototype, and I have a few abilities that I want the units to use, so I’m using the gameplay ability system. So this is my base class for the abilities:

UCLASS()
class TURNBASEDLEARNING_API UAbilityBase : public UGameplayAbility
{
	GENERATED_BODY()
	
public:
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
		FVector TargetLocation;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
		FVector TargetDirection;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
		class UAbilitySystemComponent* TargetGASComponent;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
		AActor* TargetActor;
};

And I’m setting the variables in the unit code, and then grant the ability to the ability system component on the unit like this:

void ACharacterUnit::UseAbilityOnTarget(int32 AbilityIndex, AUnit* TargetUnit)
{
	if (AbilityIndex >= 0 && AbilityIndex < AbilitiesInBar.Num())
	{
		AbilitiesInBar[AbilityIndex].GetDefaultObject()->TargetLocation = TargetUnit->GetActorLocation();

		AbilitiesInBar[AbilityIndex].GetDefaultObject()->TargetGASComponent = TargetUnit->GetGASComponent();
		AbilitiesInBar[AbilityIndex].GetDefaultObject()->TargetActor = TargetUnit;

		if (AbilitiesInBar[AbilityIndex].GetDefaultObject()->TargetActor)
		{
			UE_LOG(LogTemp, Warning, TEXT("Actor: %s"),
				   *AbilitiesInBar[AbilityIndex].GetDefaultObject()->TargetActor->GetName());

			AbilityUsed = AbilitySystemComponent->GiveAbility(
				FGameplayAbilitySpec(AbilitiesInBar[AbilityIndex],
									 1,
									 static_cast<int32>(AbilitiesInBar[AbilityIndex].GetDefaultObject()->AbilityInputID),
									 this));

			AbilitySystemComponent->TryActivateAbility(AbilityUsed, false);
		}
	}
}

void ACharacterUnit::UseAbilityOnTarget(int32 AbilityIndex, ATile* TargetTile)
{
	if (AbilityIndex >= 0 && AbilityIndex < AbilitiesInBar.Num())
	{
		if (AbilitiesInBar[AbilityIndex].GetDefaultObject()->AbilityRangeType == EAbilityRangeType::Movement
			|| AbilitiesInBar[AbilityIndex].GetDefaultObject()->AbilityRangeType == EAbilityRangeType::MovementTeleport)
		{
			//UE_LOG(LogTemp, Warning, TEXT("Tile found on character: %d"), (Tile != nullptr));
			Tile->ClearTile();
			Tile = TargetTile;
			Tile->SetUnit(this);

			AbilitiesInBar[AbilityIndex].GetDefaultObject()->TargetLocation = TargetTile->GetActorLocation();
			FVector Direction = TargetTile->GetActorLocation() - GetActorLocation();
			Direction.Normalize();

			UE_LOG(LogTemp, Warning, TEXT("Direction: %s"), *TargetTile->GetActorLocation().ToString());

			if (!bIsPlayerCharacter)
			{
				Direction = -Direction;
			}

			AbilitiesInBar[AbilityIndex].GetDefaultObject()->TargetDirection = Direction;
		}
		
		AbilityUsed = AbilitySystemComponent->GiveAbility(
			FGameplayAbilitySpec(AbilitiesInBar[AbilityIndex],
								 1,
								 static_cast<int32>(AbilitiesInBar[AbilityIndex].GetDefaultObject()->AbilityInputID),
								 this));

		AbilitySystemComponent->TryActivateAbility(AbilityUsed, false);
	}
}

So all is fine for the movement ability, as the character moves correctly to the tile selected. For the attack ability on the other hand, I’m having an issue, where the ability blueprint reads nullptr’s from the values of both the TargetGASComponent and TargetActor, and so fails to to apply the damage gameplay effect. Below are screenshots for the blueprints of the movement ability, which works, the attack ability, which fails, and the debug inside Visual Studio, which shows that there are values set in the TargetGASComponent and TargetActor variables of the used ability:



Thanks in advance, and best regards,
Svetlin

So, I did a temporary fix by having the target actor variable be on the unit, and then the ability reads from that, but I’m still not sure what’s causing the above problem.

An additional piece of info - the AbilitiesInBar variable is a TArray that has TSubclassOf type elements, and UAbilityBase is my class that extends the UGameplayAbility class.

I’m not familiar with the GameplayAbility system, but I see GetDefaultObject() calls everywhere. This basically means that you are accessing the CDO (class default object) of the class (a template basically), and not the actual class instance. Is this intentional?

Thank you! Now I know what to look up to learn how to use next :slight_smile:

I was initially thinking I’m getting the instance I’ve assigned over in the blueprint through the dropdown, but from what I’ve read after reading your answer/question, I’m gonna have to learn quite a bit more :slight_smile:

So to get this straight - to use the object that I’ve set in the blueprint, I need to create an instance of the tsubclassof? Like with the NewObject function or spawn actor if I need to spawn it?

Thanks

Yes. TSubClassOf just gives you a class. When spawning an object, it asks which class you want to spawn. Instead of hardcoding a class in, you can give it a TSubClassOf variable, so that it spawns the class that is set inside the TSubClassOf variable. On a side note, this CDO thing is just a built -in optimization. I never had to modify the CDO before (except when I was working on an editor extension).

Yeah, thanks :slight_smile: Now I gotta debug a new issue :smiley: - for some reason I now get a crash after giving the ability system component the ability, with message:
“Assertion failed: Ability->HasAllFlags(RF_ClassDefaultObject)” at:
[File:D:/Build/++UE4/Sync/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/AbilitySystemComponent_Abilities.cpp] [Line: 711], which is this check:
“check(Ability->HasAllFlags(RF_ClassDefaultObject));”

And I do the instantiation of the variable like this:

UAbilityBase* Ability = NewObject<UAbilityBase>(this, DefaultAbility);
BarAbilities.Add(Ability);

So, I did a set flags for the newly created ability, and the crashing is gone.

AbilitiesInBar.Add(DefaultAbility);
UAbilityBase* Ability = NewObject<UAbilityBase>(this, DefaultAbility);
Ability->SetFlags(EObjectFlags::RF_ClassDefaultObject);
BarAbilities.Add(Ability);