Gameplay Ability System コードリーディング ⑥ WaitTargetData - Confirmation Type
- Gameplay Ability System コードリーディング ① WaitOverlap - You are done!
- Gameplay Ability System コードリーディング ② SpawnActor - You are done!
- Gameplay Ability System コードリーディング ③ WaitTargetData - You are done!
- Gameplay Ability System コードリーディング ④ WaitTargetData - GameplayAbilityTargetActor (及び Radius) - You are done!
- Gameplay Ability System コードリーディング ⑤ WaitTargetData - Radius の利用 - You are done!
上記の続きになります。
前回の記事で、WaitTargetData
を実際に TargetActor Radius を指定して使ってみました。
その中で、ひとまず GASDocumentation の Confirm の利用方法をそのまま流用しましたが、内容は分からず利用していたのでここではそれを見ていきたいと思います。
一つは上記の WaitTargetData
ノードの Confirmation Type
に関して、もう一つは Show Ability Confirm Cancel Text
ノードに関してです。
後者は GASDocumentation の実装も関わってくるので、引き続き GASDocumentation のコードの参照も行います。
まずは、Confirm と Cancel の入力を受け付けるところから
分かりやすいところで、Confirm のメッセージが出た時に、左マウスクリックで Confirm が成立する部分から見ていきます。
Input
UENUM(BlueprintType) enum class EGDAbilityInputID : uint8 { // 0 None None UMETA(DisplayName = "None"), // 1 Confirm Confirm UMETA(DisplayName = "Confirm"), // 2 Cancel Cancel UMETA(DisplayName = "Cancel"),
EGDAbilityInputID
は前回の記事で Sample
の input を定義した場所でもあります。
続いて、この EGDAbilityInputID::Confirm
と EGDAbilityInputID::Cancel
の利用箇所を見ていきます。一箇所しかありません。
void AGDHeroCharacter::BindASCInput() { if (!ASCInputBound && AbilitySystemComponent.IsValid() && IsValid(InputComponent)) { AbilitySystemComponent->BindAbilityActivationToInputComponent(InputComponent, FGameplayAbilityInputBinds(FString("ConfirmTarget"), FString("CancelTarget"), FString("EGDAbilityInputID"), static_cast<int32>(EGDAbilityInputID::Confirm), static_cast<int32>(EGDAbilityInputID::Cancel))); ASCInputBound = true; } }
この BindASCInput
は以下の 2 箇所から呼び出されています。キャラクターが利用できるようになるときには呼び出されていると考えてよさそうです。
AGDHeroCharacter::SetupPlayerInputComponent
AGDHeroCharacter::OnRep_PlayerState
AbilitySystemComponent#BindAbilityActivationToInputComponent
を見ていきますが、おそらく名前的にも Input の Bind をまるっと行っていそうです。
まず、引数になっている FGameplayAbilityInputBinds
です。こちらは Engine の Plugin なのでコードだけ貼ります。
/** Structure that tells AbilitySystemComponent what to bind to an InputComponent (see BindAbilityActivationToInputComponent) */ struct FGameplayAbilityInputBinds { FGameplayAbilityInputBinds(FString InConfirmTargetCommand, FString InCancelTargetCommand, FString InEnumName, int32 InConfirmTargetInputID = INDEX_NONE, int32 InCancelTargetInputID = INDEX_NONE) : ConfirmTargetCommand(InConfirmTargetCommand) , CancelTargetCommand(InCancelTargetCommand) , EnumName(InEnumName) , ConfirmTargetInputID(InConfirmTargetInputID) , CancelTargetInputID(InCancelTargetInputID) { } /** Defines command string that will be bound to Confirm Targeting */ FString ConfirmTargetCommand; /** Defines command string that will be bound to Cancel Targeting */ FString CancelTargetCommand; /** Returns enum to use for ability binds. E.g., "Ability1"-"Ability9" input commands will be bound to ability activations inside the AbiltiySystemComponent */ FString EnumName; /** If >=0, Confirm is bound to an entry in the enum */ int32 ConfirmTargetInputID; /** If >=0, Cancel is bound to an entry in the enum */ int32 CancelTargetInputID; UEnum* GetBindEnum() { return FindObject<UEnum>(ANY_PACKAGE, *EnumName); } };
Confirm / Cancel に関するものと、 EnumName
という他の Ability に関するものが分けられています。
EnumName
には FString("EGDAbilityInputID")
が入れられていますが、これは最初に見た Confirm, Cancel, Sample などの Enum のことです。この指定は GASDocumentation 独自のものなので、この Enum は各プロジェクトで自由にできるようですね。
続いて AbilitySystemComponent#BindAbilityActivationToInputComponent
です。
void UAbilitySystemComponent::BindAbilityActivationToInputComponent(UInputComponent* InputComponent, FGameplayAbilityInputBinds BindInfo) { UEnum* EnumBinds = BindInfo.GetBindEnum(); SetBlockAbilityBindingsArray(BindInfo); // EGDAbilityInputID Enum の列挙に対してバインド設定 for(int32 idx=0; idx < EnumBinds->NumEnums(); ++idx) { const FString FullStr = EnumBinds->GetNameStringByIndex(idx); // Pressed event { FInputActionBinding AB(FName(*FullStr), IE_Pressed); AB.ActionDelegate.GetDelegateForManualSet().BindUObject(this, &UAbilitySystemComponent::AbilityLocalInputPressed, idx); InputComponent->AddActionBinding(AB); } // Released event { FInputActionBinding AB(FName(*FullStr), IE_Released); AB.ActionDelegate.GetDelegateForManualSet().BindUObject(this, &UAbilitySystemComponent::AbilityLocalInputReleased, idx); InputComponent->AddActionBinding(AB); } } // 以降は Confirm と Cancel のみの設定 // Bind Confirm/Cancel. Note: these have to come last! if (BindInfo.ConfirmTargetCommand.IsEmpty() == false) { FInputActionBinding AB(FName(*BindInfo.ConfirmTargetCommand), IE_Pressed); AB.ActionDelegate.GetDelegateForManualSet().BindUObject(this, &UAbilitySystemComponent::LocalInputConfirm); InputComponent->AddActionBinding(AB); } if (BindInfo.CancelTargetCommand.IsEmpty() == false) { FInputActionBinding AB(FName(*BindInfo.CancelTargetCommand), IE_Pressed); AB.ActionDelegate.GetDelegateForManualSet().BindUObject(this, &UAbilitySystemComponent::LocalInputCancel); InputComponent->AddActionBinding(AB); } if (BindInfo.CancelTargetInputID >= 0) { GenericCancelInputID = BindInfo.CancelTargetInputID; } if (BindInfo.ConfirmTargetInputID >= 0) { GenericConfirmInputID = BindInfo.ConfirmTargetInputID; } }
コード内にコメントを足しましたが、EGDAbilityInputID
の Enum を列挙し全てに Press/Release のバインドを行い、その後 Confirm / Cancel のバインド設定をおこなっているようです。
どういうアクションをバインドしている?
FInputActionBinding
を利用していることや、Action Mapping と対応したキー名になっていることからもなんらかの Action にバインドしていることは間違いありません。
まずは今回の主題でもある Confirm / Cancel にバインドされている UAbilitySystemComponent::LocalInputConfirm / LocalInputCancel
を見てみます。
void UAbilitySystemComponent::LocalInputConfirm() { FAbilityConfirmOrCancel Temp = GenericLocalConfirmCallbacks; GenericLocalConfirmCallbacks.Clear(); Temp.Broadcast(); } void UAbilitySystemComponent::LocalInputCancel() { FAbilityConfirmOrCancel Temp = GenericLocalCancelCallbacks; GenericLocalCancelCallbacks.Clear(); Temp.Broadcast(); }
ここでは GenericLocalConfirmCallbacks / GenericLocalCancelCallbacks
の delegate に関しては素通りしますが、簡単にはこれらを Broadcast し Clear しています。
続いて、各 Ability にバインドされている UAbilitySystemComponent::AbilityLocalInputReleased
を見てみます。
void UAbilitySystemComponent::AbilityLocalInputPressed(int32 InputID) { // Consume the input if this InputID is overloaded with GenericConfirm/Cancel and the GenericConfim/Cancel callback is bound if (IsGenericConfirmInputBound(InputID)) { LocalInputConfirm(); return; } if (IsGenericCancelInputBound(InputID)) { LocalInputCancel(); return; } // --------------------------------------------------------- ABILITYLIST_SCOPE_LOCK(); for (FGameplayAbilitySpec& Spec : ActivatableAbilities.Items) { if (Spec.InputID == InputID) { if (Spec.Ability) { Spec.InputPressed = true; if (Spec.IsActive()) { if (Spec.Ability->bReplicateInputDirectly && IsOwnerActorAuthoritative() == false) { ServerSetInputPressed(Spec.Handle); } AbilitySpecInputPressed(Spec); // Invoke the InputPressed event. This is not replicated here. If someone is listening, they may replicate the InputPressed event to the server. InvokeReplicatedEvent(EAbilityGenericReplicatedEvent::InputPressed, Spec.Handle, Spec.ActivationInfo.GetActivationPredictionKey()); } else { // Ability is not active, so try to activate it TryActivateAbility(Spec.Handle); } } } } }
まず、前半で Confirm / Cancel の入力に対する処理が行われています。Enum として指定した EGDAbilityInputID
には Confirm / Cancel も含まれていますし、Action Mapping にも設定されているため、こちらにも流れてくることへの対応かもしれません。
その後は、ActivatableAbilities
を for で回しながら、Ability (コード内では Spec
) がすでに Active か否かで処理を行っています。ここで Ability の Active 化も行っていたのですね。
ここまでで、入力設定から Confirm と Cancel がどのように入力を受付、そのイベントを流しているのかを確認しました。
続いて、先程すっ飛ばした、Confirm と Cancel にバインドされていた delegate を追っていきます。
AbilitySystemComponent GenericLocalConfirmCallbacks / GenericLocalCancelCallbacks
/** Generic local callback for generic ConfirmEvent that any ability can listen to */ FAbilityConfirmOrCancel GenericLocalConfirmCallbacks; /** Generic local callback for generic CancelEvent that any ability can listen to */ FAbilityConfirmOrCancel GenericLocalCancelCallbacks;
上記が定義になります。
WaitTargetData に戻る
void UAbilityTask_WaitTargetData::FinalizeTargetActor(AGameplayAbilityTargetActor* SpawnedActor) const { // 略 if (SpawnedActor->ShouldProduceTargetData()) { if (ConfirmationType == EGameplayTargetingConfirmation::Instant) { SpawnedActor->ConfirmTargeting(); } else if (ConfirmationType == EGameplayTargetingConfirmation::UserConfirmed) { // Bind to the Cancel/Confirm Delegates (called from local confirm or from repped confirm) SpawnedActor->BindToConfirmCancelInputs(); } } }
上記は AbilityTask_WaitTargetData
の FinalizeTargetActor です。SpawnedActor
は前回見た Radius のような GameplayAbilityTargetActor
です。
ここで、ノードでいうところの Confirmation Type
が Instant
か User Confirmed
かの分岐があります。
今回は User Confirmed
の方を見ていきます。
void AGameplayAbilityTargetActor::BindToConfirmCancelInputs() { check(OwningAbility); const FGameplayAbilityActorInfo* const Info = OwningAbility->GetCurrentActorInfo(); // AbilitySystemComponent を取得 UAbilitySystemComponent* const ASC = Info->AbilitySystemComponent.Get(); if (ASC) { if (Info->IsLocallyControlled()) { // We have to wait for the callback from the AbilitySystemComponent. Which will always be instigated locally ASC->GenericLocalConfirmCallbacks.AddDynamic(this, &AGameplayAbilityTargetActor::ConfirmTargeting); // Tell me if the confirm input is pressed ASC->GenericLocalCancelCallbacks.AddDynamic(this, &AGameplayAbilityTargetActor::CancelTargeting); // Tell me if the cancel input is pressed // Save off which ASC we bound so that we can error check that we're removing them later GenericDelegateBoundASC = ASC; } else { // 略 } } }
見てわかる通り、ここで前述の GenericLocalConfirmCallbacks / GenericLocalCancelCallbacks
に対してバインドされています。
AGameplayAbilityTargetActor::ConfirmTargeting
まずは Confirm の場合です。
void AGameplayAbilityTargetActor::ConfirmTargeting() { // 略 if (IsConfirmTargetingAllowed()) { ConfirmTargetingAndContinue(); if (bDestroyOnConfirmation) { Destroy(); } } } void AGameplayAbilityTargetActor::ConfirmTargetingAndContinue() { check(ShouldProduceTargetData()); if (IsConfirmTargetingAllowed()) { TargetDataReadyDelegate.Broadcast(FGameplayAbilityTargetDataHandle()); } }
ConfirmTargetingAndContinue
は以前にも触れましたが、 ConfirmTargeting
では、ConfirmTargetingAndContinue
を呼び出し Destroy をする流れになっています。
そして ConfirmTargetingAndContinue
で TargetData の待機完了の delegate に流しているということですね。(このまま WaitTargetData の ValidData に流れます)
以前見た Radius もそうですが、多くの場合はこの関数は各 TargetActor の方で独自の TargetDataHandle を作成するために override されています。次に見る Cancel も同様です。
続いて Cancel の場合です。
/** Outside code is saying 'stop everything and just forget about it' */ void AGameplayAbilityTargetActor::CancelTargeting() { // 略 CanceledDelegate.Broadcast(FGameplayAbilityTargetDataHandle()); Destroy(); }
こちらも Cancel の delegate に流して Destroy というだけです。WaitTargetData の Cancelled に流れます。
さて、ここまでで、Confirm / Cancel の入力と、それが WaitTargetData の Confirmation に続く流れを確認できました。
最後は、UI とどのように関連しているかを確認します。
Show Ability Confirm Cancel Text で UI が表示される部分の確認
UI 部分は当然 GASDocumentation 側です。
void AGDPlayerState::ShowAbilityConfirmCancelText(bool ShowText) { AGDPlayerController* PC = Cast<AGDPlayerController>(GetOwner()); if (PC) { UGDHUDWidget* HUD = PC->GetHUD(); if (HUD) { HUD->ShowAbilityConfirmCancelText(ShowText); } } }
PC->GetHUD();
を辿っていくと、以下の設定値にたどり着きます。値は BP から設定されています。
以下の UI_HUD
を利用しているようです。
GASDocumentation/UI_HUD.uasset at 44a197049d3343b4b448755fb998d531acebf26e · tranek/GASDocumentation · GitHub
Designer から見ても間違いありません。
コードに戻りますが、ShowAbilityConfirmCancelText
が呼び出されています。
Blueprint の方で実装されているようです。
単純に表示の切り替えをしているだけのようです。
Confirm / Cancel に関しては UI は特に関係無い
上記で分かる通り、単に UI は表示されているだけで、GAS の Confirm には何ら関係はありません。実際、UI の表示に関するノードを削除したとしても問題無く動作します。
Confirmation Type を Instant にした場合どうなるか
再び UAbilityTask_WaitTargetData::FinalizeTargetActor
に戻りますが、Instant の場合は SpawnedActor->ConfirmTargeting();
が呼び出されています。
この関数ですが、実はすでに確認していて、Confirm 時の Callback で呼び出される関数です。
なので、Instant とはその名の通りで、「即 Confirm されたことにする」という挙動になっています。
まとめ
ざっと Confirmation Type について見てきました。ただ、Instant
と User Confirmed
を見ただけで Custom
と Custom Multi
に関してはまだ見れていません。
余談ですが、見てきた通りで Confirm に紐付けられる Callback は一気に broadcast されるため、例えば WaitTargetData
を2つ用意し、両方とも User Confirmed
にした場合は (片方で End Ability しなければ) 両方が Confirm された扱いになります。
User Confirmed
を使う場合においては、対応する入力キーは一つに固定されるように見えるため、Ability によって confirm のキーを変更したい場合には工夫が必要になりそうです。(またはそういう仕組が用意されていそうな気もします)
以下のドキュメントに「4.6.2.1 Binding to Input without Activating Abilities (Abilities を有効化せずに入力をバインド)」がありますが、それがヒントになるかもしれません(が試してません。。)。