1. 程式人生 > 其它 >UE4 GAS外掛入門學習記錄1——初步認識及使用

UE4 GAS外掛入門學習記錄1——初步認識及使用

引言

本系列文章內容僅為個人學習記錄,使用UE的版本為4.27

學習來源及各參考文件:

GASDocumentation_Chinese

原文地址: tranek/GASDocumentation

翻譯地址: BillEliot/GASDocumentation_Chinese

B站up主:技術宅阿棍兒

 

1.GAS認識(GameplayAbilitySystem)

摘自官方文件:

Gameplay技能系統 是一個高度靈活的框架,可用於構建你可能會在RPG或MOBA遊戲中看到的技能和屬性型別。你可以構建可供遊戲中的角色使用的動作或被動技能,使這些動作導致各種屬性累積或損耗的狀態效果,實現約束這些動作使用的"冷卻"計時器或資源消耗,更改技能等級及每個技能等級的技能效果,啟用粒子或音效,等等。簡單來說,此係統可幫助你在任何現代RPG或MOBA遊戲中設計、實現及高效關聯各種遊戲中的技能,既包括跳躍等簡單技能,也包括你喜歡的角色的複雜技能集。

對於人物的基礎移動和對UI的互動(例如遊戲商店購買物品)則不建議使用GAS(可以但不建議)。

對於GAS外掛中各模組的介紹及一些名詞解釋:

  • GSC:指GameplaySystemComponent元件,所有需要使用GAS或者響應GAS系統的都需要有這個元件
  • GameplayAbility(GA):用於執行角色能力(Ability)和技能(Skill),比如讓人物跳躍,衝刺,攻擊等,可以設定等級、發動技能所需消耗量、技能冷卻等。
  • Attribute:用於管理角色或者actor的數值,例如人物血量,藍量,體力等。
  • GameplayEffect(GE):為Actor應用狀態效果 ,注意:這個不是用於顯示特效音效或者動作,而是用於新增狀態,例如人物中毒灼燒扣血。
  • GameplayTag:Tag意思為標籤,用於為角色新增一個或多個標籤,GameplayTag是GAS的一個重要組成部分,可用於各狀態之間的限制(感覺像是替代了常規的布林判斷),比如在受到攻擊時,如果自己有進行格擋,常規寫法是用一個bool判斷人物是否有格擋,而在GAS中則是使用GameplayTag在你使用格擋時給你新增一個標誌為格擋的Tag,受擊時則只需要判斷人物是否有這個Tag即可。
  • GameplayCue(GC):用於生成視覺或聲音效果 。
  • OwnerActor:擁有ASC的actor,如果是多人遊戲,ASC元件一般會放在PlayerState中,這時,PlayerState就是OwnerActor,如果是單機遊戲,則ASC可以放在character中,那麼Owneractor就是character。
  • AvatarActor:指被控制的actor物理形體,如果ASC元件在PlayerState中,PlayerState就是OwnerActor,而被控制的character就是AvatarActor,單機遊戲時,ASC在character中,則OwnerActor和AvatarActor都是character。
  • 還有一個為以上提到的所有應用同步(Replication).

GAS必須由C++建立,但是GameplayAbilityGameplayEffect可由設計師在藍圖中建立.

GAS中的現存問題:

  • GameplayEffect延遲調節(Latency Reconciliation).(不能預測能力冷卻時間, 導致高延遲玩家相比低延遲玩家, 對於短冷卻時間的能力有更低的啟用速率.
  • 不能預測性地移除GameplayEffect. 然而我們可以反向預測性地新增GameplayEffect, 從而高效的移除它. 但是這不總是合適或者可行的, 因此這仍然是個問題.

2.在專案中新增GAS

GAS是UE中的一個外掛,需要使用的話要先在外掛中啟用它,在設定-外掛中搜索GameplayAbilities勾選後重啟專案。

編輯

編輯

然後開啟專案C++類中的 "GameplayAbilities", "GameplayTasks".

編輯

 新增完成後重新編譯生成一下專案

3.使用GAS實現簡單的技能(跳躍、衝刺)

以UE自帶的C++第三人稱模板為例,製作多人遊戲相關的GAS

首先,要實現GAS,就必須要有一個ASC(Ability System Component),ASC是GAS的核心,如何需要與GAS互動的Actor都必須要附加ASC,這些物件都存於ASC並由其管理和同步(除了由AttributeSet同步的Attribute).

ASC 附加的 Actor 被引用作為該 ASC的OwnerActor,該 ASC的物理代表 Actor 被稱為 AvatarActor. OwnerActor 和 AvatarActor 可以是同一個 Actor,比如MOBA遊戲中的一個簡單AI小兵;它們也可以是不同的 Actor,比如MOBA遊戲中玩家控制的英雄,其中 ownerActor是Playerstate,AvatarActor 是英雄的character 類.絕大多數Actor的 ASC 都附加在其自身,如果你的Actor會重生並且重生時需要持久化Attribute或 GameplayEffect (比如MOBA中的英雄)那麼 ASC理想的位置就是Playerstate. 

注意:如果ASC位於playerState,則需要將NetUpdateFrequency值調高,參考視訊NetUpdateFrequency設定

建立ASC:(這裡我是在繼承PlayerState的類中)

.h檔案

包含標頭檔案:
1 #include "AbilitySystemInterface.h"
繼承介面IAbilitySystemInterface:

 重寫介面函式(必須重寫):

virtual UAbilitySystemComponent* GetAbilitySystemComponent() const override;

宣告ASC元件:

UPROPERTY()
    class UAbilitySystemComponent* AbilitySystemComponent;

.cpp檔案:  

包含標頭檔案:

#include "AbilitySystemComponent.h"

在建構函式中建立例項:

APlayerStateBase::APlayerStateBase()
{
    //建立ASC例項
    AbilitySystemComponent = CreateDefaultSubobject<UAbilitySystemComponent>(TEXT("AbilitySystemComponent"));
    //開啟網路複製
    AbilitySystemComponent->SetIsReplicated(true);
}

實現介面函式:

UAbilitySystemComponent* APlayerStateBase::GetAbilitySystemComponent() const
{
    return AbilitySystemComponent;
}

跳躍實現:

找到專案檔案,例如我的專案名是GAS_TestDemo,那對應專案檔案則為GAS_TestDemo.h

.h檔案:

建立列舉:用於對映角色輸入,UMETA(DisplayName = "Jump")裡的"Jump"是這個列舉值在藍圖的別名

UENUM(BlueprintType)
enum class EGASAbilityInputID : uint8
{
    //
    None            UMETA(DisplayName = "None"),
    //跳躍
    Jump            UMETA(DisplayName = "Jump"),
    //衝刺
    Sprint            UMETA(DisplayName = "Sprint"),
    //攻擊
    Attack            UMETA(DisplayName = "Atk"),
};

在UE編輯器的專案設定-輸入中建立對應名字的輸入操作:(注意:這裡的名字需要和上面列舉的名字一致)

因為這裡的ASC在playerState,所以在character中需要獲得對ASC的引用

.h檔案中:

先宣告ASC

UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "GameplayAbilitySystem")
    class UAbilitySystemComponent* AbilitySystemComponent;

重寫character的函式PossessedBy(),作用好像是在character被控制器控制時會回撥這個函式

//在被控制器控制時呼叫
    virtual void PossessedBy(AController* NewController) override;

.cpp檔案中:

需要將輸入和Ability聯絡起來,在SetupPlayerInputComponent函式中新增:

void AGAS_TestDemoCharacter::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);

    //在專案檔案中定義的全域性結構體EGASAbilityInputID,用於和專案設定的輸入操作名字對應
    //將輸入和能力元件繫結
    //FGameplayAbilityInputBinds的第三個引數EGASAbilityInputID為專案檔案建立的列舉型別
    if (IsValid(AbilitySystemComponent))
    {
        AbilitySystemComponent->BindAbilityActivationToInputComponent(PlayerInputComponent,
            FGameplayAbilityInputBinds(
                FString(),
                FString(),
                FString(TEXT("EGASAbilityInputID")),
                static_cast<int32>(EGASAbilityInputID::None),
                static_cast<int32>(EGASAbilityInputID::None)
                )
            );
    }
}

實現PossessedBy():注意:因為這裡的GSC在playerState中,所以需要引入標頭檔案。同時,UGameplayAbility_CharacterJump也需要引入標頭檔案:

#include "PlayerStateBase.h"    //這個標頭檔案是自己定義ASC的那個標頭檔案
#include "GameplayAbilities/Public/Abilities/GameplayAbility_CharacterJump.h"
void AGAS_TestDemoCharacter::PossessedBy(AController* NewController)
{
    Super::PossessedBy(NewController);

    //獲取玩家的PlayerState
    APlayerStateBase* PS = GetPlayerState<APlayerStateBase>(); 
    
    if (PS)
    {
        AbilitySystemComponent = Cast<APlayerStateBase>(PS)->GetAbilitySystemComponent();
        
        /**
        *初始化了Abilities的ActorInfo(參與者資訊),該結構儲存了有關我們正在對誰採取行動以及誰控制我們的資訊。即OwnerActor和AvatarActor
        *OwnerActor是邏輯上擁有此元件的參與者。
        *AvatarActor是pawn。
        */
        PS->GetAbilitySystemComponent()->InitAbilityActorInfo(PS, this);
    }

    //***判斷多人遊戲時是否是在伺服器端**********判斷ASC元件是否有效
    if (GetLocalRole() !=  ROLE_Authority || !IsValid(AbilitySystemComponent))
    {
        return;
    }

    /*給予跳躍能力,
     *FGameplayAbilitySpec第一個引數就是當操作發生後,需要執行的東西,第二個為技能等級(暫時不知道怎麼用,預設1),第三個就是繫結的輸入操作鍵,第四個應該是繫結該技能的類吧(應該)
     *其中UGameplayAbility_CharacterJump::StaticClass()是UEGAS封裝好的,類似於正常繫結跳躍時的引數&ACharacter::Jump
     * EGASAbilityInputID::Jump為專案檔案中定義的那個列舉,用於和輸入操作繫結
     */
    AbilitySystemComponent->GiveAbility(FGameplayAbilitySpec(UGameplayAbility_CharacterJump::StaticClass(),    1,
        static_cast<int32>(EGASAbilityInputID::Jump), this));

}

至此,角色的跳躍就已經被封裝成了一個技能,由於這個跳躍是自帶的,我們好像並沒有做什麼東西,因此繼續實現一個衝刺技能

 

衝刺技能實現:

在角色類中:

.h檔案:

宣告一個GA,由藍圖去指定:

  /**
     * @brief 宣告一個能力GA
     */
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GameplayAbilitySystem")
    TSubclassOf<class UGameplayAbility> SprintAbility;

.cpp檔案:

在PossessedBy()函式中繼續新增:(為什麼在這裡新增?放在這裡,衝刺技能就會在控制器控制character時啟用此技能)

  /*
     * 給予衝刺能力
     * 因為SprintAbility是由藍圖指定,所以需要判斷有效性,避免出錯
     */
    if (SprintAbility)
    {
        AbilitySystemComponent->GiveAbility(FGameplayAbilitySpec(SprintAbility, 1,
            static_cast<int32>(EGASAbilityInputID::Sprint), this));
    }

具體邏輯實現:

藍圖中:(也可以在C++中實現)

建立一個繼承C++第三人稱character的藍圖,在事件圖表中建立一個自定義事件或者函式名為Sprint:

在內容瀏覽器中右鍵選擇GameplayAbility,建立並命名為GA_Sprint,開啟:

   編寫:

  

將此GA指定到角色藍圖中對應的值:

 這樣就實現了由GAS封裝的角色衝刺技能,所以技能實現邏輯還是在人物身上,只是由GAS去封裝呼叫,可以由GAS來賦予或剝除。

如果沒有反應,看看專案輸入的名字是否和專案檔案的列舉對應上。

目前只有給予技能,還沒有學習到移除技能,先這樣吧。