関連ブログ
- [UEFN][UE5]Verseの便利な機能 – 文と式の区別はないってどういうこと? – 2024.08.28その他
- [UE5]UE5でMIDIが使える!「Harmonix」プラグインでプレイヤーアクションに応じたメロディ再生 2024.06.05その他
- [UEFN]Volumeを好きな形に設定する方法 2024.05.01その他
CATEGORY
2020.12.02その他
執筆バージョン: Unreal Engine 4.25 |
ハローアンリアル!エンジニアの片平です!
前回に引き続き、GameplayAbilitySystem関連の記事です。
前回:[UE4]GameplayAbilitySystemでコンボ攻撃を作る
前回はGameplayAbilitySystemを使用して華麗なコンボ攻撃を作成しました。
しかし、これだけだとひたすらコンボで殴るゲームになりイマイチ面白くなさそうです……。
そこで、今回は戦闘の駆け引きを面白くする「スタミナ」システムを実装していきます。もちろん、GameplayAbilitySystemを使って。
「スタミナ」の仕様は以下のようなものとします。
今回実装するのは「スタミナ」ですが、MPやSPやマナやラスやフューリーやアーケイン・パワーなどの行動するために支払う系(リソース)のパラメータの実装なら今回の実装例を汎用的に使えると思います。
ちなみに筆者はスタミナ丼が大好き
GameplayAbiliySystem(以下GAS)については、前回の記事や以下のリンクをご参照ください。
今回は、前回の記事で使用したGameplayAbilityに加え、GameplayAttribute、GameplayEffectも使用します。
GameplayAttributeとは、GASで扱う「数値(HP、MP、攻撃力、防御力、スタミナなどのパラメータ)」です。
GASで扱うパラメータはActorのメンバ変数に直接格納するのではなく、AttributeSetというクラス中にGameplayAttributeとして格納します。
GameplayAttributeの値は内部的にはfloat型です。intで扱うような値も全てfloatとして扱ってください。
GameplayEffectとは、GameplayAbilityによって引き起こされるGameplayAttributeへの「影響」です。
例えば、
などを設定するクラスです。
詳しく知りたい方は以下の公式ドキュメントもご覧ください。
前回記事で作成したBlogComboプロジェクトを引き続き使用します。
筆者は前回作成したプロジェクトを紛失したため、泣きながら自分の記事を見てもう一回作りました。
スタミナをGameplayAttributeとして宣言します。
スタミナのGameplayAttributeを含むAttributeSetを作成します。
BlogComboAttributeSet.h
1 2 3 4 |
#include "CoreMinimal.h" #include "AttributeSet.h" #include "AbilitySystemComponent.h" #include "BlogComboAttributeSet.generated.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
1 2 3 |
#include "BlogComboAttributeSet.h" #include "GameplayEffect.h" #include "GameplayEffectExtension.h" |
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));
}
}
作成したBlogComboAttributeSetをPlayerCharacterに追加します。
キャラクターやカメラの移動処理の記述があり、長くなるため中略します。
BlogComboCharacter.h
1 2 3 4 5 |
#include "CoreMinimal.h" #include "GameFramework/Character.h" #include "AbilitySystemInterface.h" #include "BlogComboAttributeSet.h" //追加する #include "BlogComboCharacter.generated.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
1 2 3 4 |
~中略~ ABlogComboCharacter::ABlogComboCharacter() { ~中略~ |
// AttributeSetを追加
BlogComboAttributeSet = CreateDefaultSubobject(TEXT(“AttributeSet”));
}
~中略~
//スタミナを取得
float ABlogComboCharacter::GetStamina()
{
return BlogComboAttributeSet->GetStamina();
}
~中略~
ここまででコンパイルします。
PlayerCharacterのGetStamina関数で現在のスタミナ値にアクセスすることができます。
数字で見ても増減がイマイチわからないので、プログレスバーとしてUI表示させておきます。
今回の趣旨とは外れるため詳しくは記載しませんが、下のドキュメントなどを参考にちゃちゃっと作りましょう。
今回はAbilityごとにコストを持たせたいので、前回作成したGameplayAbilityを継承したクラスであるBlogComboGameplayAbilityにコスト変数を追加します。
BlogComboGameplayAbility.h
メンバ定義に以下の変数を追加します。
1 2 3 |
/** (追加)アビリティコスト*/ UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Cost") FScalableFloat Cost; |
FScalableFloat型は実数以外に、Curveを指定することで実数*Curveの値を得ることができる型です。Curveに何も指定しなければ実数の値がそのまま使用されます。
コンパイルしたら、前回作成したコンボ1~3のGameplayAbilityにCostが追加されているので、それぞれClassDefault->Cost->Costにコストを設定していきます。
設定したコスト分だけ消費させる(=GameplayAttributeに影響を与える)には、GameplayEffectを使用します。
GameplayEffectを作成する前に、コストをどのように計算するかをGameplayModMagnitudeCalculationクラスで実装します。
GMMC_AbilityCost.h
1 2 3 |
#include "CoreMinimal.h" #include "GameplayModMagnitudeCalculation.h" #include "GMMC_AbilityCost.generated.h" |
/**
*
*/
UCLASS()
class BLOGCOMBO_API UGMMC_AbilityCost : public UGameplayModMagnitudeCalculation
{
GENERATED_BODY()
float CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const override;
};
GMMC_AbilityCost.cpp
1 2 |
#include "GMMC_AbilityCost.h" #include "Abilities/BlogComboGameplayAbility.h" |
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を継承した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の値が加算されます。
コンボ1~3のGameplayAbilityのCosts→CostGameplayEffectClassに作成したコスト用GameplayEffectを設定します。
スタミナを消費してコンボを繰り出せるようになりました。
動画ではわかりづらいですが、コストが足りなくなると攻撃ボタンを押してもコンボ攻撃が出せなくなります。
このままでは3発攻撃するとスタミナがなくなり一生攻撃できなくなってしまうので、自動でスタミナが回復するように実装していきます。
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をBlogComboCharacterで実行するようにします。
BlogComboCharacterのBeginPlayに以下の画像の赤線のようにノードを繋ぎます。
これで常にスタミナが自動回復するようになりましたが、コンボ攻撃中はスタミナ回復が止まってほしいです。
そこで、コンボ攻撃発動後にスタミナ自動回復にクールタイムを持たせます。
クールタイムの設定にもGameplayEffectを使用します。
GameplayEffectを継承したBPを作成します。
DurationPolicyを「HasDuration」にすることでGameplayEffectが一定時間継続します。
DurationMagnitude->ScalableFloatMagnituideはGameplayEffectが発生してから消えるまでの時間です。
今回は攻撃をしてから1秒たったらスタミナ自動回復が再開してほしいので1.0にしました。
Tags->GrantedTags->Addedに「Stamina.Cooldown」を設定します。
これでGameplayEffectが実行されているときにGameplayAbilityComponentに「Stamina.Cooldown」が付与されます。
コンボ1~3のGameplayAbilityのCommitAbilityの後ろにApplyGameEffectToOwnerを追加し、作成したクールタイム用のGameplayEffectが実行されるようにします。
以上で、スタミナ自動回復が完成しました!
攻撃中はスタミナが回復せず、1秒以上なにもしないと自動回復します。
スタミナが足りず攻撃を出せないときは通知を受け取ってメッセージを出すとかビープ音を出すとかしたいですよね。
ちょっと応用的な内容になりますが、攻撃失敗の通知の実装に癖があったのでご紹介します。
通知はデリゲートで受け取れます。
BlogComboCharacter.h
メンバ定義に以下の関数を追加します。
1 2 3 |
/** GameplayAbility失敗 */ UFUNCTION(BlueprintImplementableEvent) void OnAbilityFailed(const UGameplayAbility* Ability, const FGameplayTagContainer& GameplayTags); |
BlogComboCharacter.cpp
BeginPlayの実装部分に1行追加します。
1 2 3 |
void ABlogComboCharacter::BeginPlay() { Super::BeginPlay(); |
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ページから以下のタグを追加します。
Game/Config/DefaultGame.iniを開き、以下を追加します。
1 2 3 |
[/Script/GameplayAbilities.AbilitySystemGlobals] ActivateFailCostName=Ability.Fail.Cost ActivateFailCooldownName=Ability.Fail.Cooldown |
上記の設定を反映させるために、GameInstanceの拡張が必要です。
GameInstanceを継承したC++クラスを作成します。
以下のように実装します。
BlogComboGameInstance.h
1 2 3 |
#include "CoreMinimal.h" #include "Engine/GameInstance.h" #include "BlogComboGameInstance.generated.h" |
/**
*
*/
UCLASS()
class BLOGCOMBO_API UBlogComboGameInstance : public UGameInstance
{
GENERATED_BODY()
public:
UBlogComboGameInstance();
};
BlogComboGameInstance.cpp
1 2 |
#include "BlogComboGameInstance.h" #include "AbilitySystemGlobals.h" |
UBlogComboGameInstance::UBlogComboGameInstance()
{
UAbilitySystemGlobals::Get().InitGlobalData();
}
ここまででコンパイルします。
OnAbilityFailedイベントから以下の画像のようにノードを組みます。
コストが足りないときに攻撃を出そうとするとメッセージが表示されます。
地味ですが、意外とこういうの大事ですよね。
というわけで、今回はスタミナシステムを実装してみました。
今回ご紹介したGameplayEffectの機能を駆使することで、単純な数値の増減以外にもさまざまなキャラクター制御が実装できるので是非お試しください!