[UE5]GameplayAbilitySystemメモ2 -基本要素の実装-
前回の記事
更新情報
更新日 | 内容 |
---|---|
2024/06/01 | ・Attributeアクセサについて追記しました ・CharacterクラスなどのGASに関与しない準備の部分を削除しました ・CharacterクラスなどのGASに関与する部分を次回の記事に移行しました |
前置き
この記事ではC++を使用します。
解説を挟みますが、ある程度わかる人向けです
また、現在個人製作中のゲーム"KillGurality"のプロジェクトを用いて記事を書いているので、プロジェクト名を使用するプレフィックスなどには"KG"を使用しています。
ソースコードをコピペする場合は、環境に合わせて置き換えてください。
環境
バージョン:UE5.2
エディタ:VSCode
C++プロジェクトの作り方や勉強は、こちらの記事を参考にしてください
とてもわかりやすく、無料パートだけでも参考になるかと思います。おすすめです
VSCodeを使っている方は、この設定をしておくと便利です。
GASを使う準備
プロジェクト名.Build.csの、PrivateDependencyModuleNames.AddRangeに以下を追加してください
"GameplayTags", "GameplayTasks"
同様に、PublicDependencyModuleNames.AddRangeに以下を追加してください
"GameplayAbilities"
GameplayAbilitiesは、あとでIAbilitySystemInterfaceクラスをpublic継承するためにこちらに設定しています。
その後、プラグイン設定からGameplayAbilitiesのチェックを有効にしてください。
プロジェクトをビルドし、再起動します。
こうすることで、GASに必要なコンポーネントを使用することができます。
AttributeSet
ひとまずキャラクターのヒットポイントを管理するAttributeSetを作っていきます。
UE5エディタのツール->新規C++クラスから、AttributeSetを継承したクラスを作成します。
ここの設定は自由ですが、私はPublic直下に"/AbilitySystem"を追記することでフォルダを作成し、そこに"プロジェクト名AbilitySet"を作成しました。
ソースコードはこちらです。
AttributeSet.h
#pragma once
#include "CoreMinimal.h"
#include "AttributeSet.h"
#include "AbilitySystemComponent.h"
#include "KGAttributeSet.generated.h"
#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)
UCLASS()
class KILLGURALITY_API UKGAttributeSet : public UAttributeSet
{
GENERATED_BODY()
public:
UKGAttributeSet();
UPROPERTY(BlueprintReadOnly, Category = "Attribute")
FGameplayAttributeData Health;
ATTRIBUTE_ACCESSORS(UKGAttributeSet, Health)
UPROPERTY(BlueprintReadOnly, Category = "Attribute")
FGameplayAttributeData MaxHealth;
ATTRIBUTE_ACCESSORS(UKGAttributeSet, MaxHealth)
};
FGameplayAttributeDataでプレイヤーが持つステータスの値を定義しています。
ここで、MaxHealthは基礎値、Healthは現在の値といった扱いです。
Healthはダメージを受ける、回復アイテムを使うなどで可変しますが、基礎値は基本的には不変です。
後に追加する他のステータスに関してもこのように実装していきます。
AttributeSet.cpp
#include "AbilitySystem/KGAttributeSet.h"
UKGAttributeSet::UKGAttributeSet()
{
InitHealth(100.0f);
InitMaxHealth(100.0f);
}
お気づきかと思われますが、この2つの初期化関数はヘッダーファイルで宣言せずとも使用できます。
これは、先に定義したATTRIBUTE_ACCESSORS
が影響しています。
Attributeのアクセサーを設定
マクロを使います。
Attribute.hを見てみると、便利なマクロが用意してありました
Attribute.h
/**
* This defines a set of helper functions for accessing and initializing attributes, to avoid having to manually write these functions.
* It would creates the following functions, for attribute Health
*
* static FGameplayAttribute UMyHealthSet::GetHealthAttribute();
* FORCEINLINE float UMyHealthSet::GetHealth() const;
* FORCEINLINE void UMyHealthSet::SetHealth(float NewVal);
* FORCEINLINE void UMyHealthSet::InitHealth(float NewVal);
*
* To use this in your game you can define something like this, and then add game-specific functions as necessary:
*
* #define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
* GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
* GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
* GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
* GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)
*
* ATTRIBUTE_ACCESSORS(UMyHealthSet, Health)
*/
#define GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
static FGameplayAttribute Get##PropertyName##Attribute() \
{ \
static FProperty* Prop = FindFieldChecked<FProperty>(ClassName::StaticClass(), GET_MEMBER_NAME_CHECKED(ClassName, PropertyName)); \
return Prop; \
}
#define GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
FORCEINLINE float Get##PropertyName() const \
{ \
return PropertyName.GetCurrentValue(); \
}
#define GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
FORCEINLINE void Set##PropertyName(float NewVal) \
{ \
UAbilitySystemComponent* AbilityComp = GetOwningAbilitySystemComponent(); \
if (ensure(AbilityComp)) \
{ \
AbilityComp->SetNumericAttributeBase(Get##PropertyName##Attribute(), NewVal); \
}; \
}
#define GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName) \
FORCEINLINE void Init##PropertyName(float NewVal) \
{ \
PropertyName.SetBaseValue(NewVal); \
PropertyName.SetCurrentValue(NewVal); \
}
"ATTRIBUTE_ACCESSORS(ClassName, PropertyName)"を追記することで、初期化・ゲッター・セッターを生成してくれるようです。
ここで、GetHealthAttributeとGetHealthはそれぞれGameplayAttributeとfloat型を返すことに注意して使い分けてください
AbilitySystemComponent
AttrubuteSetの時と同じ手順で、AbilitySystemComponentを継承したクラスを作成します。
こちらはひとまず作成しておくだけで大丈夫です。
PlayerState
AttributeSetの時とほぼ同じ手順で、PlayerStateを継承したクラスを作成します。
こちらはフォルダをAbilitySystemからPlayerに変更しました。
ソースコードはこちらです
PlayerState.h
#pragma once
#include "CoreMinimal.h"
#include "AbilitySystemInterface.h"
#include "GameFramework/PlayerState.h"
#include "KGPlayerState.generated.h"
UCLASS()
class KILLGURALITY_API AKGPlayerState : public APlayerState, public IAbilitySystemInterface
{
GENERATED_BODY()
public:
AKGPlayerState();
virtual class UAbilitySystemComponent* GetAbilitySystemComponent() const override;
UAttributeSet* GetAttributeSet() const {return AttributeSet; };
protected:
UPROPERTY()
TObjectPtr<UAbilitySystemComponent> AbilitySystemComponent;
UPROPERTY()
TObjectPtr<UAttributeSet> AttributeSet;
};
前回の記事にあったように、PlayerStateはPawn, AbilitySystemComponent, Attributeを管理する役割を持っています。そのため、ここでIAbilitySystemInterfaceを継承させています。
IAbilitySystemInterfaceクラスの定義を見てみると、以下のように記述されています。
IAbilitySystemInterface
class GAMEPLAYABILITIES_API IAbilitySystemInterface
{
GENERATED_IINTERFACE_BODY()
/** Returns the ability system component to use for this actor. It may live on another actor, such as a Pawn using the PlayerState's component */
virtual UAbilitySystemComponent* GetAbilitySystemComponent() const = 0;
};
GetAbilitySystemComponent()が = 0によって純粋仮想関数として定義されているので、派生クラスでは必ずオーバーライドする必要があります。
また、ObjectポインタでAbilitySystemComponentとAttributeSetを保持できる変数を宣言しています。
UE5から登場したTObjectPtrですが、ポインタみたいなものですのでUPROPERTYを忘れないようにしましょう。詳しくはこちらの記事を参照してください。
PlayerState.cpp
#include "Player/KGPlayerState.h"
#include "AbilitySystem/KGAbilitySystemComponent.h"
#include "AbilitySystem/KGAttributeSet.h"
AKGPlayerState::AKGPlayerState()
{
AbilitySystemComponent = CreateDefaultSubobject<UKGAbilitySystemComponent>(TEXT("AbilitySystemComponent"));
AttributeSet = CreateDefaultSubobject<UKGAttributeSet>(TEXT("AttributeSet"));
}
UAbilitySystemComponent* AKGPlayerState::GetAbilitySystemComponent() const
{
return AbilitySystemComponent;
}
コンストラクタでAbilitySystemComponentとAttributeSetをサブクラスにセットします。
GetAbilitySystemComponentでは、先述した定義にコメントされていたようにAbilitySystemComponentを返すように処理をします。
おわり
前回とは打って変わってソースコードばかりの記事になってしまいました...
そしてこれを実行しても、目に見える変化は特にありません。
しかし、この基盤づくりが重要だと考えます。でかい規模のプロジェクトになるほど、ここでしっかり作りこめば後の仕様変更、バグ修正などでも役に立つはずです、きっと
次回はいよいよキャラクターが持つAbilitySystemに影響を与えてみようと思います。
ここでもまだデバッグ上での数値変化のみを実装するので、見た目の実装はまだ先になりそうです。
次回と言いつつ続くかどうかは気力次第、がんばれ~
Discussion