Lyra는 GameplayAbilitySystem를 기반으로 스킬을 처리한다.
Move, Look, Crouch, AutoRun은 별도의 C++ 구현을 두고, Dash, Heal, Grenade, Emote 등 다른 스킬은 모두 Gameplay Ability로 구현되어 있다.
먼저, LyraHeroComponent에서 InputAction을 바인드 한다.
void ULyraHeroComponent::InitializePlayerInput(UInputComponent* PlayerInputComponent)
{
...
UEnhancedInputLocalPlayerSubsystem* Subsystem = LP->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>();
check(Subsystem);
Subsystem->ClearAllMappings();
if (const ULyraPawnExtensionComponent* PawnExtComp = ULyraPawnExtensionComponent::FindPawnExtensionComponent(Pawn))
{
if (const ULyraPawnData* PawnData = PawnExtComp->GetPawnData<ULyraPawnData>())
{
if (const ULyraInputConfig* InputConfig = PawnData->InputConfig)
{
for (const FInputMappingContextAndPriority& Mapping : DefaultInputMappings)
{
...
Subsystem->AddMappingContext(IMC, Mapping.Priority, Options);
}
// The Lyra Input Component has some additional functions to map Gameplay Tags to an Input Action.
// If you want this functionality but still want to change your input component class, make it a subclass
// of the ULyraInputComponent or modify this component accordingly.
ULyraInputComponent* LyraIC = Cast<ULyraInputComponent>(PlayerInputComponent);
if (LyraIC)
{
...
// This is where we actually bind and input action to a gameplay tag, which means that Gameplay Ability Blueprints will
// be triggered directly by these input actions Triggered events.
TArray<uint32> BindHandles;
LyraIC->BindAbilityActions(InputConfig, this, &ThisClass::Input_AbilityInputTagPressed, &ThisClass::Input_AbilityInputTagReleased, /*out*/ BindHandles);
LyraIC->BindNativeAction(InputConfig, LyraGameplayTags::InputTag_Move, ETriggerEvent::Triggered, this, &ThisClass::Input_Move, /*bLogIfNotFound=*/ false);
LyraIC->BindNativeAction(InputConfig, LyraGameplayTags::InputTag_Look_Mouse, ETriggerEvent::Triggered, this, &ThisClass::Input_LookMouse, /*bLogIfNotFound=*/ false);
LyraIC->BindNativeAction(InputConfig, LyraGameplayTags::InputTag_Look_Stick, ETriggerEvent::Triggered, this, &ThisClass::Input_LookStick, /*bLogIfNotFound=*/ false);
LyraIC->BindNativeAction(InputConfig, LyraGameplayTags::InputTag_Crouch, ETriggerEvent::Triggered, this, &ThisClass::Input_Crouch, /*bLogIfNotFound=*/ false);
LyraIC->BindNativeAction(InputConfig, LyraGameplayTags::InputTag_AutoRun, ETriggerEvent::Triggered, this, &ThisClass::Input_AutoRun, /*bLogIfNotFound=*/ false);
}
}
}
}
...
}
LyraInputComponent에서 AbilityActions를 묶고, 이동 관련 함수를 바인드한다.
template<class UserClass, typename PressedFuncType, typename ReleasedFuncType>
void ULyraInputComponent::BindAbilityActions(const ULyraInputConfig* InputConfig, UserClass* Object, PressedFuncType PressedFunc, ReleasedFuncType ReleasedFunc, TArray<uint32>& BindHandles)
{
check(InputConfig);
for (const FLyraInputAction& Action : InputConfig->AbilityInputActions)
{
if (Action.InputAction && Action.InputTag.IsValid())
{
if (PressedFunc)
{
BindHandles.Add(BindAction(Action.InputAction, ETriggerEvent::Triggered, Object, PressedFunc, Action.InputTag).GetHandle());
}
if (ReleasedFunc)
{
BindHandles.Add(BindAction(Action.InputAction, ETriggerEvent::Completed, Object, ReleasedFunc, Action.InputTag).GetHandle());
}
}
}
}
void ULyraHeroComponent::Input_AbilityInputTagPressed(FGameplayTag InputTag)
{
if (const APawn* Pawn = GetPawn<APawn>())
{
if (const ULyraPawnExtensionComponent* PawnExtComp = ULyraPawnExtensionComponent::FindPawnExtensionComponent(Pawn))
{
if (ULyraAbilitySystemComponent* LyraASC = PawnExtComp->GetLyraAbilitySystemComponent())
{
LyraASC->AbilityInputTagPressed(InputTag);
}
}
}
}
void ULyraHeroComponent::Input_AbilityInputTagReleased(FGameplayTag InputTag)
{
const APawn* Pawn = GetPawn<APawn>();
if (!Pawn)
{
return;
}
if (const ULyraPawnExtensionComponent* PawnExtComp = ULyraPawnExtensionComponent::FindPawnExtensionComponent(Pawn))
{
if (ULyraAbilitySystemComponent* LyraASC = PawnExtComp->GetLyraAbilitySystemComponent())
{
LyraASC->AbilityInputTagReleased(InputTag);
}
}
}
BindAbilityActions에서는 LyraInputConfig에 저장된 <InputAciton, GameplayTag> 쌍에 대해 Input_AbilityInputTagPressed와 Input_AbilityInputTagReleased를 바인드한다. Input 함수는 ASC의 AbilityInputTagPressed/Released 함수를 호출한다.
void ULyraAbilitySystemComponent::AbilityInputTagPressed(const FGameplayTag& InputTag)
{
if (InputTag.IsValid())
{
for (const FGameplayAbilitySpec& AbilitySpec : ActivatableAbilities.Items)
{
if (AbilitySpec.Ability && (AbilitySpec.GetDynamicSpecSourceTags().HasTagExact(InputTag)))
{
InputPressedSpecHandles.AddUnique(AbilitySpec.Handle);
InputHeldSpecHandles.AddUnique(AbilitySpec.Handle);
}
}
}
}
ASC에서는 실행 가능한 Ability 목록에서 태그를 검색해 InputPressedSpecHandles와 InputHeldSpecHandles에 핸들을 추가하고,
void ULyraAbilitySystemComponent::ProcessAbilityInput(float DeltaTime, bool bGamePaused)
{
...
for (const FGameplayAbilitySpecHandle& SpecHandle : InputHeldSpecHandles)
{
if (const FGameplayAbilitySpec* AbilitySpec = FindAbilitySpecFromHandle(SpecHandle))
{
if (AbilitySpec->Ability && !AbilitySpec->IsActive())
{
const ULyraGameplayAbility* LyraAbilityCDO = Cast<ULyraGameplayAbility>(AbilitySpec->Ability);
if (LyraAbilityCDO && LyraAbilityCDO->GetActivationPolicy() == ELyraAbilityActivationPolicy::WhileInputActive)
{
AbilitiesToActivate.AddUnique(AbilitySpec->Handle);
}
}
}
}
for (const FGameplayAbilitySpecHandle& SpecHandle : InputPressedSpecHandles)
{
...
}
for (const FGameplayAbilitySpecHandle& AbilitySpecHandle : AbilitiesToActivate)
{
TryActivateAbility(AbilitySpecHandle);
}
for (const FGameplayAbilitySpecHandle& SpecHandle : InputReleasedSpecHandles)
{
...
}
InputPressedSpecHandles.Reset();
InputReleasedSpecHandles.Reset();
}
ProcessAbilityInput에서 TryActivateAbility(AbilitySpecHandle);을 통해 모든 입력을 한번에 처리한다. ProcessAbilityInput는 PostProcessInput에서 실행된다.
void ALyraPlayerController::PostProcessInput(const float DeltaTime, const bool bGamePaused)
{
if (ULyraAbilitySystemComponent* LyraASC = GetLyraAbilitySystemComponent())
{
LyraASC->ProcessAbilityInput(DeltaTime, bGamePaused);
}
Super::PostProcessInput(DeltaTime, bGamePaused);
}

대략 이런 구조이다.
반응형