- Gameplay Ability System コードリーディング ① WaitOverlap - You are done!
- Gameplay Ability System コードリーディング ② SpawnActor - You are done!
上記の続きになります。
WaitOverlap
は、待機状態になる AbilityTask、SpawnActor
は spawn を伴う AbilityTask としてのサンプルになっていました。
今回は WaitTargetData
を見ていきます。
WaitTargetData
UAbilityTask_WaitTargetData | Unreal Engine Documentation
名前の通り、データの提供を待つタスクのようです。
また、Class
を渡すか、TargetActor
を渡すかの違いはありますが、両方のノードにおいて提供されるデータを出力する指示が必要です。そのために、順序は前後しますがまずはファクトリを見ます。
ファクトリ
上記で貼ったノードが2つとうことで分かるように、static ファクトリも2つ存在しています。
/** Spawns target actor and waits for it to return valid data or to be canceled. */ UFUNCTION(BlueprintCallable, meta=(HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "true", HideSpawnParms="Instigator"), Category="Ability|Tasks") static UAbilityTask_WaitTargetData* WaitTargetData(UGameplayAbility* OwningAbility, FName TaskInstanceName, TEnumAsByte<EGameplayTargetingConfirmation::Type> ConfirmationType, TSubclassOf<AGameplayAbilityTargetActor> Class); /** Uses specified target actor and waits for it to return valid data or to be canceled. */ UFUNCTION(BlueprintCallable, meta = (HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "true", HideSpawnParms = "Instigator"), Category = "Ability|Tasks") static UAbilityTask_WaitTargetData* WaitTargetDataUsingActor(UGameplayAbility* OwningAbility, FName TaskInstanceName, TEnumAsByte<EGameplayTargetingConfirmation::Type> ConfirmationType, AGameplayAbilityTargetActor* TargetActor);
そして、以下の提供データ用の引数を持ちます。
WaitTargetData |
WaitTargetDataUsingActor |
---|---|
TSubclassOf<AGameplayAbilityTargetActor> Class |
AGameplayAbilityTargetActor* TargetActor |
両方に共通する AGameplayAbilityTargetActor
は以下にドキュメントがあります。
AGameplayAbilityTargetActor | Unreal Engine Documentation
これには以下のような子クラスが存在します。提供を待つデータの種類別に分かれているようです。
ファクトリのソースは以下のようになっています。
UAbilityTask_WaitTargetData* UAbilityTask_WaitTargetData::WaitTargetData(UGameplayAbility* OwningAbility, FName TaskInstanceName, TEnumAsByte<EGameplayTargetingConfirmation::Type> ConfirmationType, TSubclassOf<AGameplayAbilityTargetActor> InTargetClass) { UAbilityTask_WaitTargetData* MyObj = NewAbilityTask<UAbilityTask_WaitTargetData>(OwningAbility, TaskInstanceName); //Register for task list here, providing a given FName as a key MyObj->TargetClass = InTargetClass; MyObj->TargetActor = nullptr; MyObj->ConfirmationType = ConfirmationType; return MyObj; } UAbilityTask_WaitTargetData* UAbilityTask_WaitTargetData::WaitTargetDataUsingActor(UGameplayAbility* OwningAbility, FName TaskInstanceName, TEnumAsByte<EGameplayTargetingConfirmation::Type> ConfirmationType, AGameplayAbilityTargetActor* InTargetActor) { UAbilityTask_WaitTargetData* MyObj = NewAbilityTask<UAbilityTask_WaitTargetData>(OwningAbility, TaskInstanceName); //Register for task list here, providing a given FName as a key MyObj->TargetClass = nullptr; MyObj->TargetActor = InTargetActor; MyObj->ConfirmationType = ConfirmationType; return MyObj; }
TargetClass
と TargetActor
のそれぞれのプロパティがあり、それぞれに設定しているだけのようです。
delegate
UPROPERTY(BlueprintAssignable) FWaitTargetDataDelegate ValidData; UPROPERTY(BlueprintAssignable) FWaitTargetDataDelegate Cancelled;
順序が前後しましたが BlueprintAssignable
な delegate の存在を確認しました。ノードの状態とも一致しますね。
Activate / BeginSpawningActor / FinishSpawningActor
virtual void Activate() override; UFUNCTION(BlueprintCallable, meta = (HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "true"), Category = "Abilities") bool BeginSpawningActor(UGameplayAbility* OwningAbility, TSubclassOf<AGameplayAbilityTargetActor> Class, AGameplayAbilityTargetActor*& SpawnedActor); UFUNCTION(BlueprintCallable, meta = (HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "true"), Category = "Abilities") void FinishSpawningActor(UGameplayAbility* OwningAbility, AGameplayAbilityTargetActor* SpawnedActor);
ヘッダファイルを見てわかる通り、このクラスにはここまでの WaitOverlap
(Activate
) と SpawnActor
(BeginSpawningActor
, FinishSpawningActor
) で登場した 3 つの関数がすべて揃っています。
改めて AbilityTask.h
の説明文を貼ります。
We have additional support for AbilityTasks that want to spawn actors. Though this could be accomplished in an Activate() function, it would not be possible to pass in dynamic "ExposeOnSpawn" actor properties. This is a powerful feature of blueprints, in order to support this, you need to implement a different step 3:
Instead of an Activate() function, you should implement a BeginSpawningActor() and FinishSpawningActor() function.
Activate
関数では not be possible to pass in dynamic "ExposeOnSpawn" actor properties
となっているだけで、Activate
を実装するべきではないということではないようです。
では実装を見ていきます。
Activate
void UAbilityTask_WaitTargetData::Activate() { // Need to handle case where target actor was passed into task if (Ability && (TargetClass == nullptr)) { if (TargetActor) { AGameplayAbilityTargetActor* SpawnedActor = TargetActor; TargetClass = SpawnedActor->GetClass(); RegisterTargetDataCallbacks(); if (!IsValid(this)) { return; } if (ShouldSpawnTargetActor()) { InitializeTargetActor(SpawnedActor); FinalizeTargetActor(SpawnedActor); // Note that the call to FinalizeTargetActor, this task could finish and our owning ability may be ended. } else { TargetActor = nullptr; // We may need a better solution here. We don't know the target actor isn't needed till after it's already been spawned. SpawnedActor->Destroy(); SpawnedActor = nullptr; } } else { EndTask(); // そもそも TargetActor も null なら不正な状態なので即終了 } } }
冒頭に 「Need to handle case where target actor was passed into task」とあるように、WaitTargetDataUsingActor
の方を利用された場合の処理のようです。
つまり、TargetActor
が渡されているということは Spawn の必要が無いため、ここで完結できる ということです。
TargetClass = SpawnedActor->GetClass();
の部分で、WaitTargetDataUsingActor
の場合には設定されない TargetClass
を補完していることが分かります。
ざっくりとこの時点で注目したい部分だけ見ますが、
if (ShouldSpawnTargetActor()) { InitializeTargetActor(SpawnedActor); FinalizeTargetActor(SpawnedActor); // Note that the call to FinalizeTargetActor, this task could finish and our owning ability may be ended. }
ShouldSpawnTargetActor
が真の場合に Initialize
と Finalize
を渡された Actor に対して行っていることが分かります。(この SpawnedActor
は元々入力された TargetActor
です)
BeginSpawningActor / FinishSpawningActor
bool UAbilityTask_WaitTargetData::BeginSpawningActor(UGameplayAbility* OwningAbility, TSubclassOf<AGameplayAbilityTargetActor> InTargetClass, AGameplayAbilityTargetActor*& SpawnedActor) { SpawnedActor = nullptr; if (Ability) { if (ShouldSpawnTargetActor()) { UClass* Class = *InTargetClass; if (Class != nullptr) { if (UWorld* World = GEngine->GetWorldFromContextObject(OwningAbility, EGetWorldErrorMode::LogAndReturnNull)) { SpawnedActor = World->SpawnActorDeferred<AGameplayAbilityTargetActor>(Class, FTransform::Identity, nullptr, nullptr, ESpawnActorCollisionHandlingMethod::AlwaysSpawn); } } if (SpawnedActor) { TargetActor = SpawnedActor; InitializeTargetActor(SpawnedActor); } } RegisterTargetDataCallbacks(); } return (SpawnedActor != nullptr); } void UAbilityTask_WaitTargetData::FinishSpawningActor(UGameplayAbility* OwningAbility, AGameplayAbilityTargetActor* SpawnedActor) { if (IsValid(SpawnedActor)) { check(TargetActor == SpawnedActor); const FTransform SpawnTransform = AbilitySystemComponent->GetOwner()->GetTransform(); SpawnedActor->FinishSpawning(SpawnTransform); FinalizeTargetActor(SpawnedActor); } }
こちらも SpawnActor
の AbilityTask と同様の作りになっています。
InTargetClass
は入力ピンで指定された TargetClass
です。
BeginSpawningActor
では、Activate
の方と同じく ShouldSpawnTargetActor
の真をチェックしています。
その後は、SpawnActor
と同じように SpawnActorDeferred
を使って Spawn 処理を書いています。
SpawnedActor
を得た後は、また Activate
の方と同じように InitializeTargetActor
を呼び出しています。
FinishSpawningActor
では、SpawnActor
と同じように FinishSpawning
で、UCS の呼び出しを行い、最後に Activate
でも呼び出していた FinalizeTargetActor
を呼び出しています。
ここまで見てわかる通り、BeginSpawningActor
と FinishSpawningActor
で Spawn 処理の手続きはあるものの、やっていることは Activate
で直接 Actor に対して行った処理を同じことを行っているということです。
さて、ここまでで WaitTargetData
の序盤的な箇所を見てきました。ただ、ここまででは肝心の WaitTargetData
な部分が出てきていません。
TargetClass/TargetActor
として渡された AGameplayAbilityTargetActor
が、どのように扱われるのかを見ていきます。
InitializeTargetActor / FinalizeTargetActor
ヘッダファイルにはコメントも無いので省きます。
void UAbilityTask_WaitTargetData::InitializeTargetActor(AGameplayAbilityTargetActor* SpawnedActor) const { check(SpawnedActor); check(Ability); SpawnedActor->MasterPC = Ability->GetCurrentActorInfo()->PlayerController.Get(); // If we spawned the target actor, always register the callbacks for when the data is ready. SpawnedActor->TargetDataReadyDelegate.AddUObject(const_cast<UAbilityTask_WaitTargetData*>(this), &UAbilityTask_WaitTargetData::OnTargetDataReadyCallback); SpawnedActor->CanceledDelegate.AddUObject(const_cast<UAbilityTask_WaitTargetData*>(this), &UAbilityTask_WaitTargetData::OnTargetDataCancelledCallback); } void UAbilityTask_WaitTargetData::FinalizeTargetActor(AGameplayAbilityTargetActor* SpawnedActor) const { check(SpawnedActor); check(Ability); // User ability activation is inhibited while this is active AbilitySystemComponent->SpawnedTargetActors.Push(SpawnedActor); SpawnedActor->StartTargeting(Ability); if (SpawnedActor->ShouldProduceTargetData()) { // If instant confirm, then stop targeting immediately. // Note this is kind of bad: we should be able to just call a static func on the CDO to do this. // But then we wouldn't get to set ExposeOnSpawnParameters. 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(); } } }
詳細は避けながらざっくりと処理を追ってみます。
InitializeTargetActor
SpawnedActor
のMasterPC
に、現在の Ability の PlayerController を設定SpawnedActor
のTargetDataReadyDelegate
にUAbilityTask_WaitTargetData::OnTargetDataReadyCallback
を紐づけSpawnedActor
のCanceledDelegate
にUAbilityTask_WaitTargetData::OnTargetDataCancelledCallback
を紐づけ
FinalizeTargetActor
AbilitySystemComponent
のSpawnedTargetActors
にSpawnedActor
を追加SpawnedActor
のStartTargeting
を呼び出しSpawnedActor
のShouldProduceTargetData
が真の場合ConfirmationType
に応じて、即 Confirm するか、Confirm か Cancel かの入力待ちをバインド
SpawnedActor
、つまりは AGameplayAbilityTargetActor
の関連クラスの詳細を無視すれば、以下のようなことが行われているようです。
SpawnedActor
に対して TargetData の Ready or Cancel に関するコールバックを付ける- ASC の SpawnedTargetActors に
SpawnedActor
を追加 SpawnedActor
のStartTargeting
を呼び出し(待ちの開始?)WaitTargetData
のConfirmationType
に応じて、SpawnedActor
の Confirm 処理の切り替え
ノードの利用側としては、SpawnedActor
が TargetData
を発見すればコールバック (ValidData
or Cancelled
) が呼び出され、それが ConfirmationType
に依存しているという感じでしょうか。
では、コールバックで delegate が呼び出されているかの確認をしておきたいと思います。
OnTargetDataReadyCallback / OnTargetDataCancelledCallback
/** The TargetActor we spawned locally has called back with valid target data */ void UAbilityTask_WaitTargetData::OnTargetDataReadyCallback(const FGameplayAbilityTargetDataHandle& Data) { // Prediction に関する処理につき省略 if (ShouldBroadcastAbilityTaskDelegates()) { ValidData.Broadcast(Data); } if (ConfirmationType != EGameplayTargetingConfirmation::CustomMulti) { EndTask(); } } /** The TargetActor we spawned locally has called back with a cancel event (they still include the 'last/best' targetdata but the consumer of this may want to discard it) */ void UAbilityTask_WaitTargetData::OnTargetDataCancelledCallback(const FGameplayAbilityTargetDataHandle& Data) { // Prediction に関する処理につき省略 Cancelled.Broadcast(Data); EndTask(); }
現状 Prediction に関して全く追いつけていないので処理を一部省略しています。
コードを見てわかる通り、ValidData
, Cancelled
の delegate はそれぞれのコールバックで呼び出されていることが確認できました。
ConfirmationType の CustomMulti というのは、まだ全くわかりませんがこの時点で EndTask にしないというのは気になります。
まとめ
WaitTargetData
の表層だけではありますが、ここまでの WaitOverlap
や SpawnActor
の AbilityTask と比較しつつ見ていきました。
一方で、 AGameplayAbilityTargetActor
でのデータを探す処理が面白い部分だと思うので引き続き見ていきたいと思います。
加えて、Predition やネットワークのレプリケーション部分も大きく省いて見たので、その辺も見ていければと思います。