虛幻四Gameplay Ability System入門(2)
我最近在學(xué)習(xí)虛幻四的Gameplay Ability System,這個名字可以被理解為技能系統(tǒng)框架(大概),接下來我就簡稱為GAS或技能系統(tǒng)。在網(wǎng)上找了很久,發(fā)現(xiàn)相關(guān)的中文教程比較少,所以打算把自己的學(xué)習(xí)過程和對技能系統(tǒng)的理解寫成文章,既幫助我理解,也希望可以幫助到其它想要學(xué)習(xí)GAS的朋友。之前寫過一篇教程,但感覺很不滿意,于是打算重寫一遍。接下來進(jìn)入正題。
什么是Gameplay Ability System?
在很多的游戲中,角色會擁有很多的技能,比如火球術(shù),治療術(shù)等等。這些技能會消耗法力值,存在冷卻時間,可以造成傷害。同時一個角色可以擁有多個技能,技能之間也會相互關(guān)聯(lián),比如火球術(shù)無法對使用冰霜護(hù)盾的敵人造成傷害。
實(shí)現(xiàn)以上的種種效果需要一個較為復(fù)雜的框架,而虛幻四的GAS系統(tǒng)就為我們提供了一個管理技能的系統(tǒng),可以很方便的實(shí)現(xiàn)技能需要,但GAS目前離不開C++,而且官方目前還沒有提供方便入門的教程與文檔,因此學(xué)習(xí)起來還是比較痛苦的,以下是我認(rèn)為比較好的入門教程:
Bilibili
UE4官方的視頻教程,中文
https://www.bilibili.com/video/BV1X5411V7jh
Youtube
有條件的可以翻墻去看
UE4官方視頻教程,英文,相較于中文的視頻,我認(rèn)為這個教程講解的更深入一點(diǎn)。
https://www.youtube.com/watch?v=YvXvWa6vbAA
Github
這一篇是我認(rèn)為最好的文檔了,包含一個項(xiàng)目和較為完整的說明,但仍然較為復(fù)雜,建議看完上面兩個視頻對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),那么這個狀態(tài)的標(biāo)簽就為Character.State.Burning,我們可以自定義狀態(tài)并在技能和效果中設(shè)置tag之間的關(guān)系,比如角色處于燃燒標(biāo)簽時會受到傷害,但如果被帶有Water標(biāo)簽的效果影響,就可以移除燃燒標(biāo)簽。我認(rèn)為Tag應(yīng)該是技能系統(tǒng)的核心和魅力所在了。
- Gameplay Effect(GE),技能效果,它本身只是一個數(shù)據(jù)集,代表了對某些Attribute和Tag的修改。比如GE_Damage中應(yīng)該設(shè)置為對Attribute:Health修改Add -20,表示為傷害效果為扣血20點(diǎn)。它還可以添加修改Tag,或者被Tag所影響,和第四點(diǎn)的例子一樣,火焰的傷害效果本質(zhì)是一個GE,如果另外有一個帶有Water標(biāo)簽的GE作用于角色,那么它會移除燃燒效果GE。理論上GAS中對于Tag和Attribute的修改盡可能都要使用GE
- Ability Task,表示為Ability中的一個任務(wù)。比如接下來幾乎每一個技能都會用到的一個Task是PlayMontageAndWait,可以看到它創(chuàng)建并返回了一個Async Task.
- Gameplay cue,GC執(zhí)行的是非游戲邏輯的效果比如聲音特效,粒子特效,攝像機(jī)抖動等等,GC通過關(guān)聯(lián)的Tag觸發(fā),還是舉角色燃燒的粒子,當(dāng)角色身上有Burning的標(biāo)簽時,就可以設(shè)置Gameplay Cue Tag, 然后就會觸發(fā)GC_Burning,讓角色身上有一個燃燒的粒子特效。
下圖是我對GAS各個組件關(guān)系之間的簡單理解,并不完整且正確,只是幫助大致理解各個組件之間的關(guān)系。
GAS基礎(chǔ)設(shè)置
這一部分涉及到虛幻四C++和藍(lán)圖的知識,需要擁有一定的基礎(chǔ)。
首先打開虛幻四,創(chuàng)建一個C++的空白項(xiàng)目,如果覺得配置麻煩也可以創(chuàng)建一個第三人稱項(xiàng)目。
這里我直接使用了epic商城中的素材,將素材直接添加到工程
1.角色基礎(chǔ)配置
因?yàn)檫@篇文章不是介紹虛幻四C++入門的,因此我就不具體介紹角色基礎(chǔ)配置的說明了。
首先新建Character C++類,命名為CharacterBase
在Project Setting中的Input添加Axis Mappings
打開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子類BP_Character,給角色選擇mesh
創(chuàng)建AnimBlueprint,命名為AnimBP_Character,設(shè)置角色相應(yīng)的動畫。
這里只需要locomotion就夠了,把locomotion連接到output pose上即可
角色基本設(shè)置完成。
2.GAS系統(tǒng)基礎(chǔ)配置
- 使用GAS系統(tǒng)首先需要在Plugins中Enable插件Gameplay Abilities
然后在ProjectName.Build.cs中,添加三個依賴,分別是GameplayAbilities, GameplayTasks, GameplayTags
PrivateDependencyModuleNames.AddRange(new string[] { "GameplayAbilities", "GameplayTags", "GameplayTasks" });
然后Build Solution
2. 添加ASC
打開CharacterBase.h
創(chuàng)建Ability System Component,這里需要繼承一個接口,然后實(shí)現(xiàn)接口中的純虛函數(shù)GetAbilitySystemComponent(),它的作用是返回AbilitySystem,這個函數(shù)的作用是在不知道當(dāng)前角色是否具有AbilitySystem的時候時,我們就可以調(diào)用這個函數(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.接下來實(shí)現(xiàn)一個方法可以給ASC添加Ability,這個功能可以在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,但方便起見還是先創(chuàng)建一下。命名為AttributeSetBase
打開。這里我只展現(xiàn)了創(chuàng)建Attribute:Health和MaxHealth的方法。
最前面的define是一種mecro方法,它在AttributeSet中實(shí)現(xiàn),它可以自動地幫你實(shí)現(xiàn)Attribute的getter, setter等方法。
attribute需要一個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.測試
創(chuàng)建一個GameplayAbility類的藍(lán)圖,命名為BP_Test
打開后,這里完成的是啟動ability,打印一個hello在屏幕上,然后結(jié)束Ability
然后打開角色的藍(lán)圖,在beginplay中調(diào)用GiveAbility函數(shù)
點(diǎn)擊鼠標(biāo)左鍵,啟用ability。
運(yùn)行游戲,點(diǎn)擊鼠標(biāo)左鍵應(yīng)該就可以看到Hello了。