Unreal Engine 4(虚幻UE4)GameplayAbilities 插件入门教程(五)技能属性集(AttributeSet)
如果没有完成前面的教程,请前往学习。先上一段理论介绍(源于https://wiki.unrealengine.com/GameplayAbilities_and_You#GameplayTasks):
【如果您没有耐心看完这些介绍,请调到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专门设计的宏和菜单来操纵这些属性在系统的能力。一个能力系统可以使用多个属性集,或者根本不使用。
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.
系统对无法找到的属性进行描述,并将简单地忽略不适合特定参与者和他的能力系统的统计数据。因此,也许玩家和敌人都有健康,魔法,攻击伤害,防御,你的名字,玩家会有一个额外的属性集,包含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.
PreAttributeBaseChange叫做之前……那么,属性的基本值(所以没有任何临时修饰符)就会改变。在游戏逻辑中使用这种方法是不明智的,而且大多数时候允许你描述数据的夹紧。
博主注: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
.