Skip to main content

Adding New Attribute

Attribute basically is a float value for something like Level, Health, Mana, Stamina, Damage, Strength, etc that used in Attribute Set and could be automatically replicated in every client.
Attribute Set is what we've just created in the Installation step, it is the base structure with initial value that character or actor has, which you can also modified through some GameplayEffect like when the character got burns and they got some damage over time, and so on.

If you want to know more about what is Attribute and Attribute Set, you can find it here.

Add a New Attribute

Note: This must be done in C++

  • Find AGASAttributeSet in Content Browser, you can search it at the root C++ Classes directory or find it at AgateGAS/Public/Attribute. Open it.
  • From the header file, you should see there are already some attribute there, like Level, Health, Mana, Armor, and Damage.

    image.png

  • You can just only copy paste to create a new Attribute, let's say we create a MoveSpeed Attribute.
    #pragma region MoveSpeed
    	UPROPERTY(BlueprintReadOnly, Category = "AgateGAS|Attribute", ReplicatedUsing = OnRep_MoveSpeed)
    	FGameplayAttributeData MoveSpeed;
    	ATTRIBUTE_ACCESSORS(UAGASAttributeSet, MoveSpeed)
    
    	UFUNCTION()
    	virtual void OnRep_MoveSpeed(const FGameplayAttributeData& OldMoveSpeed);
    #pragma endregion

    MoveSpeed doesn't required like MaxMoveSpeed (usually we just need to clamp the max move speed with a constant value like I'd showed later), because it just to be set and no recovery behaviour like Health and Mana, so we don't create its Max Attribute.

  • And then go to the .cpp file, modify the GetLifetimeReplicatedProps function, just add your new attribute at the bottom like the others, to be able to replicated.
    void UAGASAttributeSet::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
    {
    	Super::GetLifetimeReplicatedProps(OutLifetimeProps);
    
    	DOREPLIFETIME_CONDITION_NOTIFY(UAGASAttributeSet, Level, COND_None, REPNOTIFY_Always);
    
    	DOREPLIFETIME_CONDITION_NOTIFY(UAGASAttributeSet, Health, COND_None, REPNOTIFY_Always);
    	DOREPLIFETIME_CONDITION_NOTIFY(UAGASAttributeSet, MaxHealth, COND_None, REPNOTIFY_Always);
    
    	DOREPLIFETIME_CONDITION_NOTIFY(UAGASAttributeSet, Mana, COND_None, REPNOTIFY_Always);
    	DOREPLIFETIME_CONDITION_NOTIFY(UAGASAttributeSet, MaxMana, COND_None, REPNOTIFY_Always);
    
    	DOREPLIFETIME_CONDITION_NOTIFY(UAGASAttributeSet, MoveSpeed, COND_None, REPNOTIFY_Always);
    }
  • And move to the bottom script, you will find all the implementation of replicated attribute like Level, Health, and Mana. Because we need to also replicate the MoveSpeed, then we should also created it.
    #pragma region MoveSpeed
    void UAGASAttributeSet::OnRep_MoveSpeed(const FGameplayAttributeData& OldMoveSpeed)
    {
    	GAMEPLAYATTRIBUTE_REPNOTIFY(UAGASAttributeSet, MoveSpeed, OldMoveSpeed);
    }
    #pragma endregion
  • Like I've mentioned before, because we need to clamp the Move Speed value, we should modify the PreAttributeChange implementation.
    void UAGASAttributeSet::PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue)
    {
    	Super::PreAttributeChange(Attribute, NewValue);
    
    	if (Attribute == GetMaxHealthAttribute())
    	{
    		KeepAttributeAsPercentageForMaxChange(Health, MaxHealth, NewValue, GetHealthAttribute());
    	}
    	else if (Attribute == GetMaxManaAttribute())
    	{
    		KeepAttributeAsPercentageForMaxChange(Mana, MaxMana, NewValue, GetManaAttribute());
    	}
      
      	// Just focused after this line, above lines are already in the script
    	else if (Attribute == GetMoveSpeedAttribute())
    	{
    		// Cannot slower than 150 units/s and faster than 1000 units/s
    		NewValue = FMath::Clamp<float>(NewValue, 150, 1000);
    	}
    }

    So, we clamp the MoveSpeed value so it can't less than 150 and more than 1000 every time we change it, before it got processed.

  • Just build the script then (no need to rebuild).

    image.png

    Voila, you can add a new MoveSpeed attribute now.
  • But if you've noticed, we don't see any log in the Output Log about that MoveSpeed. It is because we've never listened any changes from MoveSpeed.
    So, the log what you've seen before, it is from the AGASPlayerState script. If you open that script, you should see the cpp and find SetAbiltySystemComponent implementation.
    From there you can see some callback assignment for Health and Mana.

    image.png

     You should do something like that too for MoveSpeed to be notified in Output Log.
  • Okay let's move to AGASPlayerState header, and add this line below MaxManaChanged.
    FDelegateHandle OnMoveSpeedChanged;
    virtual void MoveSpeedChanged(const FOnAttributeChangeData& Data);
  • And create the implementation in cpp:
    void AAGASPlayerState::MoveSpeedChanged(const FOnAttributeChangeData& Data)
    {
    	UE_LOG(LogTemp, Display, TEXT("Move Speed changed from %.0f to %.0f"), Data.OldValue, Data.NewValue);
    }
  • And don't forget to also assign the callback from the attribute changes delegate with what we've just created in SetAbilitySystemComponent function.
    void AAGASPlayerState::SetAbilitySystemComponent(UAGASAbilitySystemComponent* InAbilitySystemComponent)
    {
    	...
    	OnMoveSpeedChanged = AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(AttributeSet->GetMoveSpeedAttribute())
    		.AddUObject(this, &AAGASPlayerState::MoveSpeedChanged);
      	...
    }
  • Build solution again, and Play from Unreal editor.

    image.png

    You should see the log now, it changes from 0 to 150, because we just set the value to 0, but we clamped the value from 150 to 1000.