📝

[UE5]GameplayAbilitySystemメモ2 -基本要素の実装-

2024/05/16に公開

前回の記事

https://zenn.dev/sayaivy/articles/gameplayability_1

更新情報

更新日 内容
2024/06/01 ・Attributeアクセサについて追記しました
・CharacterクラスなどのGASに関与しない準備の部分を削除しました
・CharacterクラスなどのGASに関与する部分を次回の記事に移行しました

前置き

この記事ではC++を使用します。
解説を挟みますが、ある程度わかる人向けです

また、現在個人製作中のゲーム"KillGurality"のプロジェクトを用いて記事を書いているので、プロジェクト名を使用するプレフィックスなどには"KG"を使用しています。
ソースコードをコピペする場合は、環境に合わせて置き換えてください。

環境
バージョン:UE5.2
エディタ:VSCode


C++プロジェクトの作り方や勉強は、こちらの記事を参考にしてください
とてもわかりやすく、無料パートだけでも参考になるかと思います。おすすめです
https://zenn.dev/posita33/books/ue5_starter_cpp_and_bp_001/viewer/chap_00_about

VSCodeを使っている方は、この設定をしておくと便利です。
https://dev.epicgames.com/documentation/ja-jp/unreal-engine/setting-up-visual-studio-code-for-unreal-engine#vsコード向けのintellisenseを設定する

GASを使う準備

プロジェクト名.Build.csの、PrivateDependencyModuleNames.AddRangeに以下を追加してください

"GameplayTags", "GameplayTasks"

同様に、PublicDependencyModuleNames.AddRangeに以下を追加してください

"GameplayAbilities"

GameplayAbilitiesは、あとでIAbilitySystemInterfaceクラスをpublic継承するためにこちらに設定しています。

その後、プラグイン設定からGameplayAbilitiesのチェックを有効にしてください。
alt text

プロジェクトをビルドし、再起動します。

こうすることで、GASに必要なコンポーネントを使用することができます。

AttributeSet

ひとまずキャラクターのヒットポイントを管理するAttributeSetを作っていきます。
UE5エディタのツール->新規C++クラスから、AttributeSetを継承したクラスを作成します。

alt text

ここの設定は自由ですが、私はPublic直下に"/AbilitySystem"を追記することでフォルダを作成し、そこに"プロジェクト名AbilitySet"を作成しました。

alt text

ソースコードはこちらです。

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を忘れないようにしましょう。詳しくはこちらの記事を参照してください。
https://qiita.com/okqi/items/02327f965578ee1c379f


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に影響を与えてみようと思います。
ここでもまだデバッグ上での数値変化のみを実装するので、見た目の実装はまだ先になりそうです。

次回と言いつつ続くかどうかは気力次第、がんばれ~

GitHubで編集を提案

Discussion