BLOGブログ

2020.12.02その他

[UE4]GameplayAbilitySystemで「スタミナ」を作る

執筆バージョン: Unreal Engine 4.25

ハローアンリアル!エンジニアの片平です!

前回に引き続き、GameplayAbilitySystem関連の記事です。

前回:[UE4]GameplayAbilitySystemでコンボ攻撃を作る

スタミナつけます

前回はGameplayAbilitySystemを使用して華麗なコンボ攻撃を作成しました。

しかし、これだけだとひたすらコンボで殴るゲームになりイマイチ面白くなさそうです……。
そこで、今回は戦闘の駆け引きを面白くする「スタミナ」システムを実装していきます。もちろん、GameplayAbilitySystemを使って。

「スタミナ」の仕様は以下のようなものとします。

  • 攻撃を繰り出すたびに「スタミナ」を消費する
  • 攻撃に必要な「スタミナ」がないときは攻撃できない
  • 「スタミナ」を消費する行動をしないときに「スタミナ」は自動で回復する
  • 最小値は0.0、最大値は100.0固定
  • プログレスバーで表示する

今回実装するのは「スタミナ」ですが、MPやSPやマナやラスやフューリーやアーケイン・パワーなどの行動するために支払う系(リソース)のパラメータの実装なら今回の実装例を汎用的に使えると思います。

ちなみに筆者はスタミナ丼が大好き

GameplayAbilitySystemとは

GameplayAbiliySystem(以下GAS)については、前回の記事や以下のリンクをご参照ください。

今回は、前回の記事で使用したGameplayAbilityに加え、GameplayAttribute、GameplayEffectも使用します。

GameplayAttributeとは

GameplayAttributeとは、GASで扱う「数値(HP、MP、攻撃力、防御力、スタミナなどのパラメータ)」です。
GASで扱うパラメータはActorのメンバ変数に直接格納するのではなく、AttributeSetというクラス中にGameplayAttributeとして格納します。

GameplayAttributeの値は内部的にはfloat型です。intで扱うような値も全てfloatとして扱ってください。

GameplayEffectとは

GameplayEffectとは、GameplayAbilityによって引き起こされるGameplayAttributeへの「影響」です。

例えば、

  • 攻撃で敵のHPにどれだけのダメージを与えるか
  • バフでどれだけの時間、どれだけ攻撃力を上げるか
  • GameplayAbilityを使うのにどれだけのコストを支払うか

などを設定するクラスです。

詳しく知りたい方は以下の公式ドキュメントもご覧ください。

ゲームプレイ アトリビュートとゲームプレイ エフェクト

スタミナを実装する

プロジェクト

前回記事で作成したBlogComboプロジェクトを引き続き使用します。

筆者は前回作成したプロジェクトを紛失したため、泣きながら自分の記事を見てもう一回作りました。

プレイヤーにパラメータを追加する

スタミナをGameplayAttributeとして宣言します。

AttributeSetを作成する

スタミナのGameplayAttributeを含むAttributeSetを作成します。

BlogComboAttributeSet.h

//アクセサ(Setter、Getter)を生成するためのマクロ
#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)

/**
*
*/
UCLASS()
class BLOGCOMBO_API UBlogComboAttributeSet : public UAttributeSet
{
GENERATED_BODY()

public:
// コンストラクタ
UBlogComboAttributeSet();

//GameplayEffect実行の後に実行される関数
virtual void PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)override;

/*スタミナ*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = “BlogComboAttributes”)
FGameplayAttributeData Stamina;
ATTRIBUTE_ACCESSORS(UBlogComboAttributeSet, Stamina)
};

BlogComboAttributeSet.cpp 

UBlogComboAttributeSet::UBlogComboAttributeSet() :
Stamina(100.f)
{
}

void UBlogComboAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
{
Super::PostGameplayEffectExecute(Data);

if (Data.EvaluatedData.Attribute == GetStaminaAttribute())
{
SetStamina(FMath::Clamp(GetStamina(), 0.f, 100.f));
}
}

PlayerにAttributeSetを追加する

作成したBlogComboAttributeSetをPlayerCharacterに追加します。

キャラクターやカメラの移動処理の記述があり、長くなるため中略します。

BlogComboCharacter.h

UCLASS(config=Game)
class ABlogComboCharacter : public ACharacter, public IAbilitySystemInterface
{

~中略~

public:
/** AttributeSet **/
UPROPERTY()
UBlogComboAttributeSet* BlogComboAttributeSet;

UFUNCTION(BlueprintCallable, BlueprintPure, Category = “BlogComboAttribute”)
virtual float GetStamina();
}

 

BlogComboCharacter.cpp

// AttributeSetを追加
BlogComboAttributeSet = CreateDefaultSubobject(TEXT(“AttributeSet”));

}

~中略~
//スタミナを取得
float ABlogComboCharacter::GetStamina()
{
return BlogComboAttributeSet->GetStamina();
}

~中略~

ここまででコンパイルします。

PlayerCharacterのGetStamina関数で現在のスタミナ値にアクセスすることができます。


初期値である100.0が表示されました。

UIに表示する

数字で見ても増減がイマイチわからないので、プログレスバーとしてUI表示させておきます。

今回の趣旨とは外れるため詳しくは記載しませんが、下のドキュメントなどを参考にちゃちゃっと作りましょう。

2. ヘルス、エネルギー、弾薬を表示する


左上につけました。誰がどう見てもスタミナゲージですね。

Abillityのコストを設定する

GameplayAbilityにコストを追加

今回はAbilityごとにコストを持たせたいので、前回作成したGameplayAbilityを継承したクラスであるBlogComboGameplayAbilityにコスト変数を追加します。

BlogComboGameplayAbility.h

メンバ定義に以下の変数を追加します。

FScalableFloat型は実数以外に、Curveを指定することで実数*Curveの値を得ることができる型です。Curveに何も指定しなければ実数の値がそのまま使用されます。

コンパイルしたら、前回作成したコンボ1~3のGameplayAbilityにCostが追加されているので、それぞれClassDefault->Cost->Costにコストを設定していきます。


とりあえず3段階ともスタミナ30.0消費としておきました。

コスト分だけ消費させる

設定したコスト分だけ消費させる(=GameplayAttributeに影響を与える)には、GameplayEffectを使用します。

GameplayModMagnitudeCalculationの作成

GameplayEffectを作成する前に、コストをどのように計算するかをGameplayModMagnitudeCalculationクラスで実装します。

GMMC_AbilityCost.h

/**
*
*/
UCLASS()
class BLOGCOMBO_API UGMMC_AbilityCost : public UGameplayModMagnitudeCalculation
{
GENERATED_BODY()

float CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const override;
};

 

GMMC_AbilityCost.cpp

float UGMMC_AbilityCost::CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const
{
const UBlogComboGameplayAbility* Ability = Cast(Spec.GetContext().GetAbilityInstance_NotReplicated());

if (!Ability)
{
return 0.0f;
}

return Ability->Cost.GetValueAtLevel(Ability->GetAbilityLevel());
}

今回は単純にGameplayAbilityからCostを取得してくるだけですが、例えば、攻撃力が上昇するにつれて消費コストに補正が入るなど自由な計算を実装することができます。

コスト用GameplayEffectの作成

GameplayEffectを継承したBPを作成します。


GameplayEffect->Modifierの+をクリックし、一つ追加します。

以下の画像のように設定します。(クリックで拡大します。)


デフォルトから変更した項目の解説です

Attribute 変更対象のGameplayAttributeです。
Modifier Op Attributeに対して行う演算方法です。
減算の場合はマイナス値を加算するのでAddです。
MagnitudeCalculationType CustomCalculationClassを選択することで、独自実装したMagnitude※を使用できます。
CalculationClass Magnitudeの計算に使用するGameplayModMagnitudeCalculationクラスを指定します。
Coefficient CalculationClassで算出した値にかける係数です。
-1.0にすることでマイナス値にしています。

※MagnitudeとはAttributeに与える影響の大きさです。
Modifier OpがAddだと単純にMagnitudeの値が加算されます。

コスト用GameplayEffectの設定

コンボ1~3のGameplayAbilityのCosts→CostGameplayEffectClassに作成したコスト用GameplayEffectを設定します。

スタミナ消費完成!

スタミナを消費してコンボを繰り出せるようになりました。

動画ではわかりづらいですが、コストが足りなくなると攻撃ボタンを押してもコンボ攻撃が出せなくなります。

スタミナを自動回復させる

このままでは3発攻撃するとスタミナがなくなり一生攻撃できなくなってしまうので、自動でスタミナが回復するように実装していきます。

自動回復用GameplayEffectの作成

GameplayEffectを継承したBPを作成します。


GameplayEffect->Modifierの+をクリックし、一つ追加します。

以下の画像のように設定します。(クリックで拡大します。)


1回でスタミナが0.5回復回復するように設定しました。
DurationPolicyを「Infinite」にすることで無限にGameplayEffectの効果が繰り返されます。

Period->Periodに0.016を入れます。


これで0.016秒毎にスタミナが増加します。
0.016秒は60fpsでの1フレームの秒数のため、毎フレーム更新することになります。

ネットワークゲームの場合はもっと更新間隔を長くしてUIで補完するなどの実装をしたほうがいいかなと思います。

Tags->OngoingTagRequirements->Ignore Tagsに「Stamina.Cooldown」を入れます。


Ignore Tagsに設定したTagをGameplayAbilityComponentが持っているときにはGameplayEffectは発動しません。

自動回復用GameplayEffectの設定

作成したGameplayEffectをBlogComboCharacterで実行するようにします。

BlogComboCharacterのBeginPlayに以下の画像の赤線のようにノードを繋ぎます。


これで常にスタミナが自動回復するようになりましたが、コンボ攻撃中はスタミナ回復が止まってほしいです。
そこで、コンボ攻撃発動後にスタミナ自動回復にクールタイムを持たせます。

クールタイム用GameplayEffectの作成

クールタイムの設定にもGameplayEffectを使用します。

GameplayEffectを継承したBPを作成します。


GameplayEffetを以下の画像のように設定します。


DurationPolicyを「HasDuration」にすることでGameplayEffectが一定時間継続します。

DurationMagnitude->ScalableFloatMagnituideはGameplayEffectが発生してから消えるまでの時間です。
今回は攻撃をしてから1秒たったらスタミナ自動回復が再開してほしいので1.0にしました。

Tags->GrantedTags->Addedに「Stamina.Cooldown」を設定します。


これでGameplayEffectが実行されているときにGameplayAbilityComponentに「Stamina.Cooldown」が付与されます。

クールタイム用GameplayEffectの設定

コンボ1~3のGameplayAbilityのCommitAbilityの後ろにApplyGameEffectToOwnerを追加し、作成したクールタイム用のGameplayEffectが実行されるようにします。

スタミナ自動回復完成!

以上で、スタミナ自動回復が完成しました!

攻撃中はスタミナが回復せず、1秒以上なにもしないと自動回復します。

攻撃失敗の通知を受け取る

スタミナが足りず攻撃を出せないときは通知を受け取ってメッセージを出すとかビープ音を出すとかしたいですよね。

ちょっと応用的な内容になりますが、攻撃失敗の通知の実装に癖があったのでご紹介します。

通知関数の作成とデリゲートの設定

通知はデリゲートで受け取れます。

BlogComboCharacter.h

メンバ定義に以下の関数を追加します。

BlogComboCharacter.cpp

BeginPlayの実装部分に1行追加します。

if (AbilitySystem)
{
int32 inputID(0);
if (HasAuthority() && AbilityList.Num() > 0)
{
for (auto Ability : AbilityList)
{
if (Ability)
{
AbilitySystem->GiveAbility(FGameplayAbilitySpec(Ability.GetDefaultObject(), 1, inputID++));
}
}
}
AbilitySystem->InitAbilityActorInfo(this, this);
AbilitySystem->AbilityFailedCallbacks.AddUObject(this, &ABlogComboCharacter::OnAbilityFailed);//この行を追加する
}
}

これでデリゲートの設定は完了ですが、GameplayAbility実行失敗時のGameplayTagが設定されていないため、OnAbilityFailedが通知されても引数のGameplayTagsには何も入らないです。

通知タグの作成

GameplayAbility実行失敗時のGameplayTagを作成します。

ProjectSettingsを開き、Project->GameplayTagsページから以下のタグを追加します。

  • Ability.Fail.Cooldown … クールダウン中
  • Ability.Fail.Cost … コストが足りない

通知タグの設定

Game/Config/DefaultGame.iniを開き、以下を追加します。

グローバルデータの更新

上記の設定を反映させるために、GameInstanceの拡張が必要です。

GameInstanceを継承したC++クラスを作成します。

以下のように実装します。

BlogComboGameInstance.h

/**
*
*/
UCLASS()
class BLOGCOMBO_API UBlogComboGameInstance : public UGameInstance
{
GENERATED_BODY()

public:
UBlogComboGameInstance();
};

 

BlogComboGameInstance.cpp

UBlogComboGameInstance::UBlogComboGameInstance()
{
UAbilitySystemGlobals::Get().InitGlobalData();
}

ここまででコンパイルします。

BPで通知を受け取る

OnAbilityFailedイベントから以下の画像のようにノードを組みます。

攻撃失敗通知完成!


コストが足りないときに攻撃を出そうとするとメッセージが表示されます。
地味ですが、意外とこういうの大事ですよね。

まとめ

というわけで、今回はスタミナシステムを実装してみました。

今回ご紹介したGameplayEffectの機能を駆使することで、単純な数値の増減以外にもさまざまなキャラクター制御が実装できるので是非お試しください!