/** * Called just before any modification happens to an attribute. This is lower level than PreAttributeModify/PostAttribute modify. * There is no additional context provided here since anything can trigger this. Executed effects, duration based effects, effects being removed, immunity being applied, stacking rules changing, etc. * This function is meant to enforce things like "Health = Clamp(Health, 0, MaxHealth)" and NOT things like "trigger this extra thing if damage is applied, etc". * * NewValue is a mutable reference so you are able to clamp the newly applied value as well. */ virtualvoidPreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue){ }
/** * Called just before a GameplayEffect is executed to modify the base value of an attribute. No more changes can be made. * Note this is only called during an 'execute'. E.g., a modification to the 'base value' of an attribute. It is not called during an application of a GameplayEffect, such as a 5s second +10 movement speed buff. */ virtualvoidPostGameplayEffectExecute(conststruct FGameplayEffectModCallbackData &Data){ }
UCLASS() classGASDOCUMENTATION_API UGDGameplayAbility : public UGameplayAbility { GENERATED_BODY() public: UGDGameplayAbility();
// Abilities with this set will automatically activate when the input is pressed UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Ability") EGDAbilityInputID AbilityInputID = EGDAbilityInputID::None;
// Value to associate an ability with an slot without tying it to an automatically activated input. // Passive abilities won't be tied to an input so we need a way to generically associate abilities with slots. UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Ability") EGDAbilityInputID AbilityID = EGDAbilityInputID::None;
// Tells an ability to activate immediately when its granted. // Used for passive abilities and abilities forced on others. UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Ability") bool ActivateAbilityOnGranted = false;
// If an ability is marked as 'ActivateAbilityOnGranted', activate them immediately when given here // Epic's comment: Projects may want to initiate passives or do other "BeginPlay" type of logic here. virtualvoidOnAvatarSet(const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilitySpec& Spec)override; };
AGDPlayerState::AGDPlayerState() { // Create ability system component, and set it to be explicitly replicated AbilitySystemComponent = CreateDefaultSubobject<UGDAbilitySystemComponent>(TEXT("AbilitySystemComponent")); AbilitySystemComponent->SetIsReplicated(true);
// Mixed mode means we only are replicated the GEs to ourself, not the GEs to simulated proxies. // If another GDPlayerState (Hero) receives a GE, we won't be told about it by the Server. // Attributes, GameplayTags, and GameplayCues will still replicate to us. AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Mixed);
// Create the attribute set, this replicates by default // Adding it as a subobject of the owning actor of an AbilitySystemComponent // automatically registers the AttributeSet with the AbilitySystemComponent AttributeSetBase = CreateDefaultSubobject<UGDAttributeSetBase>(TEXT("AttributeSetBase"));
// Set PlayerState's NetUpdateFrequency to the same as the Character. // Default is very low for PlayerStates and introduces perceived lag in the ability system. // 100 is probably way too high for a shipping game, you can adjust to fit your needs. NetUpdateFrequency = 100.0f;
// Instead of TWeakObjectPtrs, you could just have UPROPERTY() hard references or no references at all // and just call GetAbilitySystem() and make a GetAttributeSetBase() that can read from the PlayerState // or from child classes. Just make sure you test if the pointer is valid before using. // I opted for TWeakObjectPtrs because I didn't want a shared hard reference here and I didn't want an extra // function call of getting the ASC/AttributeSet from the PlayerState or child classes every time I // referenced them in this base class.
// Default abilities for this Character. These will be removed on Character death and // regiven if Character respawns. UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "GASDocumentation|Abilities") TArray<TSubclassOf<classUGDGameplayAbility>> CharacterAbilities;
// Default attributes for a character for initializing on spawn/respawn. // This is an instant GE that overrides the values for attributes that get reset on spawn/respawn. UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "GASDocumentation|Abilities") TSubclassOf<classUGameplayEffect> DefaultAttributes;
// These effects are only applied one time on startup UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "GASDocumentation|Abilities") TArray<TSubclassOf<classUGameplayEffect>> StartupEffects;
// Grant abilities on the Server. The Ability Specs will be replicated to the owning client. virtualvoidAddCharacterAbilities();
// Removes all CharacterAbilities. Can only be called by the Server. // Removing on the Server will remove from Client too. virtualvoidRemoveCharacterAbilities();
// Initialize the Character's attributes. Must run on Server but we run it on Client too // so that we don't have to wait. The Server's replication to the Client won't matter since // the values should be the same. virtualvoidInitializeAttributes();
virtualvoidAddStartupEffects();
其中,CharacterAbilities 为开局就赋予玩家的默认能力,DefaultAttributes 用于初始化玩家的各项属性,StartupEffects 是开局就起作用的其他 GE。由于该项目也考虑到了联机的场景,因此 AddCharacterAbilities() 只在 Server 进行,而客户端则需要等待 Ability Specs 同步;同样的,RemoveCharacterAbilities() 也只在 Server 进行;InitializeAttributes() 在 Server 与 Client 都会进行而不是等待同步。
/// @brief 赋予角色默认 Abilities,并完成按键的绑定 voidAGDCharacterBase::AddCharacterAbilities() { // Grant abilities, but only on the server if (GetLocalRole() != ROLE_Authority || !AbilitySystemComponent.IsValid() || AbilitySystemComponent->CharacterAbilitiesGiven) { return; }
for (TSubclassOf<UGDGameplayAbility>& StartupAbility : CharacterAbilities) { AbilitySystemComponent->GiveAbility( FGameplayAbilitySpec( StartupAbility, GetAbilityLevel(StartupAbility.GetDefaultObject()->AbilityID), static_cast<int32>(StartupAbility.GetDefaultObject()->AbilityInputID), this ) ); }
// Server only voidAGDHeroCharacter::PossessedBy(AController * NewController) { Super::PossessedBy(NewController);
AGDPlayerState* PS = GetPlayerState<AGDPlayerState>(); if (PS) { // Set the ASC on the Server. Clients do this in OnRep_PlayerState() AbilitySystemComponent = Cast<UGDAbilitySystemComponent>(PS->GetAbilitySystemComponent());
// AI won't have PlayerControllers so we can init again here just to be sure. // No harm in initing twice for heroes that have PlayerControllers. PS->GetAbilitySystemComponent()->InitAbilityActorInfo(PS, this);
// Set the AttributeSetBase for convenience attribute functions AttributeSetBase = PS->GetAttributeSetBase();
// If we handle players disconnecting and rejoining in the future, // we'll have to change this so that possession from rejoining doesn't reset attributes. // For now assume possession = spawn/respawn. InitializeAttributes();
AddStartupEffects();
AddCharacterAbilities();
// ... } }
// Client only voidAGDHeroCharacter::OnRep_PlayerState() { Super::OnRep_PlayerState();
AGDPlayerState* PS = GetPlayerState<AGDPlayerState>(); if (PS) { // Set the ASC for clients. Server does this in PossessedBy. AbilitySystemComponent = Cast<UGDAbilitySystemComponent>(PS->GetAbilitySystemComponent());
// Init ASC Actor Info for clients. Server will init its ASC when it possesses a new Actor. AbilitySystemComponent->InitAbilityActorInfo(PS, this);
// Bind player input to the AbilitySystemComponent. // Also called in SetupPlayerInputComponent because of a potential race condition. BindASCInput();
// Set the AttributeSetBase for convenience attribute functions AttributeSetBase = PS->GetAttributeSetBase();
// If we handle players disconnecting and rejoining in the future, we'll have to change this so that posession from rejoining doesn't reset attributes. // For now assume possession = spawn/respawn. InitializeAttributes();