Game Programming/Unreal Engine

Lyra의 스킬 발동

타자치는 문돌이

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_AbilityInputTagPressedInput_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 목록에서 태그를 검색해 InputPressedSpecHandlesInputHeldSpecHandles에 핸들을 추가하고,

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);을 통해 모든 입력을 한번에 처리한다. ProcessAbilityInputPostProcessInput에서 실행된다.

void ALyraPlayerController::PostProcessInput(const float DeltaTime, const bool bGamePaused)
{
    if (ULyraAbilitySystemComponent* LyraASC = GetLyraAbilitySystemComponent())
    {
        LyraASC->ProcessAbilityInput(DeltaTime, bGamePaused);
    }

    Super::PostProcessInput(DeltaTime, bGamePaused);
}

 

대략 이런 구조이다.

 

반응형