How to check whether an Input Action made in Blueprints is triggered (or grabs the value) from a C++ class that doesn't handle input for MOBA style ability indicator

I have a UAbilityComponent class which also handles setting the visual indicator and there will be subclasses that inherit from it (might be BP or C++). My reasoning was trying to keep my classes as modular as possible.

UInputAction* UAbilityComponent::GetSetDestinationInputAction() const
{ 
    return SetDestinationInputAction; 
}

void UAbilityComponent::SetSetDestinationInputAction(UInputAction* NewSetDestinationInputAction) 
{ 
    SetDestinationInputAction = NewSetDestinationInputAction; 
}

// Run the timer to call OnDecision() to check whether the button has been clicked to place the indicator, or keeps moving indicator in certain range via mouse movement
void UAbilityComponent::SetTimerOnDecision()
{
	if (GetWorld())
		GetWorld()->GetTimerManager().SetTimer(DecisionTimerHandle, this, &UAbilityComponent::OnDecision, 0.35f, true, 0);
}

void UAbilityComponent::OnDecision()
{
	if (SetDestinationInputAction == nullptr) {
		if (GetWorld())
			GetWorld()->GetTimerManager().ClearTimer(DecisionTimerHandle);

		return;
	}

	bool Get_IA_Destination = true;

	if (Get_IA_Destination) 
	{
		if (GetWorld())
			GetWorld()->GetTimerManager().ClearTimer(DecisionTimerHandle);

		SetTargetPosition(GetPlayerCursorLocation());
	}
	else
	{
		SetIndicatorDecalAtLocation();
	}
}

FVector UAbilityComponent::GetPlayerCursorLocation() const
{
	FHitResult HitResult;
	GetWorld()->GetFirstPlayerController()->GetHitResultUnderCursor(ECollisionChannel::ECC_Visibility, true, HitResult);
	return HitResult.Location;
}

void UAbilityComponent::SetIndicatorDecalAtLocation()
{
	if (GetOwner() == nullptr || IndicatorDecalComponent == nullptr)
		return;

	FVector PlayerCursorLocation = GetPlayerCursorLocation();
	FVector PlayerCharacterLocation = GetOwner()->GetActorLocation();

	if (FVector::Dist(PlayerCursorLocation, PlayerCharacterLocation) > GetAbilityRange())
	{
		FHitResult Hit;
		FVector End = PlayerCharacterLocation + UKismetMathLibrary::FindLookAtRotation(PlayerCharacterLocation, PlayerCursorLocation).Vector() * GetAbilityRange();
		const FCollisionQueryParams CollisionQueryParams;
		const FCollisionResponseParams CollisionResponseParams;

		GetWorld()->LineTraceSingleByChannel(Hit, PlayerCharacterLocation, End, ECollisionChannel::ECC_Visibility, CollisionQueryParams, CollisionResponseParams);

		PlayerCursorLocation = Hit.Location;
	}

	GetIndicatorDecalComponent()->SetWorldLocation(PlayerCursorLocation);
}

However, there is one problem, I can’t figure out how to grab whether the input action is triggered or not, or its value. UEnhancedInputComponent::GetBoundActionValue is one option, I’m pretty sure, but from what I understand, you’d have to use SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) to then cast UInputComponent to UEnhancedInputComponent.

But the problem is my ability component isn’t a character, so I’m not really sure what to do now? I can grab Get_IA_SetDestination in my BP subclass. But I’d prefer to have this functionality in the parent class since they all share this aspect anyways.

To give context, all of my input actions and my input mapping contexts have been made in BP, and my player controller is also BP (used the BP template then added on C++ classes). How I’m handling this is that in my player controller BP, I have input actions that handle triggering the abilities (1, 2, 3, 4), then what happens is that the timer for OnDecision() is set, and every . something seconds, it checks whether to see if IA_SetDestination is clicked. IA_SetDestination and the input actions that handle the triggering of the abilities are all in one IMC also made in BP.

Thanks to anyone who answers.

Updated the code.

GameplayAbility is still using the old Input. So in the ability component you are looking for AbilityLocalInputPressed and AbilityLocalInputReleased. These are the two functions you want to bind to your input system.

The Lyra and the Valley of the Ancient projects have some custom Ability Components you can look at to see how they work around that issue.

could you maybe call a UFUNCTION(BlueprintCallable) at some point during the execution of the pressed released triggered as needed?

if your goal is the least amount of coupling possible then you could setup Delegates for the InputAction to bind to as needed.

I should’ve clarified, I’m not using GAS since this isn’t multiplayer, so I wasn’t sure why I’d use it. Thanks for the input though, I will definitely keep it in mind. I might be misunderstanding what you’re saying though?

I think I should be able to do that, if you don’t mind, can you give an example of how it would look like? What I’m having trouble is understanding how to conceptualise it while running a timer. (Also realised I definitely set the timer incorrectly there as well, not on my computer unfortunately, so I’ll update the code for that in a few hours, setting the timer should be called in a different function I’m pretty sure.) Thank you for your response.

Ok, I updated the code to give more info.

If you’re talking about delegates in this case, do you mean using FTimerDelegate or just a regular delegate. Just wanted to make sure in case I misunderstood.

for Example if I wanted to implement the IA_Attack1 in my BP_Character derived class (for this example the class is called ARuinsCharacter so the BP_RuinsCharacter)
and ARuinsCharacter.h declares

public:
    UPROPERTY(VisibleAnywhere, BlueprintReadWrite)
    TObjectPrt<URUNActionQueue> ActionQueue;

    //...

    UFUNCTION(BlueprintCallable)
    EReturnEnum BeforeAction();

    UFUNCTION(BlueprintCallable)
    void PostAction();

the could also be BlueprintNativeEvents if you want part of the functionality to be definable in Blueprints but other parts defined in C++, or BlueprintImplementableEvent if you wanted the functionality to exist in blueprints, but the promise of the function to be defined in C++.

so then in the blueprint of the character I can do:

so if you would imagine that these BeforeAction and PostAction do something specific on the Character class, and then the ActionQueue->PeekCurrent could be a member function on whatever you have for the container holding your UAbilityComonent objects.

an implementation like this would require your Character class to HaveA container to your UAbilityComonent, but this still allows for re-usability of the UAbilityComponent Container and UAbilityComponent as they do not nessesarily depend on any of the code in the Character Class (they just need to have functions that are exposed enough to be called with appropriate variable types

for a delegate system I mean more of a regular delegate you would still need to define the Delegate Bind point somewhere that makes sense. I tend to not favor Delegates myself (at best they are calling out to something that is rarely needed and hard to get. At worst it is just an obfuscated function call and doing enough of them is coupling in and of itself) I am mentioning them more out of completeness, and not trying to pigeon whole your design.

2 Likes

Thank you, I think that makes sense to me. In this case, I think that if I were to implement what you were saying, I might have to modify it somewhat, and will definitely keep this in mind for refactoring. With PeekCurrent, from what I understand, let’s say you have several abilities, and your container is an array, you can create a parameter that takes in the index and then call the correct ability from there, right?

I’m still slightly confused as to why BeforeAction() and PostAction() would be part of the character, and not the ability itself?

Let’s just say the ability has stages, you activate the ability, then BeforeAction() would be called, let’s say to set up something, then next stage would be checking to see where the indicator is on screen (i.e. choosing where the ability would be activated) => then set the indicator which completes the action => then PostAction() will occur, which is activating the effects of the ability.

I know you’re not trying to pigeonhole my design, :slight_smile: , I’m just curious to this thought process further, trying to learn multiple techniques and mindsets. About EReturnEnum, would it be used to determine what ability is being activated? Or does it return whether an ability should be activated if at all.

I ended up finding out that you can grab the UEnhancedInputLocalPlayerSubsystem in C++, so I’m curious if you can grab the value of an action that way. Expose the UInputAction variable to BP, pass that UInputAction in, grab UEnhancedInputLocalPlayerSubsystem, then check the value of that constantly?

void UAbilityComponent::PreDecision_Implementation(UDecalComponent* NewIndicatorDecalComponent, const float& MaxIndicatorDistance, UInputAction* NewSetDestinationInputAction)
{
	SetIndicatorDecalComponent(NewIndicatorDecalComponent);
	SetAbilityRange(MaxIndicatorDistance);
	SetSetDestinationInputAction(NewSetDestinationInputAction);

	if (EnhancedInputLocalPlayerSubsystemSubsystem == nullptr) 
	{
		EnhancedInputLocalPlayerSubsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(GetWorld()->GetFirstPlayerController()->GetLocalPlayer());
		EnhancedPlayerInput = EnhancedInputLocalPlayerSubsystem->GetPlayerInput();
	}
}

void UAbilityComponent::SetTimerOnDecision()
{
	if (GetWorld())
		GetWorld()->GetTimerManager().SetTimer(DecisionTimerHandle, this, &UAbilityComponent::OnDecision, 0.35f, true, 0);
}

void UAbilityComponent::OnDecision()
{
	if (EnhancedPlayerInput == nullptr|| SetDestinationInputAction == nullptr) {
		if (GetWorld())
			GetWorld()->GetTimerManager().ClearTimer(DecisionTimerHandle);

		return;
	}

	if (EnhancedPlayerInput->GetActionValue(SetDestinationInputAction).Get<bool>())
	{
		if (GetWorld())
			GetWorld()->GetTimerManager().ClearTimer(DecisionTimerHandle);

		SetTargetPosition(GetPlayerCursorLocation());
	}
	else
	{
		SetIndicatorDecalAtLocation();
	}
}

I ended up trying to test that but for some reason, when I try to make a variable that references the subclass in BP inside of the player controller, it’s not valid, so it doesn’t run anything, do you have any idea as to why (sorry by the way, this has been something I’ve been having issues with, for instance I created a UMovementComponent C++ class, add it to my character BP, it works there, but if I attempt to reference it inside the animation BP (grabbing it as a variable from my character BP), it’s null. I don’t know if it’s because I originally started with a BP template, then added C++ classes to it, instead of starting with a C++ template?

I was thinking of using OnComponentActivated instead, but I don’t think that’ll do anything, I feel like I’m initialising my actor components in BP incorrectly somehow.

Thank you for your response by the way, I really appreciate it.

this is a very stripped down version of my implementation the BeforeAction() and PostAction() are part of the character because they do stuff like set if the character is Attacking, what the Priority of the CurrentAction is (really one higher so that the player can’t spam the same action for animation stacking), if the action can be interrupted, or if the action grants IFrames. in reality for me the PostAtcion() would be called following a PlayMontage()::OnNotifyEnd() and I am kind of considering moving BeforeAction() to the PlayMontage()::OnNotifyBegin() but this is my Implementation and I understand the consequences of being a bit more tightly coupled (:slight_smile: :slaps_own_hand: ) .

in the example I provided they are on the Character because with InhanceInputSubsystem only APawns possessing a Controller registered to the Subsystem will have InputActions bound to Delegates received from the Subsystem for the CurrentlyActive_MappingContext (this is why you can’t just put Event_Key_F() in any random Actor it should at least be a pawn, and should have a possessed controller registered.

the reason I included ActionQueue->PeekCurrent() was more of a demonstration calling something regarding a held object.

regarding your thought process sure you just need to keep track of your flow control

in my example the EReturnValue is an enum in my FQueuedAction to go get an animation from RepositorySubsystem:GameInstanceSubsystem(this way I don’t have to pass/return the full struct and just what amounts to a uint8/int32) so my character doesn’t need to have 30-50 special animations (not in the Animation Blueprint), or potentially dozens of InventoryItems each sitting around when at any given time it only needs the current Animation in PlayMontage() the next animation in the ActionQueue, and the few InventoryItems they have equipped where most any value being given as an arguement or return should have some meaning.

the UInputAction is a pointer that is bound to the delegate. the FInputActionValue is what is being sent to the registered listener from the Subsystem (the Vector2D, bool, or so on), so in my mind to piggyback and get the same FInputActionValue the other class would also need to be listening to the same delegate broadcast requiring it to possess a controller and so on, but it would need to register the same controller so it gets the commands. this would be very circular logic, and potentially doing things multiple times (the price we pay for rebind-able controls, or being able to dynamically change what buttons do like when controlling the character, or in a menu)

for your UMovementComponent* issue, and when things exist or don’t exist

keep in mind that this only accounts for things that are in the constructor, if you are assigning aUActorComponent* after the constructor it may call PostInitializeComponents() again. BeginPlay() is relative to that Actor most of the time you can treat BeginPlay as the actor saying: “I and everything I should start with exits”. as long as the MovementComponent is added in the constructor (which this is something that happens in the ACharacter class not in Pawn because there is a small expectation of AI controller) it should be there by no later then PostInitializeComponents() completes.

There are 2 MovementComponents to an ACharacter a UPawnMovementComponent (most intended for AI Controllers, but if need be can be used for actual controller; this would need to be assigned by default) and a UCharacterMovementComponent (most intended for player input things though you could throw an AI controller at it if you really wanted to) but the specific one is accessed from GetMovementCompont()

when you add C++ to your project only really makes a difference for having to potentially clean up Template Blueprints, and for pre-5.2 needing to add a couple Modules in the build.cs (EnhancedInputSubsystem and Niagra are not exposed to C++ unless added in 5.1 or before even though they exist on the Blueprint side :frowning: )

Thank you for your detailed explanation as well as the documentation. It makes a lot of sense now. I’m curious about your thoughts on what’s written below, on whether this is the right logic.

I’m trying to learn how to do a system similar to Atlas Reactor but singleplayer (no way in heck can I do multiplayer at my skill level), the game has multiple phases, decision → prep → dash → blast → move, where all abilities happen simultaneously, but they have different phases and will activate during those phases.

My idea was to have a class that handles the turn queue (not sure what it would inherit from, GameModeBase? Then use that class, make delegates then bind them to the corresponding abilities. And then maybe have a function somewhere like:

{
	switch (AbilityPhase)
	{
	case EPhase::Prep:
		TurnQueue->OnPrepSignature.AddDynamic(this, &UAbilityComponent::Resolution);
		break;
	case EPhase::Dash:
		TurnQueue->OnDashSignature.AddDynamic(this, &UAbilityComponent::Resolution);
		break;
	case EPhase::Blast:
		TurnQueue->OnBlastSignature.AddDynamic(this, &UAbilityComponent::Resolution);
		break;
	case EPhase::Move:
		TurnQueue->OnBlastSignature.AddDynamic(this, &UAbilityComponent::Resolution);
		break;
	}
}

But I guess this system would be a subsystem? I already made sure to have two IMC that I’ll switch out for ‘Atlas Reactor mode’ and normal mode.

I also just realised, I wouldn’t actually even need to use SetTimer() inside of UAbilityComponent itself to check where to put the indicator (while you’re in decision mode). Because presumably in the decision mode, you’d have a timer that’s counting down, from the class that handles the turns, you’ll set the timer in there to check your mouse movement and choices.

This

would instead all be handled by APawn/ACharacter/AController since you said only pawns can have controllers.

FVector UAbilityComponent::GetPlayerCursorLocation() const
{
	FHitResult HitResult;
	GetWorld()->GetFirstPlayerController()->GetHitResultUnderCursor(ECollisionChannel::ECC_Visibility, true, HitResult);
	return HitResult.Location;
}

void UAbilityComponent::SetIndicatorDecalAtLocation()
{
	if (GetOwner() == nullptr || IndicatorDecalComponent == nullptr)
		return;

	FVector PlayerCursorLocation = GetPlayerCursorLocation();
	// CÆ°ÌŁ ly của indicator nĂȘn tÆ°ÆĄng Ä‘ĂŽÌi, 0 là điÌŁa điĂȘ̉m của nhĂąn vĂąÌŁt
	FVector PlayerCharacterLocation = GetOwner()->GetActorLocation();

	if (FVector::Dist(PlayerCursorLocation, PlayerCharacterLocation) > GetAbilityRange())
	{
		FHitResult Hit;
		FVector End = PlayerCharacterLocation + UKismetMathLibrary::FindLookAtRotation(PlayerCharacterLocation, PlayerCursorLocation).Vector() * GetAbilityRange();
		const FCollisionQueryParams CollisionQueryParams;
		const FCollisionResponseParams CollisionResponseParams;

		GetWorld()->LineTraceSingleByChannel(Hit, PlayerCharacterLocation, End, ECollisionChannel::ECC_Visibility, CollisionQueryParams, CollisionResponseParams);

		PlayerCursorLocation = Hit.Location;
	}

	IndicatorDecalComponent->SetWorldLocation(PlayerCursorLocation);
}

SetIndicatorDecalAtLocation() would be inside of the UAbilityComponent though and so would the UDecalComponent, I think. To me it doesn’t really make sense it would be attached to the character if it changes based on what ability it is.


I do have one problem though when I was trying to test setting my indicator, I can’t figure out though why it’s not setting the decal component in the world correctly when I move my mouse around, if the range is 0.0 it should be below the character. I have a timer that checks SetIndicatorDecalAtLocation() every 0.35 seconds and it runs. I was looking at the top down template for 4.27 and it was only adding a decal component to the character itself, not sure if that’s what’s causing the issue.

If I click on a specific spot it shows the decal but it doesn’t look right (should be circular). I can’t grab the decal component and reposition it despite it being editable anywhere (public), so I’m not sure if that’s the issue. I also tried adding a USceneComponent then attached the decal component to it, still can’t move either of them.



Mhm, I am confused why it would call PostInitializeComponents() in this situation. From what I understand in BP, when you add a component on the side, it’s the equivalent of CreateDefaultSubobject in C++ inside the constructor, so shouldn’t it be InitializeComponents()? Thank you, sorry for my late response, I got busy.

this is becoming a conversation of your games implementation, and meandering away from your initial question. Has the initial question been answered, or a reasonable alternative been proposed?

on the side of multiplayer for Broadcast-Response style gameplay Unreal is already setup, if you want Lock-step determinism, or Rollback that would take some reworking of the engine to different degrees. To just get networking to function is already built in, you just need to identify which variables need to be replicated, and what functions need to be sent to the server for propagation.

The UInputAction bindings typically are bound once, though you “CAN” unbind them (the best way I could find to was needing a bindingID which I stopped there and didn’t pursue it) it is easier and less error prone to use multiple UInputActions bound to the same/similar functions though different MappingContexts and/or to just put your logic in the bound function.
if you want the same button to do different things at different points here are a few different methods you can look at

imagine each wire coming directly off a Blueprint:InputAction_[input]_Event() as a bound function call.

for rational as to why the Ability Decals would be attached or parented to the Pawn: if the location is relative to a given location (so it has a limited radial distance, or it can only be within a given arc length of the forward) then you you either need to cash that information either through a pointer to the Pawn using the ability (so you would have like a TObjectPtr<AMyCastingCharacter> AbilityUser floating around on the ability, and you would need to assign it somehow), or through a attach OnStartCasting GetParent()->GetLocation() and/or GetParent()->GetForward() depending on the need, and manipulating it.

  • the method with the held pointer you would constantly have to check if the Actor/Pawn/Character != nullptr to avoid access violations
  • for the implementation using attachment you only really need to check if the Parent != nullptr at the point of attachment, as destroy traverses up the Hierarchy to the highest node necessary then prunes bottom to top.

InitializeComponents() and PostInitializeComponents() are called by the engine on the object (because of the types of things done in them); like I have used PostInitializeComponents() as a last chance safeguard to ensure that a component the designer was supposed to attach was attached with at least a default version just to avoid access violations because again not every designer will remember “if the variable is a pointer to something that might not exist make sure it isValid before doing stuff with it”
or I will use the PostInitializeComponents() to register things with Singleton SubSystem Managers as I know the manager “should” exist, and using them can prevent the slow down of “GetActorsByClass()” as part of the trade off between semi-persistent memory and momentary performance hits. “frame rate: higher is better, but consistent is best” (most Gamer’s forget the last part though)

Alright, thank you for the explanation, I’m pretty sure my question has been answered, looking back at it (so I’ll mark the solution, just did). My mind tends to wander to multiple things at once so my bad for that.

I will then put the decal component in the pawn instead, your explanation is clear to me. Again, sorry, my mind jumps all over the place. I will save these links for later to read. Have a good day and rest of the week.

Again, sorry for meandering.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.