- 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!
- Gameplay Ability System コードリーディング ⑥ WaitTargetData - Confirmation Type - You are done!
- Gameplay Ability System コードリーディング ⑦ WaitTargetData - Confirmation Type (Custom / CustomMulti) - You are done!
上記の続きになります。
ここまでは簡単そうな Radius (AGameplayAbilityTargetActor_Radius
) を見つつ、ConfirmationType などを見てきました。
今回からは AGameplayAbilityTargetActor_Trace
から続くトレース関連を見ていきます。
SingleLineTrace
SingleLineTrace |
Radius |
---|---|
Radius
は Radius
プロパティだけに対して SingleLineTrace
は Max Range
, Trace Profile
, Trace Affects Aim Pitch
の 3 つが用意されています。他は共通しています。
共通する部分はこれらの基底である AGameplayAbilityTargetActor
に定義されているものです。
ヘッダ
AGameplayAbilityTargetActor_SingleLineTrace
が AGameplayAbilityTargetActor_Trace
を継承しているため、SingleLineTrace は以下のように小さいです。
UCLASS(Blueprintable) class GAMEPLAYABILITIES_API AGameplayAbilityTargetActor_SingleLineTrace : public AGameplayAbilityTargetActor_Trace { GENERATED_UCLASS_BODY() protected: virtual FHitResult PerformTrace(AActor* InSourceActor) override; };
親の Trace 側は以下のようになっています。
/** Intermediate base class for all line-trace type targeting actors. */ UCLASS(Abstract, Blueprintable, notplaceable, config=Game) class GAMEPLAYABILITIES_API AGameplayAbilityTargetActor_Trace : public AGameplayAbilityTargetActor { GENERATED_UCLASS_BODY() public: virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; /** Traces as normal, but will manually filter all hit actors */ static void LineTraceWithFilter(FHitResult& OutHitResult, const UWorld* World, const FGameplayTargetDataFilterHandle FilterHandle, const FVector& Start, const FVector& End, FName ProfileName, const FCollisionQueryParams Params); /** Sweeps as normal, but will manually filter all hit actors */ static void SweepWithFilter(FHitResult& OutHitResult, const UWorld* World, const FGameplayTargetDataFilterHandle FilterHandle, const FVector& Start, const FVector& End, const FQuat& Rotation, const FCollisionShape CollisionShape, FName ProfileName, const FCollisionQueryParams Params); void AimWithPlayerController(const AActor* InSourceActor, FCollisionQueryParams Params, const FVector& TraceStart, FVector& OutTraceEnd, bool bIgnorePitch = false) const; static bool ClipCameraRayToAbilityRange(FVector CameraLocation, FVector CameraDirection, FVector AbilityCenter, float AbilityRange, FVector& ClippedPosition); virtual void StartTargeting(UGameplayAbility* Ability) override; virtual void ConfirmTargetingAndContinue() override; virtual void Tick(float DeltaSeconds) override; UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (ExposeOnSpawn = true), Category = Trace) float MaxRange; UPROPERTY(BlueprintReadWrite, EditAnywhere, config, meta = (ExposeOnSpawn = true), Category = Trace) FCollisionProfileName TraceProfile; // Does the trace affect the aiming pitch UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (ExposeOnSpawn = true), Category = Trace) bool bTraceAffectsAimPitch; protected: virtual FHitResult PerformTrace(AActor* InSourceActor) PURE_VIRTUAL(AGameplayAbilityTargetActor_Trace, return FHitResult();); FGameplayAbilityTargetDataHandle MakeTargetData(const FHitResult& HitResult) const; TWeakObjectPtr<AGameplayAbilityWorldReticle> ReticleActor; };
以下に入力ピンになっているものを抜き出しましたが、先程確認した 3 つが定義されていることがわかります。つまり、これらは SingleLineTrace
固有のものではなく、Trace
全般で共通して利用するプロパティのようです。
UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (ExposeOnSpawn = true), Category = Trace) float MaxRange; UPROPERTY(BlueprintReadWrite, EditAnywhere, config, meta = (ExposeOnSpawn = true), Category = Trace) FCollisionProfileName TraceProfile; // Does the trace affect the aiming pitch UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (ExposeOnSpawn = true), Category = Trace) bool bTraceAffectsAimPitch;
StartTargeting
Radius
でも確認しましたが、StartTargeting
は、これら AGameplayAbilityTargetActor
を利用する WaitTargetData
(AbilityTask_WaitTargetData
) から呼び出されます。
AGameplayAbilityTargetActor
が WaitTargetData
の初期処理で Spawn され、StartTargeting
を呼び出してこの Actor の開始が行われるということです。
void AGameplayAbilityTargetActor_Trace::StartTargeting(UGameplayAbility* InAbility) { Super::StartTargeting(InAbility); SourceActor = InAbility->GetCurrentActorInfo()->AvatarActor.Get(); if (ReticleClass) { // 略 // ReticleActor = ~ } }
Reticle に関してはここでは割愛しますので、SourceActor
として Ability が紐付けられている Actor (例えばプレイヤーキャラクター)を入れています。
EndPlay
AActor
の EndPlay
の override です。
void AGameplayAbilityTargetActor_Trace::EndPlay(const EEndPlayReason::Type EndPlayReason) { if (ReticleActor.IsValid()) { ReticleActor.Get()->Destroy(); } Super::EndPlay(EndPlayReason); }
StartTargeting
で ReticleActor
に値が入れられた場合にそれを Destroy
しています。
Tick
AGameplayAbilityTargetActor
は Actor なので Tick が動作します。
AGameplayAbilityTargetActor_Trace::AGameplayAbilityTargetActor_Trace(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { PrimaryActorTick.bCanEverTick = true; PrimaryActorTick.TickGroup = TG_PostUpdateWork; MaxRange = 999999.0f; bTraceAffectsAimPitch = true; }
コンストラクタを見てわかる通り Tick の設定もされています。
void AGameplayAbilityTargetActor_Trace::Tick(float DeltaSeconds) { // very temp - do a mostly hardcoded trace from the source actor if (SourceActor && SourceActor->GetLocalRole() != ENetRole::ROLE_SimulatedProxy) { FHitResult HitResult = PerformTrace(SourceActor); FVector EndPoint = HitResult.Component.IsValid() ? HitResult.ImpactPoint : HitResult.TraceEnd; #if ENABLE_DRAW_DEBUG if (bDebug) { DrawDebugLine(GetWorld(), SourceActor->GetActorLocation(), EndPoint, FColor::Green, false); DrawDebugSphere(GetWorld(), EndPoint, 16, 10, FColor::Green, false); } #endif // ENABLE_DRAW_DEBUG SetActorLocationAndRotation(EndPoint, SourceActor->GetActorRotation()); } }
トレースの概要 | Unreal Engine ドキュメント
PerformTrace
は SingleLineTrace
側での実装になっていますのでここではスキップします。
ただ、FHitResult
を返しており、Hit があるかないかで ImpactPoint
か TraceEnd
を取得しており、それに応じて自身(つまり AGameplayAbilityTargetActor_SingleLineTrace
) の SetActorLocationAndRotation
を呼び出しています。
継続して Trace をするためにこの Actor 自身も移動しています。(どうして EndPoint
に移動するのか、、は分かりません 😥)
AGameplayAbilityTargetActor_SingleLineTrace::PerformTrace
Tick で呼び出される関数と分かりましたので引き続き PerformTrace
の詳細を見ていきます。
FHitResult AGameplayAbilityTargetActor_SingleLineTrace::PerformTrace(AActor* InSourceActor) { bool bTraceComplex = false; TArray<AActor*> ActorsToIgnore; ActorsToIgnore.Add(InSourceActor); FCollisionQueryParams Params(SCENE_QUERY_STAT(AGameplayAbilityTargetActor_SingleLineTrace), bTraceComplex); Params.bReturnPhysicalMaterial = true; Params.AddIgnoredActors(ActorsToIgnore); FVector TraceStart = StartLocation.GetTargetingTransform().GetLocation(); // InSourceActor->GetActorLocation(); FVector TraceEnd; AimWithPlayerController(InSourceActor, Params, TraceStart, TraceEnd); // Effective on server and launching client only // ------------------------------------------------------ FHitResult ReturnHitResult; LineTraceWithFilter(ReturnHitResult, InSourceActor->GetWorld(), Filter, TraceStart, TraceEnd, TraceProfile.Name, Params); //Default to end of trace line if we don't hit anything. if (!ReturnHitResult.bBlockingHit) { ReturnHitResult.Location = TraceEnd; } if (AGameplayAbilityWorldReticle* LocalReticleActor = ReticleActor.Get()) { // 略 } #if ENABLE_DRAW_DEBUG if (bDebug) { // 略 } #endif // ENABLE_DRAW_DEBUG return ReturnHitResult; }
FCollisionQueryParams Params
の設定で、SourceActor
つまりこの Ability のオーナーが除外されていますTraceStart
はStartLocation
(FGameplayAbilityTargetingLocationInfo
) のGetTargetingTransform
関数から取得していますTraceEnd
はAGameplayAbilityTargetActor_Trace::AimWithPlayerController
で計算されています
ここまでで Trace の準備ができたため、AGameplayAbilityTargetActor_Trace::LineTraceWithFilter
に上記で準備した値が渡され、ReturnHitResult
に値が結果が入ります。
その結果を受けて if (!ReturnHitResult.bBlockingHit)
で分かる通り、ブロッキングでヒットした場合以外は TraceEnd
が ReturnHitResult.Location
に入れられます。 (Overlap の場合はヒットしていないと同じ扱いということですかね)
LineTraceWithFilter
AGameplayAbilityTargetActor_Trace
の関数なので、SingleLineTrace 固有ではありません。
void AGameplayAbilityTargetActor_Trace::LineTraceWithFilter(FHitResult& OutHitResult, const UWorld* World, const FGameplayTargetDataFilterHandle FilterHandle, const FVector& Start, const FVector& End, FName ProfileName, const FCollisionQueryParams Params) { check(World); TArray<FHitResult> HitResults; World->LineTraceMultiByProfile(HitResults, Start, End, ProfileName, Params); OutHitResult.TraceStart = Start; OutHitResult.TraceEnd = End; for (int32 HitIdx = 0; HitIdx < HitResults.Num(); ++HitIdx) { const FHitResult& Hit = HitResults[HitIdx]; if (!Hit.HitObjectHandle.IsValid() || FilterHandle.FilterPassesForActor(Hit.HitObjectHandle.FetchActor())) { OutHitResult = Hit; OutHitResult.bBlockingHit = true; // treat it as a blocking hit return; } } }
入力ピンで受け取っていた ProfileName
を使って LineTraceMultiByProfile
を実行しています。
ここで取得できた TArray<FHitResult>
から単一の OutHitResult
を返すわけですが、for
ループで回して条件の合致をチェックして最初に合致したものの FHitResult
の情報を使っているようです。
if (!Hit.HitObjectHandle.IsValid() || FilterHandle.FilterPassesForActor(Hit.HitObjectHandle.FetchActor()))
に関してですが、後者の Filter
に関しては、入力ピンの Filter
を使っている部分は Radius でも同様の判定があったので理解できるのですが、!Hit.HitObjectHandle.IsValid()
に関しては私の知識ではピンときませんでした。
HitObjectHandle
が無い Hit というものがどういうケースをさしているのかが分かりませんが、その場合も有効なブロッキングヒットとして扱うようになっているようです。
AimWithPlayerController
こちらも AGameplayAbilityTargetActor_Trace
の関数です。前述の通り、この関数の結果として利用するのは OutTraceEnd
です。
void AGameplayAbilityTargetActor_Trace::AimWithPlayerController(const AActor* InSourceActor, FCollisionQueryParams Params, const FVector& TraceStart, FVector& OutTraceEnd, bool bIgnorePitch) const { if (!OwningAbility) // Server and launching client only { return; } APlayerController* PC = OwningAbility->GetCurrentActorInfo()->PlayerController.Get(); check(PC); FVector ViewStart; FRotator ViewRot; PC->GetPlayerViewPoint(ViewStart, ViewRot); const FVector ViewDir = ViewRot.Vector(); FVector ViewEnd = ViewStart + (ViewDir * MaxRange); ClipCameraRayToAbilityRange(ViewStart, ViewDir, TraceStart, MaxRange, ViewEnd); FHitResult HitResult; LineTraceWithFilter(HitResult, InSourceActor->GetWorld(), Filter, ViewStart, ViewEnd, TraceProfile.Name, Params); const bool bUseTraceResult = HitResult.bBlockingHit && (FVector::DistSquared(TraceStart, HitResult.Location) <= (MaxRange * MaxRange)); const FVector AdjustedEnd = (bUseTraceResult) ? HitResult.Location : ViewEnd; FVector AdjustedAimDir = (AdjustedEnd - TraceStart).GetSafeNormal(); if (AdjustedAimDir.IsZero()) { AdjustedAimDir = ViewDir; } if (!bTraceAffectsAimPitch && bUseTraceResult) { FVector OriginalAimDir = (ViewEnd - TraceStart).GetSafeNormal(); if (!OriginalAimDir.IsZero()) { // Convert to angles and use original pitch const FRotator OriginalAimRot = OriginalAimDir.Rotation(); FRotator AdjustedAimRot = AdjustedAimDir.Rotation(); AdjustedAimRot.Pitch = OriginalAimRot.Pitch; AdjustedAimDir = AdjustedAimRot.Vector(); } } OutTraceEnd = TraceStart + (AdjustedAimDir * MaxRange); }
まず PC->GetPlayerViewPoint(ViewStart, ViewRot);
です。
APlayerController::GetPlayerViewPoint | Unreal Engine Documentation
GASDocumentation の場合は TPS なので、キャラクターの後ろにあるカメラのポイントになります。
FVector ViewEnd = ViewStart + (ViewDir * MaxRange);
で ViewEnd
を出していますが、MaxRange
が入力ピンから受け取っているものなので、ここだけみると ViewEnd
はカメラからの距離で測っているようです。
ClipCameraRayToAbilityRange
ClipCameraRayToAbilityRange
関数が呼び出されます、ここで ViewEnd
の位置を計算し直しているようです。
bool AGameplayAbilityTargetActor_Trace::ClipCameraRayToAbilityRange(FVector CameraLocation, FVector CameraDirection, FVector AbilityCenter, float AbilityRange, FVector& ClippedPosition) { FVector CameraToCenter = AbilityCenter - CameraLocation; float DotToCenter = FVector::DotProduct(CameraToCenter, CameraDirection); if (DotToCenter >= 0) //If this fails, we're pointed away from the center, but we might be inside the sphere and able to find a good exit point. { float DistanceSquared = CameraToCenter.SizeSquared() - (DotToCenter * DotToCenter); float RadiusSquared = (AbilityRange * AbilityRange); if (DistanceSquared <= RadiusSquared) { float DistanceFromCamera = FMath::Sqrt(RadiusSquared - DistanceSquared); float DistanceAlongRay = DotToCenter + DistanceFromCamera; //Subtracting instead of adding will get the other intersection point ClippedPosition = CameraLocation + (DistanceAlongRay * CameraDirection); //Cam aim point clipped to range sphere return true; } } return false; }
変数名が変わっているので以下に対応表を書きます。
関数の外 | 関数内 |
---|---|
ViewStart |
CameraLocation |
ViewDir |
CameraDirection |
TraceStart |
AbilityCenter |
MaxRange |
AbilityRange |
ViewEnd |
ClippedPosition |
3次元のベクトルをスクショで表示しようというのが無理がある上に全て位置ベクトルでの表示で余計にややこしいのですが
FVector CameraToCenter = AbilityCenter - CameraLocation; float DotToCenter = FVector::DotProduct(CameraToCenter, CameraDirection);
この 2 行に関してのみ図示してみます。この場合、AbilityCenter
つまり TraceStart
はキャラクター前方の Sphere の位置を指定していると考えてください。
まず赤で表される CameraToCenter
ですが、AbilityCenter
と CameraLocation
のベクトルの差です。実際には3次元なので見た目の長さや角度に違和感はありますが、以下のような感じになると思います。(ホント、もっと良い見せ方ないのか。。)
続いて DotToCenter
ですが、CameraToCenter
と CameraDirection
の内積です。CameraDirection
は大きさ 1 なので CameraToCenter
を CameraDirection
の方向にした場合の大きさです。
図では黒線で表していますが、DotToCenter
は長さなので、CameraDirection
の方向に図示しています。例によって 3次元だとよくわからん感はあるのですが、赤線が CameraDirection
方向に射影されたら黒線ぐらいの長さになるというのはなんとなく分かるような分からんような。
続いて if (DotToCenter >= 0)
で判定していますが、これが負になるということは、カメラの向きと、カメラ - トレーススタートの向きが 90 度より大きいということなのでほぼ視界外れているか端の方にいるということになりそうなのでここでは false
としているのでしょうか。(コメントの意図はよく分かりませんでした 😅)
float DistanceSquared = CameraToCenter.SizeSquared() - (DotToCenter * DotToCenter); float RadiusSquared = (AbilityRange * AbilityRange); // 長さの比較 if (DistanceSquared <= RadiusSquared) { float DistanceFromCamera = FMath::Sqrt(RadiusSquared - DistanceSquared); float DistanceAlongRay = DotToCenter + DistanceFromCamera; //Subtracting instead of adding will get the other intersection point ClippedPosition = CameraLocation + (DistanceAlongRay * CameraDirection); //Cam aim point clipped to range sphere return true; }
まずは長さの比較です。ベクトルが混ざっているので全体的に 2 乗されていますが最終的に長さを比較しています。
まず AbilityRange
ですが、これは入力の MaxRange
ですので Trace する範囲(ここでは SingleLine なので単純に線の長さ)です。
これと比較するのは CameraToCenter
と DotToCenter
の長さを引いたものです。DotToCenter
は前述の通り、単に CameraToCenter
を CameraDirection
方向にしただけです。
自分でもふわっとした理解になってしまうのですが、DistanceSquared <= RadiusSquared
は、まず DistanceSquared
は「カメラからトレース開始位置の距離」と「それをカメラの方向を考慮した距離」の差なので、もしカメラがトレース開始位置とは異なる方向を向いていればいる程「その差は大きく」なります。
そして最終的に ClippedPosition
は CameraLocation
に対しての位置を計算します。つまり、上記の「差」が「トレース範囲」よりも大きくなってしまうと「カメラとトレース開始位置の間のみを対象とする」ことになりかねないということだと思います。(実際、この ClippedPosition
は ViewEnd
として LineTraceWithFilter
のパラメータとして使われます)
DistanceFromCamera
がカメラの方向も考慮した上でのトレースする距離を表し、DistanceAlongRay
でそれをカメラ方向を考慮したトレース開始位置に足し合わせることで、カメラからの総トレース距離を計算しています。
私個人としては、SingleLineTrace といえば、「キャラクターの前方からトレースして」みたいなことを実装することがほとんどなので、こういうコードを見るのはとても新鮮でした。
再び AimWithPlayerController に戻る
ClipCameraRayToAbilityRange
が長くなったので以下に以降の処理を貼り直します。(全部ではありません)
ClipCameraRayToAbilityRange(ViewStart, ViewDir, TraceStart, MaxRange, ViewEnd); FHitResult HitResult; LineTraceWithFilter(HitResult, InSourceActor->GetWorld(), Filter, ViewStart, ViewEnd, TraceProfile.Name, Params); const bool bUseTraceResult = HitResult.bBlockingHit && (FVector::DistSquared(TraceStart, HitResult.Location) <= (MaxRange * MaxRange)); const FVector AdjustedEnd = (bUseTraceResult) ? HitResult.Location : ViewEnd; FVector AdjustedAimDir = (AdjustedEnd - TraceStart).GetSafeNormal(); if (AdjustedAimDir.IsZero()) { AdjustedAimDir = ViewDir; }
ClipCameraRayToAbilityRange
で ViewEnd
が得られましたので、それを使って LineTraceWithFilter
を行っています。ViewStart
はカメラの位置です。これを行うために ClipCameraRayToAbilityRange
でカメラからトレースする範囲を計算していたんですね。
その結果を受けて、BlockingHit
かつ (FVector::DistSquared(TraceStart, HitResult.Location) <= (MaxRange * MaxRange))
でトレース開始位置(カメラ位置ではなく入力ピンから受け取ったトレース開始位置)からヒットした場所の距離と、トレース範囲の距離を比較し、有効範囲であれば bUseTraceResult = true
としています。トレース開始位置からではなく、カメラからのトレースなので意図しない場所でヒットしないかをチェックしているのだと思います。
AdjustedEnd
には、先程の bUseTraceResult
に応じて、ヒットした場所か ViewEnd
(ClipCameraRayToAbilityRange
で計算したもの) を入れています。
AdjustedAimDir
は AdjustedEnd
と TraceStart
の差の向きのみを取得しています。
そして、あまりに差が小さい場合には ViewDir
(カメラ向き) を代わりに利用しています。
AimDir
ということは、狙う方向的なことだと思うので、カメラからトレースしてヒットしたベクトルと、トレース開始位置のベクトルの差というのはなるほどという感じです。
AimWithPlayerController
の最後のブロックです。
まず、if
ブロックを通過しない場合は、先程の AdjustedAimDir
を使ってトレース開始位置からトレース距離分 AdjustedAimDir
を伸ばしたベクトルを OutTraceEnd
としています。
if (!bTraceAffectsAimPitch && bUseTraceResult) { FVector OriginalAimDir = (ViewEnd - TraceStart).GetSafeNormal(); if (!OriginalAimDir.IsZero()) { // Convert to angles and use original pitch const FRotator OriginalAimRot = OriginalAimDir.Rotation(); FRotator AdjustedAimRot = AdjustedAimDir.Rotation(); AdjustedAimRot.Pitch = OriginalAimRot.Pitch; AdjustedAimDir = AdjustedAimRot.Vector(); } } OutTraceEnd = TraceStart + (AdjustedAimDir * MaxRange);
bTraceAffectsAimPitch
は入力ピンで受け取る値です。Pitch
に関してはここまでの結果を使わないとうことでしょうか。
まず、ViewEnd
(ClipCameraRayToAbilityRange
で取得) と TraceStart
(トレース開始位置 ) の差の向きを OriginalAimDir
としています。
それが有効な場合には、Pitch
のみ AdjustedAimDir
から差し替えているようです。(と書いていますが、Vector -> Rotation -> Vector の変換ってよくわかってない。。)
結局 AimWithPlayerController はなんだったのか
結果として受け取るのは OutTraceEnd
、つまり外側の値としては TraceEnd
、SingleLineTrace のトレース範囲の端です。
そのために、カメラの向きを考慮したトレース範囲を計算し、それを持って一度 LineTrace を行い、その結果としてヒットが得られればその方向にトレースの方向を調整しています。
よって、私のような素人がよくやる Trace はキャラクターの ForwardVector でみたいなものではなく、カメラの向きを考慮した上でトレースの方向を決めているという感じと個人的には理解しました。
ConfirmTargetingAndContinue
Radius
のところでも見ましたが、Trace 系も同様に ConfirmTargetingAndContinue
で AbilityTask_WaitTargetData
の confirm を受け付けます。つまりこれが実行された時点でこの GameplayAbilityTargetActor_SingleLineTrace
はデータを出力します。
Tick
はあくまで TargetActor 自身の SetActorLocationAndRotation
しか更新していません。
void AGameplayAbilityTargetActor_Trace::ConfirmTargetingAndContinue() { check(ShouldProduceTargetData()); if (SourceActor) { bDebug = false; FGameplayAbilityTargetDataHandle Handle = MakeTargetData(PerformTrace(SourceActor)); TargetDataReadyDelegate.Broadcast(Handle); } } FGameplayAbilityTargetDataHandle AGameplayAbilityTargetActor_Trace::MakeTargetData(const FHitResult& HitResult) const { /** Note: This will be cleaned up by the FGameplayAbilityTargetDataHandle (via an internal TSharedPtr) */ return StartLocation.MakeTargetDataHandleFromHitResult(OwningAbility, HitResult); }
Tick
でも実行されていた PerformTrace
を再度実行し、その時点でヒットするデータを FGameplayAbilityTargetDataHandle
に詰めた上で delegate に流しています。
まとめ
簡単には、トレース開始位置、範囲、トレース対象のプロファイルを渡した上で、内部では LineTraceByProfile
を行い、Confirm 時点でヒットしているデータを渡すというだけのものでした。
加えて、トレース開始位置と範囲だけを渡しているため、トレース終了位置を内部で決定しておりその処理が個人的には面白いものでした。