Skip to content

Input buffer #260

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#include "Animation/GBFAnimNotifyState_InputBuffer.h"

#include "GBFLog.h"

#include <Components/SkeletalMeshComponent.h>

void UGBFAnimNotifyState_InputBuffer::NotifyBegin( USkeletalMeshComponent * mesh_component, UAnimSequenceBase * animation, float total_duration, const FAnimNotifyEventReference & event_reference )
{
Super::NotifyBegin( mesh_component, animation, total_duration, event_reference );

if ( auto * aibc = GetAbilityInputBufferComponent( mesh_component ) )
{
aibc->StartMonitoring( InputTagsToCheck, TriggerPriority );
}
}

void UGBFAnimNotifyState_InputBuffer::NotifyEnd( USkeletalMeshComponent * mesh_component, UAnimSequenceBase * animation, const FAnimNotifyEventReference & event_reference )
{
Super::NotifyEnd( mesh_component, animation, event_reference );

if ( auto * aibc = GetAbilityInputBufferComponent( mesh_component ) )
{
aibc->StopMonitoring();
}
}

UGBFAbilityInputBufferComponent * UGBFAnimNotifyState_InputBuffer::GetAbilityInputBufferComponent_Implementation( const USkeletalMeshComponent * mesh_component ) const
{
if ( mesh_component == nullptr )
{
return nullptr;
}

UGBFAbilityInputBufferComponent * aibc = nullptr;

if ( const auto * owner = mesh_component->GetOwner() )
{
aibc = owner->FindComponentByClass< UGBFAbilityInputBufferComponent >();

if ( aibc != nullptr )
{
return aibc;
}
// Check all parent
auto * parent = owner->GetParentActor();
while ( parent )
{
aibc = parent->FindComponentByClass< UGBFAbilityInputBufferComponent >();
if ( aibc != nullptr )
{
return aibc;
}
parent = parent->GetParentActor();
}

// Check all attached actors
TArray< AActor * > actors;
owner->GetAttachedActors( actors, true, true );
actors.RemoveAll( []( AActor * actor ) {
return !Cast< APawn >( actor );
} );
for ( auto & child : actors )
{
aibc = child->FindComponentByClass< UGBFAbilityInputBufferComponent >();
if ( aibc != nullptr )
{
return aibc;
}
}
}

UE_LOG( LogGBF, Error, TEXT( "No Ability Input Buffer Component found" ) );
return nullptr;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
#include "Characters/Components/GBFAbilityInputBufferComponent.h"

#include "Characters/Components/GBFHeroComponent.h"
#include "Characters/Components/GBFPawnExtensionComponent.h"
#include "GAS/Components/GBFAbilitySystemComponent.h"
#include "Input/GBFInputComponent.h"

void UGBFAbilityInputBufferComponent::StartMonitoring( FGameplayTagContainer input_tags_to_check, ETriggerPriority trigger_priority )
{
if ( input_tags_to_check.IsEmpty() )
{
return;
}

Reset();
TriggerPriority = trigger_priority;
InputTagsToCheck = input_tags_to_check;
BindActions();
}

void UGBFAbilityInputBufferComponent::StopMonitoring()
{
RemoveBinds();
TryToTriggerAbility();
Reset();
}

void UGBFAbilityInputBufferComponent::Reset()
{
TriggeredTags.Reset();
InputTagsToCheck.Reset();
BindHandles.Reset();
}

void UGBFAbilityInputBufferComponent::BindActions()
{
if ( InputTagsToCheck.IsEmpty() )
{
return;
}

auto * pawn = GetPawn< APawn >();
if ( pawn == nullptr )
{
return;
}

const auto * hero_component = UGBFHeroComponent::FindHeroComponent( pawn );
if ( hero_component == nullptr )
{
return;
}

for ( auto & input_config : hero_component->GetBoundActionsByInputconfig() )
{
auto * input_component = pawn->FindComponentByClass< UGBFInputComponent >();
if ( input_component == nullptr )
{
continue;
}
for ( auto & tag : InputTagsToCheck )
{
if ( const auto * input_action = input_config.Key->FindAbilityInputActionForTag( tag ) )
{
BindHandles.Add( input_component->BindAction( input_action, ETriggerEvent::Triggered, this, &ThisClass::AbilityInputTagPressed, tag ).GetHandle() );
}
}
}
}

void UGBFAbilityInputBufferComponent::RemoveBinds()
{
auto * pawn = GetPawn< APawn >();
if ( pawn == nullptr )
{
return;
}

if ( const auto * hero_component = UGBFHeroComponent::FindHeroComponent( pawn ) )
{
if ( auto * input_component = pawn->FindComponentByClass< UGBFInputComponent >() )
{
for ( auto & handle : BindHandles )
{
input_component->RemoveBindingByHandle( handle );
}
}
}
}

void UGBFAbilityInputBufferComponent::AbilityInputTagPressed( FGameplayTag input_tag )
{
TriggeredTags.Add( input_tag );
}

bool UGBFAbilityInputBufferComponent::TryToTriggerAbility()
{
if ( TriggeredTags.IsEmpty() )
{
return false;
}

auto * pawn = GetPawn< APawn >();
if ( pawn == nullptr )
{
return false;
}

const auto * pawn_ext_comp = UGBFPawnExtensionComponent::FindPawnExtensionComponent( pawn );
if ( pawn_ext_comp == nullptr )
{
return false;
}

if ( auto * asc = pawn_ext_comp->GetGBFAbilitySystemComponent() )
{
for ( auto & tagged_ability_tag : InputTagsToCheck )
{
auto * tagged_ability = asc->FindAbilityClassWithInputTag( tagged_ability_tag );
asc->CancelAbility( tagged_ability );
}

// Try to activate ability in priority order
FGameplayTag tag = TryToGetInputTagWithPriority();

while ( tag.IsValid() )
{
if ( auto * ability = asc->FindAbilityClassWithInputTag( tag ) )
{
asc->CancelAbility( ability );
bool activated = asc->TryActivateAbilityByClass( ability->GetClass() );
if ( activated )
{
return true;
}
}

tag = TryToGetInputTagWithPriority();
}
}
return false;
}

FGameplayTag UGBFAbilityInputBufferComponent::TryToGetInputTagWithPriority()
{
if ( TriggeredTags.IsEmpty() )
{
return FGameplayTag::EmptyTag;
}

switch ( TriggerPriority )
{
case ETriggerPriority::LastTriggeredInput:
return GetLastTriggeredInput();

case ETriggerPriority::MostTriggeredInput:
return GetMostTriggeredInput();

default:
return FGameplayTag::EmptyTag;
}
}

FGameplayTag UGBFAbilityInputBufferComponent::GetLastTriggeredInput()
{
FGameplayTag first_tag = TriggeredTags[ 0 ];
TriggeredTags.RemoveAll(
[ & ]( FGameplayTag & tag ) {
return tag.MatchesTagExact( first_tag );
} );
return first_tag;
}

FGameplayTag UGBFAbilityInputBufferComponent::GetMostTriggeredInput()
{
TMap< int, FGameplayTag > triggered_tag_map;

// Remove all to get count easily
for ( auto & tag_to_remove : InputTagsToCheck )
{
int count = TriggeredTags.RemoveAll(
[ & ]( FGameplayTag & tag ) {
return tag.MatchesTagExact( tag_to_remove );
} );
triggered_tag_map.Add( count, tag_to_remove );
}

// Get most triggered input
int max = -1;
for ( auto & i : triggered_tag_map )
{
if ( i.Key > max )
{
max = i.Key;
}
}

FGameplayTag most_triggered_tag = triggered_tag_map.FindAndRemoveChecked( max );

// Sort to keep order if first ability fails
triggered_tag_map.Remove( 0 );
triggered_tag_map.KeySort(
[]( const int & a, const int & b ) {
return a > b;
} );
for ( auto & i : triggered_tag_map )
{
TriggeredTags.Add( i.Value );
}

return most_triggered_tag;
}
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,11 @@ void UGBFHeroComponent::ClearAbilityCameraMode( const FGameplayAbilitySpecHandle
}
}

TMap< const UGBFInputConfig *, TArray< uint32 > > UGBFHeroComponent::GetBoundActionsByInputconfig() const
{
return BoundActionsByInputConfig;
}

void UGBFHeroComponent::OnRegister()
{
Super::OnRegister();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,24 @@ FGameplayAbilitySpecHandle UGBFAbilitySystemComponent::FindAbilitySpecHandleForC
return FGameplayAbilitySpecHandle();
}

UGameplayAbility * UGBFAbilitySystemComponent::FindAbilityClassWithInputTag( FGameplayTag input_tag )
{
if ( !input_tag.IsValid() )
{
return nullptr;
}

for ( const auto & ability_spec : ActivatableAbilities.Items )
{
if ( ability_spec.Ability && ability_spec.DynamicAbilityTags.HasTagExact( input_tag ) )
{
return ability_spec.Ability;
}
}

return nullptr;
}

void UGBFAbilitySystemComponent::OurCancelAllAbilities()
{
static const auto GameplayTagContainer = FGameplayTagContainer::CreateFromArray(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#pragma once

#include "Characters/Components/GBFAbilityInputBufferComponent.h"

#include <Animation/AnimNotifies/AnimNotifyState.h>
#include <CoreMinimal.h>

#include "GBFAnimNotifyState_InputBuffer.generated.h"

class UGBFAbilityInputBufferComponent;

UCLASS( DisplayName = "Ability Input Buffer Window" )
class GAMEBASEFRAMEWORK_API UGBFAnimNotifyState_InputBuffer : public UAnimNotifyState
{
GENERATED_BODY()

public:
void NotifyBegin( USkeletalMeshComponent * mesh_component, UAnimSequenceBase * animation, float total_duration, const FAnimNotifyEventReference & event_reference ) override;
void NotifyEnd( USkeletalMeshComponent * mesh_component, UAnimSequenceBase * animation, const FAnimNotifyEventReference & event_reference ) override;

protected:
UFUNCTION( BlueprintNativeEvent )
UGBFAbilityInputBufferComponent * GetAbilityInputBufferComponent( const USkeletalMeshComponent * mesh_component ) const;

private:
UPROPERTY( EditAnywhere )
ETriggerPriority TriggerPriority;
UPROPERTY( EditAnywhere, Meta = ( Categories = "Input" ) )
FGameplayTagContainer InputTagsToCheck;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#pragma once

#include "Characters/Components/GBFPawnComponent.h"

#include <CoreMinimal.h>

#include "GBFAbilityInputBufferComponent.generated.h"

UENUM( BlueprintType )
enum class ETriggerPriority : uint8
{
LastTriggeredInput = 0 UMETA( DisplayName = "Last Triggered Input" ),
MostTriggeredInput = 1 UMETA( DisplayName = "Most Triggered Input" )
};

UCLASS( Blueprintable )
class GAMEBASEFRAMEWORK_API UGBFAbilityInputBufferComponent : public UPawnComponent
{
GENERATED_BODY()
public:
UFUNCTION( BlueprintCallable )
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UFUNCTION before other functions, except for constructor and inline functions

void StartMonitoring( FGameplayTagContainer input_tags_to_check, ETriggerPriority trigger_priority );

UFUNCTION( BlueprintCallable )
void StopMonitoring();

protected:
void Reset();
void BindActions();
void RemoveBinds();
void AbilityInputTagPressed( FGameplayTag input_tag );
bool TryToTriggerAbility();
FGameplayTag TryToGetInputTagWithPriority();
FGameplayTag GetLastTriggeredInput();
FGameplayTag GetMostTriggeredInput();

ETriggerPriority TriggerPriority;
FGameplayTagContainer InputTagsToCheck;
TArray< FGameplayTag > TriggeredTags;
TArray< uint32 > BindHandles;
};
Loading