若是沒有完成前面的教程,請前往學習。先上一段理論介紹(源於https://wiki.unrealengine.com/GameplayAbilities_and_You#GameplayTasks):ios
【若是您沒有耐心看完這些介紹,請調到MarkA處】數組
AttributeSets are thankfully very simple to explain. They define float values (and ONLY float values. Right now only float attributes are supported) and can be connected to AbilitySystems to grant the ability system in question these attributes. GameplayEffects and GameplayEffectExecutionCalculations have specifically designed macros and menus to manipulate these attributes on an ability system. An ability system may use multiple attribute sets or none at all, too.安全
值得慶幸的是,屬性是很容易解釋的。它們定義浮點值(而且只定義浮點值。如今只支持浮點屬性,而且能夠鏈接到能力系統來授予對這些屬性進行質疑的能力系統。GameplayEffects和GameplayEffectExecutionCalculations專門設計的宏和菜單來操縱這些屬性在系統的能力。一個能力系統可使用多個屬性集,或者根本不使用。app
The system accounts for attributes it cannot find and will simply ignore stats that are not appropriate for the particular actor and his AbilitySystem. As such, maybe both players and foes have Health, Mana, attack damage, defense, you name 'em, and players then have an extra attribute set containing RPG attributes such as Strength, Intelligence, Constitution and the like. These are all perfectly possible scenarios, and it's nice that the system gives you the option to mix and match multiple attribute sets. The best way to bind an attribute set to an ability system is to create the AttributeSet as the same actor's subobject in the constructor. The ability system should find it by itself. It does for me, at least.ide
系統對沒法找到的屬性進行描述,並將簡單地忽略不適合特定參與者和他的能力系統的統計數據。所以,也許玩家和敵人都有健康,魔法,攻擊傷害,防護,你的名字,玩家會有一個額外的屬性集,包含RPG屬性,如力量,智慧,體質等等。這些都是徹底可能的場景,很好,系統提供了混合和匹配多個屬性集的選項。將一個屬性集綁定到一個能力系統的最佳方法是在構造函數中建立一個AttributeSet做爲相同的參與者的子對象。能力系統應該本身找到它。至少對我來講是這樣。函數
Attributes within attribute sets are defined like any other UPROPERTY, which is amazingly practical and straightforward. Why can't everything in this module be... Well, it isn't that easy anyway, due to the AttributeSet's functions, which either deal with finding out which UPROPERTY the current parameter is talking about or have to do with the infinitely more complex GameplayEffectExecutionCalculation.學習
屬性集中的屬性與其餘UPROPERTY同樣定義,這是很是實用和直接的。爲何這個模塊裏的全部東西都不能……嗯,它不是那麼容易,因爲AttributeSet的功能,要麼處理尋找UPROPERTY當前參數的討論或與無限GameplayEffectExecutionCalculation更加複雜。測試
PreAttributeBaseChange is called before... well, an attribute's base value (so without any temporary modifiers) is changed. It would be unwise to use this for game logic, and is mostly there to allow you to describe stat clamping.ui
PreAttributeBaseChange叫作以前……那麼,屬性的基本值(因此沒有任何臨時修飾符)就會改變。在遊戲邏輯中使用這種方法是不明智的,並且大多數時候容許你描述數據的夾緊。this
博主注:PreAttributeBaseChange適用於校驗值,並將它們限定在範圍內(Clamp)。
PreAttributeChange is in the same boat, but here you can define clamping with temporary modifiers instead. Either way, NewValue describes the new value of a changed stat, and FGameplayAttribute Attribute describes some info about the stat we're talking about. If you want to find out if this particular Attribute change is talking about a particular Attribute MyAttribute in UMyAttributeSet, you'd do it something like this:
PreAttributeChange在同一條船上,可是在這裏你能夠用臨時的修飾符來定義。不管如何,NewValue描述了更改的stat的新值,FGameplayAttribute屬性描述了咱們正在討論的stat的一些信息。若是您想知道這個特定的屬性更改是否正在討論UMyAttributeSet中的某個屬性MyAttribute,那麼您應該這樣作:
Attribute.GetUProperty()==FindFieldChecked<UProperty>(UMyAttributeSet::StaticClass(),GET_MEMBER_NAME_CHECKED(UMyAttributeSet, MyAttribute))
This code takes the UPROPERTY variable of the Attribute parameter and checks if the referenced UPROPERTY is identical with the one that describes MyAttribute in UMyAttributeSet. The macro is mostly there for safety, I believe this is actually defined as a relatively simple string.
此代碼獲取屬性參數的UPROPERTY變量,並檢查引用的UPROPERTY是否與在UMyAttributeSet中描述MyAttribute的一個屬性相同。這個宏主要是爲了安全起見,我相信這其實是一個相對簡單的字符串。
PreGameplayEffectExecute is a function that takes the data a GameplayEffectExecutionCalculation spits out (including which stats it wishes to modify, and by how much), and can then decide if the GameplayEffectExecutionCalculation is allowed to influence the AttributeSet in any way, by returning an appropriate bool. PostGameplayEffectExecute happens after this evaluation and as such you are unable to throw the GameplayEffectExecution out properly by then. However, because 90% of the time things such as damage calculations will be effect executions, here will be an excellent place to wrap such a thing up, such as by, for example, checking if the damage you took killed you.
PreGameplayEffectExecute是一個函數,一個GameplayEffectExecutionCalculation突出接收數據(包括數據它但願修改和多少),而後能夠決定是否容許GameplayEffectExecutionCalculation影響AttributeSet以任何方式,經過返回一個適當的布爾值。PostGameplayEffectExecute發生後評價,所以你不能趕走GameplayEffectExecution正確。然而,因爲90%的時間,例如破壞計算將是執行死刑,這裏將是一個很好的地方來包裝這樣的東西,例如,例如,檢查你所形成的傷害。
【MarkA標記處】
來進行第一組實驗吧,筆者沒有想到這花費了我七到九個小時才摸索到。
第1.1步:
在Character.h中配置一個類數組(暴露)。
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AttributeSets")
TArray<TSubclassOf<class UAttributeSet>>AttributeSets;
第1.2步:
在beginplay末尾處添加下述代碼,表示初始化到AbilitySystem中。
for (TSubclassOf<UAttributeSet>& Set : AttributeSets)
{
AbilitySystem->InitStats(Set, nullptr);
}
第1.3步【十分重要,建立AttributeSet的代碼】:建立C++類,選擇AttributeSet。命名爲AS01。
爲了讓你們知道全部細節,筆者貼出了所有的AS01的代碼:
▼代碼開始(Cpp文件) // Fill out your copyright notice in the Description page of Project Settings. #include "AS01.h" /*void UAS01::PostGameplayEffectExecute(const FGameplayEffectModCallbackData & Data) { return; }*/ ▲代碼結束 ▼代碼開始(h文件) // Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "CoreMinimal.h" #include "AttributeSet.h" #include "AS01.generated.h" /** * */ UCLASS() class GATUT_API UAS01 : public UAttributeSet { GENERATED_BODY() public: UPROPERTY(Category = "Wizard Attributes | Health", EditAnywhere, BlueprintReadWrite) float Health; //virtual void PostGameplayEffectExecute(const struct FGameplayEffectModCallbackData &Data) override; }; ▲代碼結束
知識點說明:
#: AttributeSet表示屬性集,它僅僅是一些浮點數的集合罷了。好比我有AS_Basic表示基本屬性集,帶有HP,MP,有AS_Animal表示野獸類屬性集,帶有Constitution(體質),Wild(野性),Fecundity(繁殖力)等屬性。
#: 這些屬性都是默認值爲零的。
第1.4步:
建立一個GE(GameplayEffect),命名爲GE_Milk,表示提升血量。在其中配置modifier以下:
第1.5步:
建立一個GA(GameplayAbility),命名爲GA_Milk, 表示自愈技能。在其中寫施用GE_Milk的邏輯:
第1.6步:在character中配置:
紅色框:仍是之前的那個MyAbilities數組,若是不記得細節就請去看第一次的教程。
黃色框:就是前文在代碼中暴露的AttributeSets數組,配置上AS01(屬性集),代碼將會在Beginplay的時候初始化它們。
邏輯:激活一個GA_Milk以下圖所示。
第1.7步:在character中寫查看Health的邏輯:其中有一個很重要的節點:GetFloatAttributeFromAbilitySystemComponent,表示讀取AbilitySystem中的AS01.Health屬性。
第1.8步:編譯運行。
讓咱們繼續探索。下一組實驗介紹的是屬性的修改先後,這個實驗體現了AttributeSet的一個優點。當你修改某個屬性時,它可能會超越上限或下限,此時就應該在PreAttributeChange來控制它限制在範圍之內。
第2.1步:在AS01.h文件中新增PreAttributeChange函數,這個函數表示「修改此屬性集時,將會執行的代碼」。同時請加上HealthAttribute函數,這個函數的意義是獲得Health這個屬性的類型(或者是說成鍵),若是還有不明白的,能夠從代碼中看個究竟。以下面的代碼所示。
▼代碼開始 // Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "CoreMinimal.h" #include "AttributeSet.h" #include "AS01.generated.h" /** * */ UCLASS() class GATUT_API UAS01 : public UAttributeSet { GENERATED_BODY() public: UPROPERTY(Category = "Wizard Attributes | Health", EditAnywhere, BlueprintReadWrite) float Health; virtual void PostGameplayEffectExecute(const struct FGameplayEffectModCallbackData &Data) override;//先無視這個 FGameplayAttribute HealthAttribute(); //得到Health屬性類型(或者說得到Health鍵,用來標識Health); virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue); }; ▲代碼結束 在AS01.cpp文件中實現它。 ▼代碼開始 // Fill out your copyright notice in the Description page of Project Settings. #include "AS01.h" #include "GameplayEffectExtension.h" #include "GameplayEffect.h" #include "GameplayEffectTypes.h" #include "AbilitySystemComponent.h" //先不要管這個 void UAS01::PostGameplayEffectExecute(const FGameplayEffectModCallbackData & Data) { UE_LOG(LogTemp, Warning, TEXT("%s"), *FString("This AS is ready to be changed")); UAbilitySystemComponent* Source = Data.EffectSpec.GetContext().GetOriginalInstigatorAbilitySystemComponent(); if (HealthAttribute() == Data.EvaluatedData.Attribute) { AActor* DamagedActor = nullptr; AController* DamagedController = nullptr; if (Data.Target.AbilityActorInfo.IsValid() && Data.Target.AbilityActorInfo->AvatarActor.IsValid()) { DamagedActor = Data.Target.AbilityActorInfo->AvatarActor.Get(); DamagedController = Data.Target.AbilityActorInfo->PlayerController.Get(); UE_LOG(LogTemp, Warning, TEXT("The DamagedActor Name is:%s"), *( DamagedActor->GetName() )); } } return; } //返回Health屬性鍵 FGameplayAttribute UAS01::HealthAttribute() { static UProperty* Property = FindFieldChecked<UProperty>(UAS01::StaticClass(), GET_MEMBER_NAME_CHECKED(UAS01, Health)); return FGameplayAttribute(Property); } //修改前 void UAS01::PreAttributeChange(const FGameplayAttribute & Attribute, float & NewValue) { UE_LOG(LogTemp, Warning, TEXT("%s"), *FString("PreAttributeChagne AS01")); //若是這個待修改的屬性是Health屬性,那麼就這樣Clamp if (Attribute == HealthAttribute()) { NewValue = FMath::Clamp(NewValue, 0.f, 100.f); //Clamp表示截取在某個範圍內,若是這個不熟悉,請看官方文檔中FMath中關於它的介紹 UE_LOG(LogTemp, Warning, TEXT("%s"), *FString("43 AS01")); } } ▲代碼結束
第2.2步:(請注意這些AttributeSet、Ability、Effect的掛鉤綁定是前面教程中有說起的,若是前面的教程沒有看,請前往學習)
編譯運行查看結果:
從結果中能夠看出,此屬性值的Clamp生效了。
讓咱們繼續探索,下一組實驗也是花費了幾個小時才摸索出來的,新的一個概念是「執行」Execution。
第3.1步:GAMilk的邏輯以下。
第3.2步:GAMilk中的細節不用配置:
第3.3步:在GEMilk中配置屬性以下,其中Modifiers表示修改,正如上面所說的,【一次修改將會add AS01.Health 10點】下方的Period表示執行週期,勾選ExecutePeriodicEffectOnApplication,【表示在此效果激活期間(HasDuration 10s)將會以2s週期地執行(execution)】,每一次執行就會進行一次modify,也就是說,在這10s之內,每兩秒鐘add 10 health。
至此,不妨運行看看效果,發現正如上文所述那樣。
第3.4步:一樣,咱們也作一個GE_Poison和GA_Poison表示此角色有「自毒」的Ability,
在Character中不要忘記了綁定MyAbilities數組(這是筆者自定義的,若是不明白其中的原理請看以前的教程)和綁定輸入事件。
至此,運行,先釋放GA_Milk,而後釋放GA_Poison,經過打印能夠看到血量的變更。
第3.5步:接下來將會介紹AttributeSet中的兩個重要函數
//這些函數在AS01.h中(即AttributeSet文件中) //表示數值修改前執行,前文說過了 virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue); //執行前執行 virtual bool PreGameplayEffectExecute(struct FGameplayEffectModCallbackData &Data); //執行後執行 virtual void PostGameplayEffectExecute(const struct FGameplayEffectModCallbackData &Data) override;
▼代碼開始(爲了讓你們清晰地參考,貼出AS01.h文件) // Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "CoreMinimal.h" #include "AttributeSet.h" #include "AS01.generated.h" /** * */ UCLASS() class GATUT_API UAS01 : public UAttributeSet { GENERATED_BODY() public: UPROPERTY(Category = "Wizard Attributes | Health", EditAnywhere, BlueprintReadWrite) float Health; virtual void PostGameplayEffectExecute(const struct FGameplayEffectModCallbackData &Data) override; FGameplayAttribute HealthAttribute(); virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue); virtual bool PreGameplayEffectExecute(struct FGameplayEffectModCallbackData &Data); }; ▲代碼結束 ▼代碼開始(AS01.cpp文件) // Fill out your copyright notice in the Description page of Project Settings. #include "AS01.h" #include "GameplayEffectExtension.h" #include "GameplayEffect.h" #include "GameplayEffectTypes.h" #include "AbilitySystemComponent.h" //我爲何知道要添加這些頭文件?這個有點難解釋 //GE執行前事件,此事件僅僅在「執行前」調用 /**原文解釋: * Called just before modifying the value of an attribute. AttributeSet can make additional modifications here. Return true to continue, or false to throw out the modification. * 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 5 ssecond +10 movement speed buff. */ bool UAS01::PreGameplayEffectExecute(FGameplayEffectModCallbackData & Data) { UE_LOG(LogTemp, Warning, TEXT("%s"), *FString("57 AS01 PreGPEE")); return true; } //GE執行後事件 /** * 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 5 ssecond +10 movement speed buff. */ void UAS01::PostGameplayEffectExecute(const FGameplayEffectModCallbackData & Data) { UE_LOG(LogTemp, Warning, TEXT("%s"), *FString("This AS is ready to be changed")); //得到施放這次修改的源頭 UAbilitySystemComponent* Source = Data.EffectSpec.GetContext().GetOriginalInstigatorAbilitySystemComponent(); //這次修改的屬性是Health嗎? if (HealthAttribute() == Data.EvaluatedData.Attribute) { AActor* TargetActor = nullptr; AController* TargetController = nullptr; if (Data.Target.AbilityActorInfo.IsValid() && Data.Target.AbilityActorInfo->AvatarActor.IsValid()) { TargetActor = Data.Target.AbilityActorInfo->AvatarActor.Get(); TargetController = Data.Target.AbilityActorInfo->PlayerController.Get(); //這裏給出了修改目標TargetActor的抓取方法 UE_LOG(LogTemp, Warning, TEXT("The TargetActor Name is:%s"), *(TargetActor->GetName() )); } AActor* SourceActor = nullptr; AController* SourceController = nullptr; AController* SourcePlayerController = nullptr; if (Source && Source->AbilityActorInfo.IsValid() && Source->AbilityActorInfo->AvatarActor.IsValid()) { SourceActor = Source->AbilityActorInfo->AvatarActor.Get(); SourceController = Source->AbilityActorInfo->PlayerController.Get(); //這裏給出了發起修改的源頭SourceActor的抓取方法 UE_LOG(LogTemp, Warning, TEXT("The SourceActor Name is:%s"), *(SourceActor->GetName())); } //Clamp,其實Clamp不是「執行後事件」最重要的邏輯 Health = FMath::Clamp(Health, 0.f, 100.f); //「執行後事件」最重要的邏輯是:若是數值過低,那麼表現觸發特定事情,如Health觸零,那麼死亡。 if (Health <= 0.f) { UE_LOG(LogTemp, Warning, TEXT("%s"), *FString("Death Logic Here 執行死亡邏輯")); TargetActor->Destroy(); } } return; } //返回Health屬性鍵 FGameplayAttribute UAS01::HealthAttribute() { static UProperty* Property = FindFieldChecked<UProperty>(UAS01::StaticClass(), GET_MEMBER_NAME_CHECKED(UAS01, Health)); return FGameplayAttribute(Property); } //修改前 void UAS01::PreAttributeChange(const FGameplayAttribute & Attribute, float & NewValue) { UE_LOG(LogTemp, Warning, TEXT("%s"), *FString("PreAttributeChagne AS01")); //若是這個待修改的屬性是Health屬性,那麼就這樣Clamp if (Attribute == HealthAttribute()) { NewValue = FMath::Clamp(NewValue, 0.f, 100.f); //Clamp表示截取在某個範圍內,若是這個不熟悉,請看官方文檔中FMath中關於它的介紹 UE_LOG(LogTemp, Warning, TEXT("%s"), *FString("43 AS01")); } } ▲代碼結束
稍等一下下,咱們還有一件重要的事情沒有作呢,那就是屬性的初始化。
第3.6步:新建一張表格csv(逗號分隔文件,若是不熟悉這個文件的樣式,能夠自行簡單瞭解一下)
其中的內容爲:
Name BaseValue MinValue MaxValue DerivedAttributeInfo bCanStack
AS01.Health 70 0 100 (這裏有一個空缺項) FALSE
注意:第一行須要絲絕不差,其它的數值能夠自行調整。
第3.7步:保存後關閉excel,導入資源。
導入形式選擇:(下面的CurrentFile請無視)
導入成功後查看:
第3.7B步:若是您以爲上面的作法(3.7和3.8)太複雜,那麼能夠選用下面的作法:
經過這樣的簡單的添加也可使用。
第3.8步:還差一點點了,在人物中添加一個UDataTable以及初始化,還記得以前的Character.cpp中構造函數裏有一段代碼:
for (TSubclassOf<UAttributeSet>& Set : AttributeSets)
{
AbilitySystem->InitStats(Set, nullptr);
}
咱們這裏將nullptr改成數據表便可。
具體的代碼以下(爲了參考,我給出所有的代碼,請特別留意【Mark+】處)
▼代碼開始 // Copyright 1998-2017 Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "GameFramework/Character.h" #include "AttributeSet.h" #include "AbilitySystemInterface.h" //改動1:添加頭文件 #include "GATutCharacter.generated.h" //這個枚舉類型使得FGameplayAbiliyInputBinds可以映射到技能槽中 UENUM(BlueprintType) enum class AbilityInput : uint8 //C++11的新特性,冒號後面寫的是無符號8位整數,表示這個枚舉只用無符號8位整數來表示 { UseAbility1 UMETA(DisplayName="Use Spell 1"), //藍圖中的展現名 UseAbility2 UMETA(DisplayName="Use Spell 2"), UseAbility3 UMETA(DisplayName = "Use Spell 3"), UseAbility4 UMETA(DisplayName = "Use Spell 4"), WeaponAbility UMETA(DisplayName="Use Weapon") //注意:被動技能也能夠聲明在這裏,可是無須和輸入作綁定。 };//*/ UCLASS(config=Game) class AGATutCharacter : public ACharacter ,public IAbilitySystemInterface //改動2:繼承此接口 { GENERATED_BODY() /** Camera boom positioning the camera behind the character */ UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true")) class USpringArmComponent* CameraBoom; /** Follow camera */ UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true")) class UCameraComponent* FollowCamera; //改動3:添加一個Abi組件 //BlueprintReadOnly的內容實際上是不容許寫在private區中的,若是寫在private區且要藍圖可讀,那麼須要寫上meta=(AllowPrivateAccess="true") UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Abilities, meta = (AllowPrivateAccess = "true")) class UAbilitySystemComponent* AbilitySystem; public: AGATutCharacter(); /** Base turn rate, in deg/sec. Other scaling may affect final turn rate. */ UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Camera) float BaseTurnRate; /** Base look up/down rate, in deg/sec. Other scaling may affect final rate. */ UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Camera) float BaseLookUpRate; protected: /** Resets HMD orientation in VR. */ void OnResetVR(); /** Called for forwards/backward input */ void MoveForward(float Value); /** Called for side to side input */ void MoveRight(float Value); /** * Called via input to turn at a given rate. * @param Rate This is a normalized rate, i.e. 1.0 means 100% of desired turn rate */ void TurnAtRate(float Rate); /** * Called via input to turn look up/down at a given rate. * @param Rate This is a normalized rate, i.e. 1.0 means 100% of desired turn rate */ void LookUpAtRate(float Rate); /** Handler for when a touch input begins. */ void TouchStarted(ETouchIndex::Type FingerIndex, FVector Location); /** Handler for when a touch input stops. */ void TouchStopped(ETouchIndex::Type FingerIndex, FVector Location); protected: // APawn interface virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override; // End of APawn interface public: /** Returns CameraBoom subobject **/ FORCEINLINE class USpringArmComponent* GetCameraBoom() const { return CameraBoom; } /** Returns FollowCamera subobject **/ FORCEINLINE class UCameraComponent* GetFollowCamera() const { return FollowCamera; } //改動4:添加一個返回此組件的方法 UFUNCTION(BlueprintCallable, Category = AS) UAbilitySystemComponent* GetAbilitySystemComponent()const; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Abilities) TArray< TSubclassOf<class UGameplayAbility> > MyAbilities; //符文 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Abilities) TSubclassOf<class UGameplayAbility> Rune; UFUNCTION(BlueprintCallable, Category = Abilities) void BindTargetToAbility(AGATutCharacter* Target, TSubclassOf<UGameplayAbility> &Ability); virtual void BeginPlay()override; static FName AbilitySystemName; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AttributeSets") TArray<TSubclassOf<class UAttributeSet>>AttributeSets; //Mark+ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = Abilities) UDataTable * AttrDataTable; }; ▲代碼結束 ▼代碼開始 // Copyright 1998-2017 Epic Games, Inc. All Rights Reserved. #include "GATutCharacter.h" #include "AbilitySystemComponent.h" #include "AbilitySystemGlobals.h" #include "GameplayAbilitiesModule.h" #include "Kismet/HeadMountedDisplayFunctionLibrary.h" #include "Camera/CameraComponent.h" #include "Components/CapsuleComponent.h" #include "Components/InputComponent.h" #include "GameFramework/CharacterMovementComponent.h" #include "GameFramework/Controller.h" #include "GameFramework/SpringArmComponent.h" FName AGATutCharacter::AbilitySystemName(TEXT("AbilitySystem")); ////////////////////////////////////////////////////////////////////////// // AGATutCharacter AGATutCharacter::AGATutCharacter() { // Set size for collision capsule GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f); // set our turn rates for input BaseTurnRate = 45.f; BaseLookUpRate = 45.f; // Don't rotate when the controller rotates. Let that just affect the camera. bUseControllerRotationPitch = false; bUseControllerRotationYaw = false; bUseControllerRotationRoll = false; // Configure character movement GetCharacterMovement()->bOrientRotationToMovement = true; // Character moves in the direction of input... GetCharacterMovement()->RotationRate = FRotator(0.0f, 540.0f, 0.0f); // ...at this rotation rate GetCharacterMovement()->JumpZVelocity = 600.f; GetCharacterMovement()->AirControl = 0.2f; // Create a camera boom (pulls in towards the player if there is a collision) CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom")); CameraBoom->SetupAttachment(RootComponent); CameraBoom->TargetArmLength = 300.0f; // The camera follows at this distance behind the character CameraBoom->bUsePawnControlRotation = true; // Rotate the arm based on the controller // Create a follow camera FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera")); FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName); // Attach the camera to the end of the boom and let the boom adjust to match the controller orientation FollowCamera->bUsePawnControlRotation = false; // Camera does not rotate relative to arm AbilitySystem = CreateDefaultSubobject<UAbilitySystemComponent>(TEXT("AbilitySystem")); // Note: The skeletal mesh and anim blueprint references on the Mesh component (inherited from Character) // are set in the derived blueprint asset named MyCharacter (to avoid direct content references in C++) } ////////////////////////////////////////////////////////////////////////// // Input void AGATutCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) { // Set up gameplay key bindings check(PlayerInputComponent); PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &ACharacter::Jump); PlayerInputComponent->BindAction("Jump", IE_Released, this, &ACharacter::StopJumping); PlayerInputComponent->BindAxis("MoveForward", this, &AGATutCharacter::MoveForward); PlayerInputComponent->BindAxis("MoveRight", this, &AGATutCharacter::MoveRight); // We have 2 versions of the rotation bindings to handle different kinds of devices differently // "turn" handles devices that provide an absolute delta, such as a mouse. // "turnrate" is for devices that we choose to treat as a rate of change, such as an analog joystick PlayerInputComponent->BindAxis("Turn", this, &APawn::AddControllerYawInput); PlayerInputComponent->BindAxis("TurnRate", this, &AGATutCharacter::TurnAtRate); PlayerInputComponent->BindAxis("LookUp", this, &APawn::AddControllerPitchInput); PlayerInputComponent->BindAxis("LookUpRate", this, &AGATutCharacter::LookUpAtRate); // handle touch devices PlayerInputComponent->BindTouch(IE_Pressed, this, &AGATutCharacter::TouchStarted); PlayerInputComponent->BindTouch(IE_Released, this, &AGATutCharacter::TouchStopped); // VR headset functionality PlayerInputComponent->BindAction("ResetVR", IE_Pressed, this, &AGATutCharacter::OnResetVR); //測試證實:在本篇的教程裏,不加這一句也能夠 //AbilitySystem->BindAbilityActivationToInputComponent(PlayerInputComponent, FGameplayAbiliyInputBinds("ConfirmInput", "CancelInput", "AbilityInput")); } UAbilitySystemComponent * AGATutCharacter::GetAbilitySystemComponent() const { return AbilitySystem; } void AGATutCharacter::BindTargetToAbility(AGATutCharacter * Target, TSubclassOf<UGameplayAbility>& Ability) { if (Ability == nullptr || Target == nullptr)return; AbilitySystem->InitAbilityActorInfo(this, Target); AbilitySystem->GiveAbility(FGameplayAbilitySpec(Rune.GetDefaultObject(), 1, 0)); UE_LOG(LogTemp, Warning, TEXT("%s"), *FString("BindTargetToAbility")); } void AGATutCharacter::BeginPlay() { Super::BeginPlay(); if (AbilitySystem == nullptr)return; UE_LOG(LogTemp, Warning, TEXT("%s"), *FString("Char Beginplay 97")); if (HasAuthority() && MyAbilities.Num()) { UE_LOG(LogTemp, Warning, TEXT("%s"), *FString("Char Bplay 99")); for (auto i = 0; i<MyAbilities.Num(); i++) { if (MyAbilities[i] == nullptr)continue; AbilitySystem->GiveAbility(FGameplayAbilitySpec(MyAbilities[i].GetDefaultObject(), 1, 0)); UE_LOG(LogTemp, Warning, TEXT("%s"), *FString("103bp we register an ability!")); } UE_LOG(LogTemp, Warning, TEXT("%s"), *FString("All Ablities registered")); } AbilitySystem->InitAbilityActorInfo(this, this); //這兩個參數覺得着Owner和Avatar UE_LOG(LogTemp, Warning, TEXT("%s"), *FString("Char Bplay 105"));//*/ for (TSubclassOf<UAttributeSet>& Set : AttributeSets) { AbilitySystem->InitStats(Set, AttrDataTable);//Mark+ } //UAbilitySystemGlobals* ASG = IGameplayAbilitiesModule::Get().GetAbilitySystemGlobals(); //FAttributeSetInitter* ASI = ASG->GetAttributeSetInitter(); } void AGATutCharacter::OnResetVR() { UHeadMountedDisplayFunctionLibrary::ResetOrientationAndPosition(); } void AGATutCharacter::TouchStarted(ETouchIndex::Type FingerIndex, FVector Location) { Jump(); } void AGATutCharacter::TouchStopped(ETouchIndex::Type FingerIndex, FVector Location) { StopJumping(); } void AGATutCharacter::TurnAtRate(float Rate) { // calculate delta for this frame from the rate information AddControllerYawInput(Rate * BaseTurnRate * GetWorld()->GetDeltaSeconds()); } void AGATutCharacter::LookUpAtRate(float Rate) { // calculate delta for this frame from the rate information AddControllerPitchInput(Rate * BaseLookUpRate * GetWorld()->GetDeltaSeconds()); } void AGATutCharacter::MoveForward(float Value) { if ((Controller != NULL) && (Value != 0.0f)) { // find out which way is forward const FRotator Rotation = Controller->GetControlRotation(); const FRotator YawRotation(0, Rotation.Yaw, 0); // get forward vector const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X); AddMovementInput(Direction, Value); } } void AGATutCharacter::MoveRight(float Value) { if ( (Controller != NULL) && (Value != 0.0f) ) { // find out which way is right const FRotator Rotation = Controller->GetControlRotation(); const FRotator YawRotation(0, Rotation.Yaw, 0); // get right vector const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y); // add movement in that direction AddMovementInput(Direction, Value); } } ▲代碼結束
第3.9步:在藍圖中記得綁定AttributeSet到上述的AttrDataTable中。
編譯運行,打印,能夠看到有默認值70。您還能夠釋放GA_Milk和GA_Poison技能,查看數值的浮動變化。當人物的血量低至0時,人物將會消失不見(Destroy)。
不容易啊,你們繼續加油。原創聲明:本文系小江村兒的文傑原創,如有參考的資料必在本文中給出。
——小江村兒的文傑 zouwj5@qq.com
.