[How To] Unbind an action from the input component

The Input Component offers us only one way of removing an action binding: by index.

Here’s the naive (and buggy) way to do this
[SPOILER]
I put it in a spoiler so people just trying to find the answer don’t copy and paste this without reading, because this does not work.



InputComponent->BindAction("MyAction", IE_Pressed, this, &AMyActor::MyFunction);
int32 bindingIndex = InputComponent->GetNumActionBindings() - 1;


Because we just added the binding, naturally, its index will be the last index in the bindings array, so we just get the length of the array and subtract 1 from it. So far so good.
Now to remove the binding, we would do:



InputComponent->RemoveActionBinding(bindingIndex);


This “works”. The problem is: if another binding gets removed before we remove our binding, our binding’s index will be shifted by -1, so we will actually end up removing the wrong binding.

PS: I also have previously tried to locate it by storing a pointer to the created FInputActionBinding, but this will lead to problems because the way dynamic arrays work, when an element is removed the others are shifted in memory so you might end up having an invalid pointer or pointing to a different element.
[/SPOILER]
The correct way to do it
So to circumvent this problem we do this:




FInputActionBinding myActionBinding; //This should be declared in your class!




myActionBinding = FInputActionBinding("MyAction", IE_Pressed);
myActionBinding.ActionDelegate.BindDelegate(this, &AMyActor::MyFunction);

&InputComponent->AddActionBinding(myActionBinding);


and to remove it, we loop through all bindings in our input component until we find a binding that has the same action name and same delegate as the one we’re looking for:



for (int i = 0; i < InputComponent->GetNumActionBindings(); i++)
{
   FInputActionBinding binding = InputComponent->GetActionBinding(i);
   if (CompareInputActionBindings(binding, myActionBinding))
   {
      playerController->InputComponent->RemoveActionBinding(i);
      i--;
      continue;
    }
 }


Here’s the function I used to compare two FInputActionBindings



bool CompareInputActionBindings(FInputActionBinding lhs, FInputActionBinding rhs)
{
    return lhs.ActionDelegate.GetDelegateForManualSet().GetHandle() == rhs.ActionDelegate.GetDelegateForManualSet().GetHandle() &&
        lhs.GetActionName() == rhs.GetActionName();
}


I hope to have helped someone

4 Likes

Actually your second example has a similar and even worse problem than the first. Since you are storing a pointer to the FInputActionBinding, but the actual value pointed to is in the list of action bindings. When an element is removed from the list, it may (and probably will) shift elements in memory to fill the now free space in the list, thereby making you pointer invalid (point to the wrong thing, or invalid memory). Generally it is very unsafe to store a reference or pointer to an element in contained in any kind of dynamic memory container such as a list. When elements are added to the list, the list may allocate entirely new memory for the entire list and move everything over (this is how dynamic memory works), so in this case all your pointers would become invalid and not just some. This creates extremely difficult to debug memory errors that don’t seem to make sense.

Oh God. When I wrote this post I had no idea about this (I just learned about a week ago how dynamic arrays work). You probably saved me and a bunch other people from a lot of headaches, thank you!
I guess I’ll have to find another way of removing a binding from the input component… If I find another way to do it I’ll update my post.

Yeah, I couldn’t really find a good way to do it. Personally I’m just binding it then putting a Boolean in the callback for when it shouldn’t be called. You can also compare the action name if you really want to remove it. If you have multiple callbacks bound to the same action name however, this might not be what you want.

Ok I figured it out (it was pretty easy actually). Here’s how I did it:

Instead of storing a pointer to the FInputActionBinding returned from AddActionBinding, I stored the FInputActionBinding and then searched for it in the loop using a comparison. I had to make my own comparison function for FInputActionBinding and had to do some digging in the engine code to find out how to compare two delegates, but I eventually found a way to do it. I’ve updated the original post with the code and everything.

Again, thanks a LOT for pointing this out. :slight_smile:

This is awesome! I had a lot of callback,wrapper, and boolean checks to disable a bunch of input. I think this is much more efficient and cleaner. You could just remove it from the Input Binding when you don’t need it. Do you also know how to handle Input Axis Binding? It seems different from the action bindings.

Thank you very much! :slight_smile:

when you remove a certain input and binding it back again won’t work anymore. so I guess I go back to boolean checks and callbacks. I hope unreal fixes this where we can bind and unbind input dynamically. It makes the code much neater that way. although binding axis action is much easier. You just add and remove from the InputComponent->AxisBinding array. Upon adding it to the array it automatically overrides the input of the character.

As an update for anyone looking, You don’t need to loop through the action bindings anymore to unbind, you can just do:


InputComponent->RemoveActionBindingForHandle(myActionBinding.GetHandle());

1 Like

For this approach to work you need to assign the return value of AddActionBinding:

myActionBinding = &InputComponent->AddActionBinding(myActionBinding);

For anyone who’s still having trouble with this. Here is my solution:

It’s important to know that for my scenario, the UInputAction is not even in the Control Mapping and I want to basically replace an action in the mapping with this new action. The background for the scenario is that I have weapon pickups, and each weapon has a different firing mode. Some might be a hold weapon, some might be a down weapon, some might be pressed, some might be pressed and released, some might be down and released, etc. So each weapon has an action associated with it and no way am I putting all of those actions and key mappings in my control mapping. Furthermore I think the player would hate me if I made them need to remap all of those keys for every different action if they wanted to change controls. SO, here is my solution:

void APlayerBase::SetFireInputToAction_Implementation(UInputAction* action) {

	
	TArray<FEnhancedActionKeyMapping> mappings = baseControls->GetMappings();

	

	//delete previous weapons action
	for (FEnhancedActionKeyMapping& map : mappings) {

		if (map.Action == fireAction) {

			

			//make mapping to the new action with each key
			baseControls->MapKey(action, map.Key);

		}

	}
	//now unmap the current fire action
	baseControls->UnmapAllKeysFromAction(fireAction);

	//need to rebuild the control mapping
	UEnhancedInputLibrary::RequestRebuildControlMappingsUsingContext(baseControls);

	

	int32 previousAction = -1;
	for (int i = 0; i < playerEnhancedInput->GetNumActionBindings(); i++) {

		if (playerEnhancedInput->GetActionBinding(i).ActionDelegate.IsBoundToObject(fireAction)) {

			previousAction = playerEnhancedInput->GetActionBinding(i).GetHandle();
			break;
		}

	}

	//remove old binding
	playerEnhancedInput->RemoveActionEventBinding(previousAction);

	//update what the current fire action is
	fireAction = action;

	//make new binding
	playerEnhancedInput->BindAction(fireAction, ETriggerEvent::Triggered, this, &APlayerBase::fireInput);

}
1 Like

This thread helped a lot, but if anyone is still a bit confused the following worked for myself:

FEnhancedInputActionEventBinding& EventBinding = EnhancedInputComponent->BindAction(IA, ETriggerEvent::Triggered, this, &ThisClass::Function);

EnhancedInputComponent->RemoveBindingByHandle(EventBinding.GetHandle());

I just store the Handle myself and then remove the binding when the object is destroyed:

//store in your class to use wherever
uint32 BindingHandle = EventBinding.GetHandle();