Lyra Interaction System
Lyra は、独自の ゲームプレイ アビリティ/UGameplayAbility (ULyraGameplayAbility_Interact) を介して、インタラクション インターフェース/Iインターフェース を使用使用して、プレイヤーが Lyra 内のオブジェクトと対話する方法と、それらのオブジェクトがプレイヤーと対話する方法との間に因果関係を確立します。
LyraGameplayAbility_Interact
クラスによって、インタラクションの呼び出し方法のロジックを管理できます。
ULyraGameplayAbility_Interact.h
#pragma once
#include "CoreMinimal.h"
#include "AbilitySystem/Abilities/LyraGameplayAbility.h"
#include "Interaction/InteractionQuery.h"
#include "Interaction/IInteractableTarget.h"
#include "LyraGameplayAbility_Interact.generated.h"
class FIndicatorDescriptor;
/**
* ULyraGameplayAbility_Interact
*
* Gameplay ability used for character interacting
*/
UCLASS(Abstract)
class ULyraGameplayAbility_Interact : public ULyraGameplayAbility
{
GENERATED_BODY()
public:
ULyraGameplayAbility_Interact(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override;
UFUNCTION(BlueprintCallable)
void UpdateInteractions(const TArray<FInteractionOption>& InteractiveOptions);
UFUNCTION(BlueprintCallable)
void TriggerInteraction();
protected:
UPROPERTY(BlueprintReadWrite)
TArray<FInteractionOption> CurrentOptions;
TArray<TSharedRef<FIndicatorDescriptor>> Indicators;
protected:
UPROPERTY(EditDefaultsOnly)
float InteractionScanRate = 0.1f;
UPROPERTY(EditDefaultsOnly)
float InteractionScanRange = 500;
UPROPERTY(EditDefaultsOnly)
TSoftClassPtr<UUserWidget> DefaultInteractionWidgetClass;
};
AbilityTask_WaitForInteractableTargets_SingleLineTrace
はゲームプレイ アビリティ タスク であり、ライン トレースを実行し、インターフェースを実装するアクタに遭遇するまでループ タイマーで待機します。
次に例を示します。
LyraPawnActor を制御しているプレイヤーのヘルスが低下しているので、プレイヤーはポーンに収集可能なヘルス アイテムをピックアップするように指示します。プレイヤーの照準線を収集品に合わせて、「Use/Interact」キーを押すと、ポーンからライン トレースが発射されます。トレースが収集品に命中すると、収集品に実装されているインタラクション インターフェースがプレイヤーのヘルスをフルに回復させるロジックを処理します。
インタラクション アビリティ タスク
UAbilityTask_WaitForInteractableTargets
は、インタラクション可能なターゲットを追跡する新しいトレース方法を作るために使用されます。
次に例を示します。
LyraPawnActor を制御するプレイヤーが、開きたいドアに近づいているとします。プレイヤーの照準線をドアに合わせて、「使用」キーを押すと、ドアの「ロック解除/ロック」、またはドアを開けることを試みるオプションを含む、放射状メニューが表示されます。
Unreal のライン トレースの詳細については、「トレース」を参照してください。
UAbilityTask_WaitForInteractableTargets.h
#pragma once
#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "Abilities/Tasks/AbilityTask.h"
#include "Engine/EngineTypes.h"
#include "CollisionQueryParams.h"
#include "WorldCollision.h"
#include "Engine/CollisionProfile.h"
#include "Abilities/GameplayAbilityTargetDataFilter.h"
#include "Interaction/InteractionOption.h"
#include "Interaction/InteractionQuery.h"
#include "Interaction/IInteractableTarget.h"
#include "AbilityTask_WaitForInteractableTargets.generated.h"
class AActor;
class UPrimitiveComponent;
class UGameplayAbility;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FInteractableObjectsChangedEvent, const TArray<FInteractionOption>&, InteractableOptions);
UCLASS(Abstract)
class UAbilityTask_WaitForInteractableTargets : public UAbilityTask
{
GENERATED_UCLASS_BODY()
public:
UPROPERTY(BlueprintAssignable)
FInteractableObjectsChangedEvent InteractableObjectsChanged;
protected:
static void LineTrace(FHitResult& OutHitResult, const UWorld* World, const FVector& Start, const FVector& End, FName ProfileName, const FCollisionQueryParams Params);
void AimWithPlayerController(const AActor* InSourceActor, FCollisionQueryParams Params, const FVector& TraceStart, float MaxRange, FVector& OutTraceEnd, bool bIgnorePitch = false) const;
static bool ClipCameraRayToAbilityRange(FVector CameraLocation, FVector CameraDirection, FVector AbilityCenter, float AbilityRange, FVector& ClippedPosition);
void UpdateInteractableOptions(const FInteractionQuery& InteractQuery, const TArray<TScriptInterface<IInteractableTarget>>& InteractableTargets);
FCollisionProfileName TraceProfile;
// Does the trace affect the aiming pitch
bool bTraceAffectsAimPitch = true;
TArray<FInteractionOption> CurrentOptions;
};
トレース用に選択した AbilityTask は、FInteractionQuery
構造体から一連のインタラクション可能なターゲットを提供します。
struct FInteractionQuery
#pragma once
#include "CoreMinimal.h"
#include "Abilities/GameplayAbility.h"
#include "InteractionQuery.generated.h"
/** */
USTRUCT(BlueprintType)
struct FInteractionQuery
{
GENERATED_BODY()
public:
/** The requesting pawn. */
UPROPERTY(BlueprintReadWrite)
TWeakObjectPtr<AActor> RequestingAvatar;
/** Provides us the capability to specify a controller - this does not need to match the owner of the requesting avatar. */
UPROPERTY(BlueprintReadWrite)
TWeakObjectPtr<AController> RequestingController;
/** A generic UObject to provide extra data required for the interaction */
UPROPERTY(BlueprintReadWrite)
TWeakObjectPtr<UObject> OptionalObjectData;
};
to the method UAbilityTask_WaitForInteractableTargets::UpdateInteractableOptions
:
void UAbilityTask_WaitForInteractableTargets::UpdateInteractableOptions(const FInteractionQuery& InteractQuery, const TArray<TScriptInterface<IInteractableTarget>>& InteractableTargets)
{
TArray<FInteractionOption> NewOptions;
for (const TScriptInterface<IInteractableTarget>& InteractiveTarget :InteractableTargets)
{
TArray<FInteractionOption> TempOptions;
FInteractionOptionBuilder InteractionBuilder(InteractiveTarget, TempOptions);
InteractiveTarget->GatherInteractionOptions(InteractQuery, InteractionBuilder);
for (FInteractionOption& Option :TempOptions)
{
FGameplayAbilitySpec* InteractionAbilitySpec = nullptr;
// if there is a handle and a target ability system, we're triggering the ability on the target.
if (Option.TargetAbilitySystem && Option.TargetInteractionAbilityHandle.IsValid())
{
// Find the spec
InteractionAbilitySpec = Option.TargetAbilitySystem->FindAbilitySpecFromHandle(Option.TargetInteractionAbilityHandle);
}
// If there's an interaction ability then we're activating it on ourselves.
else if (Option.InteractionAbilityToGrant)
{
// Find the spec
InteractionAbilitySpec = AbilitySystemComponent->FindAbilitySpecFromClass(Option.InteractionAbilityToGrant);
if (InteractionAbilitySpec)
{
// update the option
Option.TargetAbilitySystem = AbilitySystemComponent;
Option.TargetInteractionAbilityHandle = InteractionAbilitySpec->Handle;
}
}
if (InteractionAbilitySpec)
{
// Filter any options that we can't activate right now for whatever reason.
if (InteractionAbilitySpec->Ability->CanActivateAbility(InteractionAbilitySpec->Handle, AbilitySystemComponent->AbilityActorInfo.Get()))
{
NewOptions.Add(Option);
}
}
}
}
bool bOptionsChanged = false;
if (NewOptions.Num() == CurrentOptions.Num())
{
NewOptions.Sort();
for (int OptionIndex = 0; OptionIndex < NewOptions.Num(); OptionIndex++)
{
const FInteractionOption& NewOption = NewOptions[OptionIndex];
const FInteractionOption& CurrentOption = CurrentOptions[OptionIndex];
if (NewOption != CurrentOption)
{
bOptionsChanged = true;
break;
}
}
}
else
{
bOptionsChanged = true;
}
if (bOptionsChanged)
{
CurrentOptions = NewOptions;
InteractableObjectsChanged.Broadcast(CurrentOptions);
}
}
これは、インタラクション可能な各ターゲットで IInteractableTarget::GatherInteractionOptions
を呼び出します。
virtual void GatherInteractionOptions(const FInteractionQuery& InteractQuery, FInteractionOptionBuilder& OptionBuilder) = 0;
一連のインタラクション可能なオブジェクトを更新すると、インタラクション アビリティ (GA_Interact) は、プレイヤーがインタラクション対象オブジェクトにフォーカスして、その特定のオブジェクトとインタラクションしたいプレイヤーからの入力を受ける取ると、この TriggerInteraction
関数を呼び出します。
現在のオプションを呼び出したら、インタラクションを実行させる方法は 2 つあります。1 つ目の方法は、FInteractionOption::InteractionAbilityToGrant
関数を使用して、プレイヤーの Ability System コンポーネントにアビリティを付与する方法です。Weapon Pickup アクタなどのシンプルなロジックでは、この関数の使用が推奨されます。
また、複雑なロジックを処理するために、ユーザーが独自の Ability System コンポーネントを含むオブジェクトをインタラクションする場合は、FInteractionOption::TargetAbilitySystem
関数とFInteractionOption::TargetInteractionHandle
関数を呼び出すことができます。これにより、Lyra キャラクター (アバター) のアビリティ システム コンポーネントでアビリティを呼び出すのではなく、インタラクション可能なオブジェクトでアビリティを呼び出します。
インタラクション関数 FInteractionOption::InteractionAbilityToGrant
は、ULyraGameplayAbility_Interact
インタラクション アビリティの基本クラスから継承されます。これは、範囲指定されたループとタイマーとしてタスク関数 AbilityTask_GrantNearbyInteraction
を実行し、近くのアビリティを収集し、ユーザーがそれらのアビリティのインタラクションを試行する前にキャラクターにそれらのアビリティを付与します。InteractionScanRate
float を大きくすることで、InteractionRange
より半径を広げることができます。これを行わないと、レプリケーションでアビリティが十分迅速にクライアントに提供されません。
このアビリティはイベント ゲームプレイ タグ、FInteractionOption::InteractionEventTag
を使用して呼び出されます。このタグは、アビリティのトリガーと一致している必要があります。たとえば、GA_Collect_Interaction
は Ability.Type.Interact.Collect
イベントが送信されたときにトリガーされます。これはインタラクション オプションで設定されます。
GA_Collect_Interaction
は、1 種類のみのインタラクションを表します。すなわち、地面にあるオブジェクトをピックアップして、インベントリに追加する能力を提供するアビリティです。地面にあるリンゴを食べてプレイヤーのヘルスを回復させたり、ドアを開けたり、車両に乗ったり、チェストを開けたりするアビリティを作成するなど、イマジネーションは無限に広がります。
この分離動作により、中央のパッシブ インタラクション スキャナーから、あらゆる種類のインタラクションが提供されます。
Lyra インタラクションの重要な用語
InteractableTarget - IInteractableTarget インターフェースを実装しているアクタまたはコンポーネント。ワールドのどのオブジェクトとインタラクションできるかを決定します。
InteractionOption - 「Affordance」または「Option」。たとえば、リンゴは「Collect」および「Consume」の両方を指定できます。
InteractionInstigator - インタラクションを開始するポーン (LyraPawnActor)。これは IInteractionInstigator
インターフェースを実装する場合も実装しない場合もあります。これにより、オプションとその表示方法をさらにカスタマイズすることができます。