【GAS】依存するパラメータの計算をGameplayEffectで
はじめに
この記事は
https://qiita.com/advent-calendar/2024/ue
シリーズ3の4日目の投稿記事です。
本記事では 「かしこさを上げると最大MPが増える」 といった RPG でよくある
別パラメータに依存するパラメータの変化を
GameplayEffectのAttributeBasedModifiersを用いてやってみよう!といった内容になっています。
| 検証時のバージョン | UE5.5.0 |
| 言語設定 | 英語 |
GameplayEffectとは(以下 GE)
Unreal Engine内でGameplayAbilitySystem(通称:GAS)と呼ばれるフレームワークの機能の1つです。
GASでは、GEを用いることで、以下のようなパラメータの変化を適用できます。
即時に反映される変化
例 : ダメージや回復時間制限付きの変化
例 : バフ/デバフ永続的な変化 ← 本記事はこのパラメータの変化についてです。
例 : ステータスの上昇
事前準備
変動するパラメータは GASの機能の1つであるAttributeSetで定義をします。
AttributeSetについては、本記事では割愛します。
今回必要なパラメータは2つで、以下のように定義しました。
UCLASS()
class MYBLOG_API UMyBlogAttributeSet : public UAttributeSet
{
GENERATED_BODY()
public:
UMyBlogAttributeSet();
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
// かしこさ
UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_Int)
FGameplayAttributeData Int;
ATTRIBUTE_ACCESSORS(UMyBlogAttributeSet, Int);
// 最大MP
UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_MaxMP)
FGameplayAttributeData MaxMP;
ATTRIBUTE_ACCESSORS(UMyBlogAttributeSet, MaxMP);
UFUNCTION()
void OnRep_Int( FGameplayAttributeData const& OldHealth ) const;
UFUNCTION()
void OnRep_MaxMP( FGameplayAttributeData const& OldMaxHealth ) const;
};
Attribute Based Modifierとは
GEにはModifiersという変数があり、指定したパラメータの値をどう変化させるかを設定します。
Modifiers を追加した直後の Magnitude Calculation Type は Scalable Float になっています。
これは指定した数値をAttributeの値に対して計算を行うようになっています。
Modifier Op によって計算方法が変わります。(四則演算や代入)

Attribute Based Modifer は、この計算を別のパラメータの値を用いて計算を行うものになります。
こちらを選択すると添付のような設定項目に切り替わります。

依存するパラメータ
まず依存するパラメータの設定ですが、BackingAttribute がそれにあたります。
| 変数名 | 説明 |
|---|---|
| AttributeCapture | 依存されるパラメータ( 例 : かしこさ ) |
| AttributeSource | 値を取得する先 ( 例 : 誰のかしこさか ) Source : GEを適用する処理を呼んだ人 Target : GEを適用された人 |
| Snapshot | 適用されたタイミングのパラメータの値を計算に使いつづけるか (例 : 時間制限付きor永続的なGEでtrueの場合 適用されたタイミングで、かしこさが5であれば 適用期間内で、かしこさが増えても計算に用いられる値は5となる) |
パラメータを用いた計算式
続いて、このパラメータを用いた計算になりますが、
必要な設定項目は以下になります。
| 変数名 | 説明 |
|---|---|
| Coefficient (以下 : C) | 依存するパラメータの影響度 |
| PreMultiplyAdditiveValue (以下 : Pre) |
計算のはじめに依存するパラメータに加算する数値 |
| PostMultiplyAdditiveValue (以下 : Post) |
計算の最後に加算する数値 |
これらを用いた計算式が以下です。
例
表題の「かしこさをあげると最大MPが増える」にもう少し情報を加え、
最大MPの初期値は50、かしこさの初期値は1で、1上げると最大MPが2.5増える とすると

| かしこさ | 1 |
| C | 2.5 |
| Pre | -1 |
| Post | 50 |
上記の設定にすることで...
のように最大MPの初期値が表題の通りになりますね。
そして、かしこさが1あがると...
のように最大MPが2.5増えるのが確認できますね。
カーブテーブルを用いた計算
Attribute Curve を設定していると、かしこさの値を横軸とした
カーブの縦軸の値を計算に用いることができます。
まとめ
以上、GameplayEffectを用いた
別パラメータに依存するパラメータの変化についての説明でした。
知ったときに「めっちゃ便利!」と思って記事にしてみました。
本記事が少しでもお役に立てたなら幸いです。
不明な点や間違ってる点などありましたら、連絡をいただければ幸いです。
参考
Attributeの定義はこちらの記事を参考に定義しました。
zenn.dev
SetFocus 〜あなただけ見つめてる〜
この記事は
Unreal Engine (UE) - Qiita Advent Calendar 2023 - Qiita
シリーズ3の13日目の投稿記事です。
検証時のバージョン: UE5.2.0
はじめに
今まで敵キャラクターがプレイヤーに体を向けて周囲をぐるぐる移動する処理を作る際、
向きを合わせるという処理を、ビヘイビアツリーのサービスを作成し、
角度計算と向きの設定を行う処理を記述していました。
UE5のゲームサンプルであるLyraStarterGameを見ていると、
サービス内にそのような処理を記述せずとも、AIControllerのSetFocusという処理を用いることで
同様のことができることがわかりました。
今回はSetFocusの概要と使い方についてまとめてみました。
SetFocusとは

SetFocusはAIControllerが特定のアクターに注意を向けるための処理です。
使い方は簡単で、引数のNewFocusに注意を向けたいアクターを指定するだけです。
処理の流れ
設定したアクターの情報は、AAIController::UpdateControlRotation内で利用されます。
この処理では、AIControllerが操作しているポーンから対象への角度計算が行われ、
操作しているポーンのAPawn::FaceRotationに渡すことで、向きを設定します。
※ポーンのUseControllerRotationYawに
チェックを入れていないと、向きの設定は反映されません。
プレイヤーを対象とした例
以下の動画は、SetFocusを使用してプレイヤーの周囲をぐるぐる移動させる様子です。
自分で実装した部分は、プレイヤー周辺を移動する処理のみとなります。
ブログ用 pic.twitter.com/Wf7cIxceld
— goolee (@goolee07) 2023年5月31日
アクター以外を注目したい場合

AAIController::SetFocalPoint の処理があり、特定アクターではなく、
特定座標へ注目させることもできます。
まとめ
SetFocusはAIControllerが特定のアクターや座標に注意を向けるための便利な処理です。
簡単な使い方と流れをまとめました。
不明な点や間違ってる点などありましたら、連絡をいただければ幸いです。
明日は mokoさんの UE5 エミッシブの値を露出に関わらず一定に保つ です。 お楽しみに!
3次元の経路探索 - データ作成編
この記事は qiita.comUnreal Engine 4(UE4) #1 Advent Calendar 2020
の23日目の投稿記事です。
ごりです。久々の投稿になります。
3次元の経路探索を何回かに分けて書かせていただきます。
今回はデータの作成編です。
はじめに
この記事は、下記を参考に作成したものになります。
誤っている点などございましたら、連絡いただけると助かります。
サンプルプロジェクト
下記で配布しています。
drive.google.com
バージョン
ソースコードも含めていますので、ビルド環境が必要となります。
以下の環境で作成しました。
UE4 Ver.4.25.4
Microsoft Visual Studio Community 2019 Version 16.8.3
使い方
プロジェクトを開くとSVOBoundsVolumeが置かれたレベルが開きます。
詳細タブのカテゴリ【SVO】で階層数を指定し、Generateボタンを押すと、SVOが構築されます。

カテゴリ【デバッグ】ではそれぞれチェックを入れることで可視化することができます。

全てにチェックをいれると、このようになります。

SVO( Sparse Voxel Octrees )
SVOはライティングやレイトレーシングで使用される一般的なデータ構造となっています。
基本的な作りは八分木で、各ツリーのボリュームを8つに階層的に分割することで、
高速な位置検索を可能としています。
SVOはリーフノード、ノード、リンクの3つの要素で構築できます。
リーフノード
SVOでのリーフノードは通常の八分木とは扱いが異なり、
最下層(レイヤー0)のノードのことを指します。
リーフノードは衝突or空き領域だけを考慮しているので、
必要なデータは状態を表す1ビットのみとなります。
リーフノードは通常のノードとは異なり、
64個の場所を表すノードで、サブノードと呼ばれます。
上層のノードはこのリーフノードを元に構築されます。
USTRUCT(BlueprintType) struct FSVOLeafNode { //! サブノードインデックスリスト int64 mSubNodeIndices; };
ノード
ツリーのノードには下記が含まれます。
・空間上の位置を知るための位置情報
・親ノードへのリンク
・最初の子ノードへのリンク
・ノード間を移動できるように隣接ノードへのリンク
子ノードへのリンクが最初の子だけで済むのは、
ノードがモートンコード順に格納されているため、
0~7をオフセットして子ノードへ移動することができます。
子ノードへのリンクが無効の場合、このノードにはボクセルが含まれていないと判断できます。
USTRUCT(BlueprintType) struct FSVONode { //! 位置情報 FVector mLocation; //! 親ノードへのリンク FSVOLink mParent; //! 最初の子へのリンク FSVOLink mFirstChild; //! 隣接リンク TArray< FSVOLink > mNeighbours; };
隣接リンク
隣接ノードへのリンクはポインタを使用すると、32ビットと64ビットOSで
データサイズが大きく変化してしまうので、メモリ使用量制御のため、
ポインタの代わりに配列へのオフセットが使用されます。
リンクは経路探索でも使用されるので、同じ階層のノードだけでなく、
上下の層を行き来できるように階層番号とノード番号が必要になります。
また、リーフノードは64個の場所を表すノード(サブノードと呼ばれています)で、
サブノード番号もリンクに必要になります。
この3つの情報は32ビットの整数に変換して格納されます。
USTRUCT(BlueprintType) struct FSVOLink { //! レイヤーインデックス( 0 ~ 15 ) int32 mLayerIndex : 4; //! ノードインデックス( 0 ~ 4194303 ) int32 mNodeIndex : 22; //! サブノートインデックス( 0 ~ 63 ) //! リーフノード内のボクセルインデックスにのみ使用 int32 mSubNodeIndex : 6; };
構築手順
1層分のSVOの構築手順は下図のようになっており、
リーフノードを生成し、それをもとに上層のノードを生成、
最後に均一なノードを削除してSVOが構築されます。

リーフノードの生成
リーフノードは最下層から更に2階層細かくボリュームを分割し、ボリューム内に
コリジョンボクセルが含まれているかどうかを64ビットの値に格納していきます。
ここで、使用しているUSVOSystemLibrary::BoxOverlapActors は
UKismetSystemLibrary::BoxOverlapActorsを、
回転値をオーバーラップ判定に用いれるように変更しています。
void ASVOBoundsVolume::GenerateLeafNodes(){ /** リーフノード数を計算 **/ int32 NodeNum = GetNodeNum(SVO::LAYER_LEAF); /** リーフノードのボクセルサイズを取得 **/ FVector VoxelSize = GetVoxelSizeInLayer(SVO::LAYER_LEAF); /** 障害物判定 **/ TArray<TEnumAsByte<EObjectTypeQuery>> ObjectTypes = { EObjectTypeQuery::ObjectTypeQuery1, //! WorldStatic }; TArray<AActor*> IgnoreActors = { this }; TArray<AActor*> OverlapActors; for (int32 i = 0; i < NodeNum; i++) { int32 LeafNodeIndex = i >> SVO::BIT_LEAFLAYER; if (!mLeafNodes.Contains(LeafNodeIndex)) { mLeafNodes.Add(LeafNodeIndex, FSVOLeafNode()); } /** 指定階層のノード座標を取得 **/ FVector Location = GetNodeLocationInLayer(SVO::LAYER_LEAF, i); /** コリジョンがノード内に存在するか判定 **/ if (USVOSystemLibrary::BoxOverlapActors( this, Location, VoxelSize * 0.5f, GetActorRotation(), ObjectTypes, nullptr, IgnoreActors, OverlapActors )) { mLeafNodes[LeafNodeIndex].SetBlock(i & SVO::BIT_LEAFNODEINDEX); } } }
各層のノードの生成
各層のノードは先程生成したリーフノードをもとに
下層から上層に向けて、生成していきます。
リーフノードの値が0以外の場合、リーフノードには
コリジョンが含まれているということがわかります。
このときに、上層のノードへリンクづけを行います。
void ASVOBoundsVolume::GenerateLayerNodes() { /** 指定層分の領域確保 **/ mNodeList.SetNum(mNumOfLayers+1); for (auto LeafNode : mLeafNodes) { int32 CurrentLayer = 0; while (CurrentLayer <= mNumOfLayers) { /** 子の階層とインデックスを計算 **/ int32 ChildLayer = FMath::Max(0, CurrentLayer - 1); int32 ChildIndex = LeafNode.Key >> (SVO::BIT_LAYER * ChildLayer); if (CurrentLayer != 0) { /** 最下層でない場合は自身へのリンクを子ノードに設定する **/ int32 ParentIndex = LeafNode.Key >> (SVO::BIT_LAYER * CurrentLayer); mNodeList[CurrentLayer - 1][ChildIndex].SetParent(FSVOLink(CurrentLayer, ParentIndex)); } int32 NodeIndex = LeafNode.Key >> (SVO::BIT_LAYER * CurrentLayer); if (!mNodeList[CurrentLayer].Contains(NodeIndex)) { FSVONode Node; Node.SetLocation(GetNodeLocationInLayer(CurrentLayer, NodeIndex)); mNodeList[CurrentLayer].Add(NodeIndex, Node); } if (!mNodeList[CurrentLayer][NodeIndex].HasAnyChildren()) { if (!LeafNode.Value.IsOpen()) { FSVOLink Child(ChildLayer, ChildIndex); if (CurrentLayer == 0) { /** 最下層のノードが均一でない場合はリーフノードの最初のサブノートインデックスを最初の子に設定 **/ int32 SubNodeIndex = LeafNode.Key << SVO::BIT_LEAFNODEINDEX; Child.SetSubNodeIndex(SubNodeIndex); } mNodeList[CurrentLayer][NodeIndex].SetFirstChild(Child); } } CurrentLayer++; } } }
隣接ノードのリンクづけ
上層までノードが生成されたので、次は上層から下層の順に
隣接ノードのリンクづけをしていきます。
この時点では均一なノードは削除していないので、ノードリストに含まれていない
モートンコードはボリューム外として、処理をスキップします。
均一なノード(子を持たないノード)だった場合は上層も均一かどうか判定し、
均一だった場合は上層のノードを隣接ノードとしてリンクを生成していきます。
void ASVOBoundsVolume::GenerateNeighbourLink() { int32 CurrentLayer = mNumOfLayers; while ( CurrentLayer >= 0 ) { for (auto& Node : mNodeList[CurrentLayer]) { /** モートンコードを1度座標に戻す **/ FVector Location = Morton::Decode( Node.Key ); for (int32 i = 0; i < SVO::Directions.Num(); i++) { /** 上下左右前後の6方向の座標でモートンコードに変換 **/ int32 NeighborIndex = Morton::Code( Location + SVO::Directions[i] ); FSVOLink Neighbour; if (FindNeighbour(CurrentLayer, NeighborIndex, Neighbour )) { Node.Value.AddNeighbour( Neighbour ); } } } CurrentLayer--; } } bool ASVOBoundsVolume::FindNeighbour(int32 CurrentLayer, int32 NeighbourIndex, FSVOLink& Neighbour) { /** 隣接ノード情報をクリア **/ Neighbour.Clear(); if (!mNodeList[CurrentLayer].Contains(NeighbourIndex)) { /** 領域外の場合スキップ **/ return false; } Neighbour.SetLayerIndex(CurrentLayer); Neighbour.SetNodeIndex(NeighbourIndex); if (mNodeList[CurrentLayer][NeighbourIndex].HasAnyChildren()) { if (CurrentLayer == 0 && mLeafNodes[NeighbourIndex].IsClosed()) { return false; } /** ノードが均一でない場合は、ここでストップ **/ return true; } /** 均一な場合、均一な上層ノードを探索 **/ int32 Layer = CurrentLayer+1; int32 Index = NeighbourIndex >> SVO::BIT_LAYER; while ( Layer <= mNumOfLayers ) { if (!mNodeList[CurrentLayer][Index].HasAnyChildren()) { Neighbour.SetLayerIndex(Layer); Neighbour.SetNodeIndex(Index); } Layer++; Index = Index >> SVO::BIT_LAYER; } return true; }
均一ノードの削除
隣接ノードへのリンクを生成したら、最後に下層から上層に向けて
均一なノードの子ノードを削除していきます。
void ASVOBoundsVolume::Rasterize() { int32 CurrentLayer = 1; while ( CurrentLayer <= mNumOfLayers) { for ( auto Node : mNodeList[CurrentLayer] ) { if ( Node.Value.HasAnyChildren()) { continue; } /** 均一なノードの場合、子ノードは不要なので削除 **/ for (int32 i = 0; i < SVO::NUM_CHILDREN; i++) { int32 RemoveNodeIndex = Node.Key << SVO::BIT_LAYER | i; mNodeList[CurrentLayer-1].Remove(RemoveNodeIndex); if (CurrentLayer-1 == 0) { /** 最下層の場合はリーフノードも削除 **/ mLeafNodes.Remove(RemoveNodeIndex); } } } CurrentLayer++; } }
まとめ
ここまで見ていただきありがとうございました。
昔に作ったプロジェクトを引っ張り出してきて確認してみたら、
リーフノードの部分完全に抜けており、
「あれ?これ全然違うな。」となり、大慌てで作り直してました。
続編については年内に投稿予定です。
明日は @nokonoko_08 さんの記事になります。
果たして上司に脅されずに済んだのでしょうか。
ゲーム内の1日のサイクルをつくる
- はじめに
- サンプルプロジェクト
- バージョン
- 実装解説
- DayCycleGameStateBase.h
- DayCycleGameStateBase.cpp
- W_DayCycle
- BP_DayCycleSky
- まとめ
はじめに
シミュレーションゲーム等で
必要となってくる、ゲーム内のサイクルの構築について説明いたします。
サンプルプロジェクト
GooglDriveにてサンプルプロジェクトを配布しています。
作りに不明な点ありましたらご連絡ください。
バージョン
ソースコードも含めていますので、ビルド環境が必要となります。
以下の環境で作成しました。
UE4 Ver.4.24.0
Microsoft VisualStudio Community 2017 - Ver.15.9.5
ウェイポイントの経路探索をA*アルゴリズムでやってみました。
この記事はUnreal Engine 4(UE4) #2 Advent Calendar 2019の15日目の投稿記事です。 qiita.com
ごりです。
久々の投稿で、初のアドカレ参加となります。
わかりにくいところ等ありましたら
連絡をいただけると助かります。
- はじめに
- サンプルプロジェクト作成しました
- バージョン
- 動かしてみる
- 実装解説
- CityData.h/cpp
- ノードの定義
- ノードの管理
- WayPoint.h/cpp
- PathFindComponent.h/cpp
- アルゴリズムの流れ
- CityData.h/cpp
- まとめ
はじめに
A*アルゴリズムとは、探索アルゴリズムの一種です。
スタートノード(開始地点)からゴールノード(目標地点)までの経路を計算し、
この経路が最短であることを保証するアルゴリズムとなります。
今回はレベルに配置するウェイポイントをノードとして
A*アルゴリズムの経路探索を行っていきます。
GameplayAbility - GameplayAbility と コンポーネント の準備編
こんばんは、ごりです。
今日は、こちらの記事の続きからです。
goolee.hatenablog.com
今回は、実際に使う GameplayAbility と Component のクラスを作っていきます。

コンテンツブラウザの 新規C++クラスをクリック。
全てのクラスを表示にチェックし、 GameplayAbility と打ちます。

GameplayAbility を選択し、次へ。
ファイル名は 「GameplayAbilityBase」 とします。
続いて、新規C++クラスから 「AbilitySystemComponent」と打ちます。
ファイル名は 「AbilitySystemComponentBase 」とします。

作成が終わったら、
Component に処理を追加していきます。
今回もソースコードを記述します。
まず AbilitySystemComponentBase.h
/** *@file AbilitySystemComponentBase.h *@brief GameplayAbility を用いるためのComponent *@author goolee *@date 2018/12/01 */ #pragma once #include "CoreMinimal.h" #include "AbilitySystemComponent.h" #include "GameplayTagContainer.h" #include "AbilitySystemComponentBase.generated.h" class UGameplayAbilityBase; /** * GameplayAbility を扱う Component * ゲーム中に指定したデータを扱うために、AbilitySystemComponent を拡張 */ UCLASS() class GPASTUDY_API UAbilitySystemComponentBase : public UAbilitySystemComponent { GENERATED_BODY() public: /** *@fn *コンストラクタ */ UAbilitySystemComponentBase(); /** *@fn *現在実行中の Ability の中から指定した GameplayTag と一致するものすべて取得 *@param (GameplayTagContainer) 指定するGameplayTagの集まり *@return (ActiveAbilities) 指定したタグと一致したAbility */ void GetActiveAbilitiesWithTags( const FGameplayTagContainer& GameplayTagContainer, TArray<UGameplayAbilityBase*>& ActiveAbilities ); };
つづいて AbilitySystemComponent.cpp
#include "AbilitySystemComponentBase.h" #include "GameplayAbilityBase.h" UAbilitySystemComponentBase::UAbilitySystemComponentBase() {} void UAbilitySystemComponentBase::GetActiveAbilitiesWithTags(const FGameplayTagContainer& GameplayTagContainer, TArray<UGameplayAbilityBase*>& ActiveAbilities) { TArray<FGameplayAbilitySpec*> AbilitiesToActivate; GetActivatableGameplayAbilitySpecsByAllMatchingTags( GameplayTagContainer, AbilitiesToActivate, false ); // Iterate the list of all ability specs for (FGameplayAbilitySpec* Spec : AbilitiesToActivate) { // Iterate all instances on this ability spec TArray<UGameplayAbility*> AbilityInstances = Spec->GetAbilityInstances(); for (UGameplayAbility* ActiveAbility : AbilityInstances) { ActiveAbilities.Add(Cast<UGameplayAbilityBase>(ActiveAbility)); } } }
GameplayAbilityBase には今回は処理を追加しません。
ではこのComponentとAbility をキャラクターに持たせて、
GameplayAbilityを使えるようにしていきます。
CharacterBase.h と CharacterBase.cpp です。
/** *@file CharacterBase.h *@brief GameplayAbility を用いる キャラクターの基底クラス *@author goolee *@date 2018/12/01 */ #pragma once #include "CoreMinimal.h" #include "GameFramework/Character.h" #include "AttributeSetBase.h" #include "AbilitySystemInterface.h" #include "AbilitySystemComponentBase.h" #include "CharacterBase.generated.h" UCLASS() class GPASTUDY_API ACharacterBase : public ACharacter, public IAbilitySystemInterface { GENERATED_BODY() public: /** * @fn * コンストラクタ */ ACharacterBase(); /** *@fn *コントローラ所有された際の処理 *@param このキャラクターを所有するコントローラ * *前回、この処理書いてなかったです、すみません。 */ virtual void PossessedBy(AController* NewController) override; protected: /** * GameplayAbilityを扱うコンポーネント */ UPROPERTY() UAbilitySystemComponentBase* AbilitySystem; /** * キャラクターのステータス * ブループリントから呼び出しはできないが、 * ガベージコレクションに追加するため UPROPERTY() を記述 */ UPROPERTY() UAttributeSetBase* AttributeSet; /** *キャラクター生成時から実行可能なAbility */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Abilities") TArray<TSubclassOf<UGameplayAbilityBase>> Abilities; /** * @fn * ゲーム開始時や、生成時に呼ばれる処理 */ virtual void BeginPlay() override; /** * @fn * キャラクターの体力を取得 * @return キャラクターの体力 */ UFUNCTION(BlueprintCallable) virtual float GetHealth() const; public: /** * @fn * 毎フレーム呼ばれる処理 */ virtual void Tick(float DeltaTime) override; /** *@fn *AbilitySystemComponentを取得 *@return AbilitySystemComponent */ UAbilitySystemComponent* GetAbilitySystemComponent() const override; /** *@fn *指定したタグと一致するAbilityを実行 *@param (AbilityTags) 実行するAilityがもつGameplayTag *@param (bAllowRemoteActivation) true...ローカル/サーバーで実行 false...ローカルでのみ実行 *@return true...実行に成功 false...失敗 */ UFUNCTION(BlueprintCallable, Category = "Abilities") bool ActivateAbilitiesWithTags(FGameplayTagContainer AbilityTags, bool bAllowRemoteActivation = true); /** *@fn *キャラクターが現在実行中の Ability の中から指定した GameplayTag と一致するものすべて取得 *@param (AbilityTags) 指定するGameplayTagの集まり *@return (ActiveAbilities) 指定したタグと一致したAbility */ UFUNCTION(BlueprintCallable, Category = "Abilities") void GetActivateAbilitiesWithTags( FGameplayTagContainer AbilityTags, TArray<UGameplayAbilityBase*>& ActiveAbilities); };
#include "CharacterBase.h" #include "GameplayAbilityBase.h" ACharacterBase::ACharacterBase(){ //trueにすると毎フレームTick関数を呼び出す設定 PrimaryActorTick.bCanEverTick = true; //AbilitySystemConponentの生成 AbilitySystem = CreateDefaultSubobject<UAbilitySystemComponentBase>(TEXT("AbilitySystem")); //マルチプレイヤー用の設定 AbilitySystem->SetIsReplicated(true); //AttributeSet の生成 AttributeSet = CreateDefaultSubobject<UAttributeSetBase>(TEXT("AttributeSet")); } void ACharacterBase::PossessedBy(AController* NewController) { Super::PossessedBy( NewController ); if (AbilitySystem) { //AbilitySytemを持つ Actor情報と このAbilitySystemで動く Actor情報の初期化 AbilitySystem->InitAbilityActorInfo(this, this); } } void ACharacterBase::BeginPlay() { Super::BeginPlay(); /** * AbilitySytemに使えるAbilityを登録 * これをしないとTagでAbilityを指定しても実行されない。 */ if (AbilitySystem) { for (auto Ability : Abilities) { AbilitySystem->GiveAbility(FGameplayAbilitySpec(Ability, 1, INDEX_NONE, this)); } } } void ACharacterBase::Tick(float DeltaTime){ Super::Tick(DeltaTime); } float ACharacterBase::GetHealth() const { return AttributeSet->GetHealth(); } UAbilitySystemComponent* ACharacterBase::GetAbilitySystemComponent() const { return AbilitySystem; } bool ACharacterBase::ActivateAbilitiesWithTags(FGameplayTagContainer AbilityTags, bool bAllowRemoteActivation) { if (AbilitySystem) { return AbilitySystem->TryActivateAbilitiesByTag( AbilityTags, bAllowRemoteActivation ); } return false; } void ACharacterBase::GetActivateAbilitiesWithTags(FGameplayTagContainer AbilityTags, TArray<UGameplayAbilityBase*>& ActiveAbilities) { if (AbilitySystem) { AbilitySystem->GetActiveAbilitiesWithTags( AbilityTags, ActiveAbilities ); } }
ここまで出来たら、ビルドして BP_CharacterBase を見てみましょう。
コンポーネントに AbilitySystemが追加され、

デフォルトの詳細に、Abilitiesが増えているのがわかりますね。

これで GameplayAbilityを使う準備は整いました。
次回から使い方を書いていきたいと思います。
その都度、機能拡張をしていきます。
ソースコードのコメントに、説明書きましたが、
不明な点があれば、ご連絡ください。
ではでは。
GameplayAbility - AttributeSet の準備編
こんばんは、ごりです。
今日はこちらの記事の続きからです。
goolee.hatenablog.com
今回は GameplayAbility を使う上で必要となってくるパラメータをまとめたクラスの準備をしていきます。
UE4サンプルの ActionRPG では キャラクターのステータスとして使っています。
前回作成した GPAStudy プロジェクトを起動して、 新規C++クラスを追加します。

右上の 全てのクラスを表示にチェックをして AttributeSet と検索します。

「AttributeSet」を選択して次へをクリック。
ファイル名は 「AttributeSetBase」 とします。
今回はソースコードを記述していきます。
説明はコメントで省略いたします。
まず AttributeSetBase.h です。
/** *@file AttributeSetBase.h *@brief GameplayAbility で用いるパラメータの集合 *@author goolee *@date 2018/12/01 */ #pragma once #include "CoreMinimal.h" #include "AttributeSet.h" #include "AbilitySystemComponent.h" #include "AttributeSetBase.generated.h" /** * @def * AttributeSet.h で定義されている * Attribute への Setter, Getter を定義するためのマクロ */ #define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \ GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \ GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \ GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \ GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName) /** * @class * GameplayAbilityで用いるキャラクターのパラメータをまとめたクラス */ UCLASS() class GPASTUDY_API UAttributeSetBase : public UAttributeSet { GENERATED_BODY() public: /** * @fn * コンストラクタ */ UAttributeSetBase(); /** * Blueprintから読み取り可能な キャラクターの体力を表す変数 * マクロで Setter,Getterを定義しているので、 SetHealth(), GetHealth()が呼び出し可能 */ UPROPERTY(BlueprintReadOnly, Category="Health", ReplicatedUsing = OnRep_Health) FGameplayAttributeData Health; ATTRIBUTE_ACCESSORS( UAttributeSetBase, Health ) /** * @fn * レプリケートされるAttributeSetの変数を取得する * @brief この関数の中で DOREPLIFTIME マクロを用いてレプリケートされた変数として追加する * @return (OutLifeTimeProps) */ virtual void GetLifetimeReplicatedProps( TArray<FLifetimeProperty>& OutLifetimeProps ) const override; protected: /** * @fn * Healthの値が変更された際に呼び出される処理 */ UFUNCTION() virtual void OnRep_Health(); };
つづいて、 AttributSet.cpp です。
#include "AttributeSetBase.h" #include "Net/UnrealNetwork.h" UAttributeSetBase::UAttributeSetBase() : Health(10.0f){ } void UAttributeSetBase::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const { Super::GetLifetimeReplicatedProps(OutLifetimeProps); DOREPLIFETIME( UAttributeSetBase, Health ); } void UAttributeSetBase::OnRep_Health() { GAMEPLAYATTRIBUTE_REPNOTIFY(UAttributeSetBase, Health); }
GetLifetimeReplicatedProps() と OnRepHealth() は マルチプレイヤーゲームを
作る際に必要となる処理です。
ブログを読んでいる方でマルチプレイヤーゲームを作りたい方は
こちらを記述してください。
そうでない方は、この二つの関数を記述していなくても大丈夫です。
間違ってたら連絡ください...
ではこのAttributeSetをキャラクターに持たせていきます。
前回作成した CharacterBase クラスに書いていきます。
まず CharacterBase.h です。
/** *@file CharacterBase.h *@brief GameplayAbility を用いる キャラクターの基底クラス *@author goolee *@date 2018/12/01 */ #pragma once #include "CoreMinimal.h" #include "GameFramework/Character.h" #include "AttributeSetBase.h" #include "CharacterBase.generated.h" UCLASS() class GPASTUDY_API ACharacterBase : public ACharacter { GENERATED_BODY() public: /** * @fn * コンストラクタ */ ACharacterBase(); protected: /** * キャラクターのステータス * ブループリントから呼び出しはできないが、 * ガベージコレクションに追加するため UPROPERTY() を記述 */ UPROPERTY() UAttributeSetBase* AttributeSet; /** * @fn * ゲーム開始時や、生成時に呼ばれる処理 */ virtual void BeginPlay() override; /** * @fn * キャラクターの体力を取得 *@return キャラクターの体力 */ UFUNCTION(BlueprintCallable) virtual float GetHealth() const; public: /** * @fn * 毎フレーム呼ばれる処理 */ virtual void Tick(float DeltaTime) override; };
キャラクターに AttributeSetBase 型の変数と
AttributeSet内の体力を取得する関数を宣言しました。
つづいて CharacterBase.cpp です。
#include "CharacterBase.h" ACharacterBase::ACharacterBase() { //trueにすると毎フレームTick関数を呼び出す設定 PrimaryActorTick.bCanEverTick = true; //AttributeSet の生成 AttributeSet = CreateDefaultSubobject<UAttributeSetBase>(TEXT("AttributeSet")); } void ACharacterBase::BeginPlay() { Super::BeginPlay(); } void ACharacterBase::Tick(float DeltaTime) { Super::Tick(DeltaTime); } float ACharacterBase::GetHealth() const { return AttributeSet->GetHealth(); }
今回ソースコードに追加するのはここまでです。
ここまで書いたらビルドしてプロジェクトに戻ってください。
コンテンツブラウザの C++クラスフォルダ内の CharacterBase を右クリックして
「CharacterBaseに基づくブループリントクラスを作成します」をクリック。
名前は 「BP_CharacterBase」とします。
フォルダは わかりやすくコンテンツ直下にします。

作成されると、BP_CharacterBaseが開かれると思います。
イベントグラフで 右クリックして GetHealth と打ってみましょう。

無事にでてきましたね。
BeginPlay から printString で 値の確認をしてみましょう。

起動時の ThirdPersonExampleMap に追加して、実行してみます。

上の写真のように表示されればOKです。
これでAttributeSetの準備が完了しました。
今回はここまでとします。
次回からは GameplayAbility を使うためのコンポーネントを準備していきます。
今日の内容でわからないことがあればご連絡ください。
ではでは。