虛幻四Gameplay Ability System入門(mén)(2)
我最近在學(xué)習(xí)虛幻四的Gameplay Ability System,這個(gè)名字可以被理解為技能系統(tǒng)框架(大概)扇救,接下來(lái)我就簡(jiǎn)稱(chēng)為GAS或技能系統(tǒng)吴菠。在網(wǎng)上找了很久杰刽,發(fā)現(xiàn)相關(guān)的中文教程比較少十拣,所以打算把自己的學(xué)習(xí)過(guò)程和對(duì)技能系統(tǒng)的理解寫(xiě)成文章祭饭,既幫助我理解图毕,也希望可以幫助到其它想要學(xué)習(xí)GAS的朋友惫皱。之前寫(xiě)過(guò)一篇教程像樊,但感覺(jué)很不滿意,于是打算重寫(xiě)一遍旅敷。接下來(lái)進(jìn)入正題生棍。
什么是Gameplay Ability System?
在很多的游戲中,角色會(huì)擁有很多的技能媳谁,比如火球術(shù)涂滴,治療術(shù)等等友酱。這些技能會(huì)消耗法力值,存在冷卻時(shí)間柔纵,可以造成傷害缔杉。同時(shí)一個(gè)角色可以擁有多個(gè)技能,技能之間也會(huì)相互關(guān)聯(lián)搁料,比如火球術(shù)無(wú)法對(duì)使用冰霜護(hù)盾的敵人造成傷害或详。
實(shí)現(xiàn)以上的種種效果需要一個(gè)較為復(fù)雜的框架,而虛幻四的GAS系統(tǒng)就為我們提供了一個(gè)管理技能的系統(tǒng)郭计,可以很方便的實(shí)現(xiàn)技能需要霸琴,但GAS目前離不開(kāi)C++,而且官方目前還沒(méi)有提供方便入門(mén)的教程與文檔拣宏,因此學(xué)習(xí)起來(lái)還是比較痛苦的沈贝,以下是我認(rèn)為比較好的入門(mén)教程:
Bilibili
UE4官方的視頻教程,中文
https://www.bilibili.com/video/BV1X5411V7jh
Youtube
有條件的可以翻墻去看
UE4官方視頻教程勋乾,英文,相較于中文的視頻嗡善,我認(rèn)為這個(gè)教程講解的更深入一點(diǎn)辑莫。
https://www.youtube.com/watch?v=YvXvWa6vbAA
Github
這一篇是我認(rèn)為最好的文檔了,包含一個(gè)項(xiàng)目和較為完整的說(shuō)明罩引,但仍然較為復(fù)雜各吨,建議看完上面兩個(gè)視頻對(duì)GAS有了一定了解再看。
https://github.com/tranek/GASDocumentation
GAS系統(tǒng)的基本構(gòu)成
- Ability System Component袁铐,GAS系統(tǒng)的大腦揭蜒,擁有Abilities(技能)和Attributes(屬性),可以把它理解為技能系統(tǒng)的中樞剔桨。
- Ability屉更,技能∪髯海可以把它理解為某項(xiàng)能力瑰谜,比如火球術(shù),跳躍等等树绩,它應(yīng)該包含較為完整的邏輯萨脑,可以添加給角色的技能系統(tǒng),也可以從技能系統(tǒng)中移除饺饭。
- Attribute和AttibuteSet渤早,屬性和屬性集。Attribute代表了角色的某種屬性瘫俊,比如Health鹊杖,Mana等等悴灵。
- Tags,層次化的標(biāo)簽仅淑。它代表了某種狀態(tài)或者屬性称勋。比如角色處于燃燒狀態(tài),那么這個(gè)狀態(tài)的標(biāo)簽就為Character.State.Burning涯竟,我們可以自定義狀態(tài)并在技能和效果中設(shè)置tag之間的關(guān)系赡鲜,比如角色處于燃燒標(biāo)簽時(shí)會(huì)受到傷害,但如果被帶有Water標(biāo)簽的效果影響庐船,就可以移除燃燒標(biāo)簽银酬。我認(rèn)為T(mén)ag應(yīng)該是技能系統(tǒng)的核心和魅力所在了。
- Gameplay Effect(GE)筐钟,技能效果揩瞪,它本身只是一個(gè)數(shù)據(jù)集,代表了對(duì)某些Attribute和Tag的修改篓冲。比如GE_Damage中應(yīng)該設(shè)置為對(duì)Attribute:Health修改Add -20李破,表示為傷害效果為扣血20點(diǎn)。它還可以添加修改Tag壹将,或者被Tag所影響嗤攻,和第四點(diǎn)的例子一樣,火焰的傷害效果本質(zhì)是一個(gè)GE诽俯,如果另外有一個(gè)帶有Water標(biāo)簽的GE作用于角色妇菱,那么它會(huì)移除燃燒效果GE。理論上GAS中對(duì)于Tag和Attribute的修改盡可能都要使用GE
- Ability Task暴区,表示為Ability中的一個(gè)任務(wù)闯团。比如接下來(lái)幾乎每一個(gè)技能都會(huì)用到的一個(gè)Task是PlayMontageAndWait,可以看到它創(chuàng)建并返回了一個(gè)Async Task.
- Gameplay cue仙粱,GC執(zhí)行的是非游戲邏輯的效果比如聲音特效房交,粒子特效,攝像機(jī)抖動(dòng)等等缰盏,GC通過(guò)關(guān)聯(lián)的Tag觸發(fā)涌萤,還是舉角色燃燒的粒子,當(dāng)角色身上有Burning的標(biāo)簽時(shí)口猜,就可以設(shè)置Gameplay Cue Tag, 然后就會(huì)觸發(fā)GC_Burning负溪,讓角色身上有一個(gè)燃燒的粒子特效。
下圖是我對(duì)GAS各個(gè)組件關(guān)系之間的簡(jiǎn)單理解济炎,并不完整且正確川抡,只是幫助大致理解各個(gè)組件之間的關(guān)系。
GAS基礎(chǔ)設(shè)置
這一部分涉及到虛幻四C++和藍(lán)圖的知識(shí),需要擁有一定的基礎(chǔ)崖堤。
首先打開(kāi)虛幻四侍咱,創(chuàng)建一個(gè)C++的空白項(xiàng)目,如果覺(jué)得配置麻煩也可以創(chuàng)建一個(gè)第三人稱(chēng)項(xiàng)目密幔。
這里我直接使用了epic商城中的素材楔脯,將素材直接添加到工程
1.角色基礎(chǔ)配置
因?yàn)檫@篇文章不是介紹虛幻四C++入門(mén)的,因此我就不具體介紹角色基礎(chǔ)配置的說(shuō)明了胯甩。
首先新建Character C++類(lèi)昧廷,命名為CharacterBase
在Project Setting中的Input添加Axis Mappings
打開(kāi)CharacterBase.h和Character.cpp
添加Camera和SprintArm,函數(shù)MoveForward和MoveRight
GENERATED_BODY()
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Camera", meta=(AllowPrivateAccess = "true"))
class USpringArmComponent* CameraBoom;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Camera", meta=(AllowPrivateAccess = "true"))
class UCameraComponent* FollowCamera;
protected:
// Character Movement
void MoveForward(float Value);
void MoveRight(float Value);
cpp實(shí)現(xiàn)
// Sets default values
ACharacterTesting::ACharacterTesting()
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
bUseControllerRotationPitch = false;
bUseControllerRotationRoll = false;
bUseControllerRotationYaw = false;
GetCharacterMovement()->bOrientRotationToMovement = true;
CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("Camera Boom"));
CameraBoom->SetupAttachment(RootComponent);
CameraBoom->TargetArmLength = 300.0f;
CameraBoom->bUsePawnControlRotation = true;
FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("Follow Camera"));
FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
FollowCamera->bUsePawnControlRotation = false;
}
// Called to bind functionality to input
void ACharacterTesting::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
PlayerInputComponent->BindAxis("MoveForward", this, &ACharacterTesting::MoveForward);
PlayerInputComponent->BindAxis("MoveRight", this, &ACharacterTesting::MoveRight);
PlayerInputComponent->BindAxis("LookUp", this, &APawn::AddControllerPitchInput);
PlayerInputComponent->BindAxis("Turn", this, &APawn::AddControllerYawInput);
}
void ACharacterTesting::MoveForward(float Value)
{
if((Controller != nullptr) && (Value != 0.0f))
{
const FRotator Rotation = Controller->GetControlRotation();
const FRotator YawRotation(0, Rotation.Yaw, 0);
const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
AddMovementInput(Direction, Value);
}
}
void ACharacterTesting::MoveRight(float Value)
{
if((Controller != nullptr) && (Value != 0.0f))
{
const FRotator Rotation = Controller->GetControlRotation();
const FRotator YawRotation(0, Rotation.Yaw, 0);
const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
AddMovementInput(Direction, Value);
}
}
在UE4 Editor中創(chuàng)建CharacterBase子類(lèi)BP_Character偎箫,給角色選擇mesh
創(chuàng)建AnimBlueprint,命名為AnimBP_Character,設(shè)置角色相應(yīng)的動(dòng)畫(huà)木柬。
這里只需要locomotion就夠了,把locomotion連接到output pose上即可
角色基本設(shè)置完成淹办。
2.GAS系統(tǒng)基礎(chǔ)配置
- 使用GAS系統(tǒng)首先需要在Plugins中Enable插件Gameplay Abilities
然后在ProjectName.Build.cs中眉枕,添加三個(gè)依賴(lài),分別是GameplayAbilities, GameplayTasks, GameplayTags
PrivateDependencyModuleNames.AddRange(new string[] { "GameplayAbilities", "GameplayTags", "GameplayTasks" });
然后Build Solution
2. 添加ASC
打開(kāi)CharacterBase.h
創(chuàng)建Ability System Component怜森,這里需要繼承一個(gè)接口速挑,然后實(shí)現(xiàn)接口中的純虛函數(shù)GetAbilitySystemComponent(),它的作用是返回AbilitySystem副硅,這個(gè)函數(shù)的作用是在不知道當(dāng)前角色是否具有AbilitySystem的時(shí)候時(shí)梗摇,我們就可以調(diào)用這個(gè)函數(shù),而不需要使用Cast_To
UCLASS()
class GAS__API ACharacterBase : public ACharacter, public IAbilitySystemInterface
{
//.......
public:
// ......
// Ability System Component
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="CharacterBase")
UAbilitySystemComponent* AbilitySystem;
virtual UAbilitySystemComponent* GetAbilitySystemComponent() const;
}
GetAbilitySystemComponent()實(shí)現(xiàn)
ACharacterBase::ACharacterBase()
{
//......
AbilitySystem = CreateDefaultSubobject<UAbilitySystemComponent>("AbilitySystem");
}
// override AbilityInterface virtual function
UAbilitySystemComponent* ACharacterBase::GetAbilitySystemComponent() const
{
return AbilitySystem;
}
這樣子Character就擁有了Ability System Component了想许。
3.接下來(lái)實(shí)現(xiàn)一個(gè)方法可以給ASC添加Ability,這個(gè)功能可以在BP中調(diào)用断序。
// Add Ability to Character
UFUNCTION(BlueprintCallable, Category="Ability System")
void GiveAbility(TSubclassOf<UGameplayAbility> Ability);
實(shí)現(xiàn)
void ACharacterBase::GiveAbility(TSubclassOf<UGameplayAbility> Ability)
{
if(AbilitySystem)
{
if(HasAuthority() && Ability)
{
AbilitySystem->GiveAbility(FGameplayAbilitySpec(Ability, 1));
}
AbilitySystem->InitAbilityActorInfo(this, this);
}
}
藍(lán)圖中調(diào)用的例子
4.創(chuàng)建和實(shí)現(xiàn)基礎(chǔ)的AttributeSet
在第一步我們實(shí)際上還用不到Attribute流纹,但方便起見(jiàn)還是先創(chuàng)建一下。命名為AttributeSetBase
打開(kāi)违诗。這里我只展現(xiàn)了創(chuàng)建Attribute:Health和MaxHealth的方法漱凝。
最前面的define是一種mecro方法,它在AttributeSet中實(shí)現(xiàn)诸迟,它可以自動(dòng)地幫你實(shí)現(xiàn)Attribute的getter, setter等方法茸炒。
attribute需要一個(gè)ReplicatedUsing方法。
#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)
/**
*
*/
UCLASS()
class GAS__API UAttributeSetBase : public UAttributeSet
{
GENERATED_BODY()
public:
UAttributeSetBase();
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
// Health and MaxHealth
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="Abilities", ReplicatedUsing=OnRep_Health)
FGameplayAttributeData Health;
ATTRIBUTE_ACCESSORS(UAttributeSetBase, Health);
UFUNCTION()
virtual void OnRep_Health(const FGameplayAttributeData& OldHealth);
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="Abilities", ReplicatedUsing=OnRep_MaxHealth)
FGameplayAttributeData MaxHealth;
ATTRIBUTE_ACCESSORS(UAttributeSetBase, MaxHealth);
UFUNCTION()
virtual void OnRep_MaxHealth(const FGameplayAttributeData& OldMaxHealth);
};
void UAttributeSetBase::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME_CONDITION_NOTIFY(UAttributeSetBase, Health, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UAttributeSetBase, MaxHealth, COND_None, REPNOTIFY_Always);
}
void UAttributeSetBase::OnRep_Health(const FGameplayAttributeData& OldHealth)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UAttributeSetBase, Health, OldHealth);
}
void UAttributeSetBase::OnRep_MaxHealth(const FGameplayAttributeData& OldMaxHealth)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UAttributeSetBase, MaxHealth, OldMaxHealth);
}
最后一步是把AttributeSet添加給角色阵苇。
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="Abilities")
UAttributeSetBase* AttributeSet;
AttributeSet = CreateDefaultSubobject<UAttributeSetBase>("Attribute Set");
OK壁公,到這一步角色的基礎(chǔ)GAS配置就完成了
5.測(cè)試
創(chuàng)建一個(gè)GameplayAbility類(lèi)的藍(lán)圖,命名為BP_Test
打開(kāi)后绅项,這里完成的是啟動(dòng)ability紊册,打印一個(gè)hello在屏幕上,然后結(jié)束Ability
然后打開(kāi)角色的藍(lán)圖快耿,在beginplay中調(diào)用GiveAbility函數(shù)
點(diǎn)擊鼠標(biāo)左鍵囊陡,啟用ability芳绩。
運(yùn)行游戲,點(diǎn)擊鼠標(biāo)左鍵應(yīng)該就可以看到Hello了撞反。