[Request] Axis Mappings using modifier keys

I think Axis Mappings should also have the option to use modifier keys, the same as Action Mappings do.

In my in-game level editor I’m using Mouse Wheel Axis to zoom in and out. It would be nice if I could bind Mouse Wheel Axis + Shift to raise or lower the camera height. Currently I have to bind that to Q/E. I could probably hard code the modifier, but I don’t like doing that.

Hi Bajee,

Thank you for your request. I have entered a feature request, UE-20870, to be considered by the development staff.

Hi Bajee, while this is an interesting idea it isn’t something we are likely to be able to prioritize any time in the near future.

If you were interested in tackling it yourself I thought I could give you a couple of pointers as to where the changes would need to be made:

In PlayerInput.h FInputAxisKeyMapping would need to add the modifier booleans similar to FInputActionKeyMapping and the FInputAxisMappingCustomization would need to modified to display them in InputStructCustomization.cpp again similar to how the input action is done.

In UInputComponent you would need to change FInputAxisKeyBinding to have an FInputChord instead of a FKey.

UK2Node_InputAxisKeyEvent and UK2Node_GetInputAxisKeyValue would also need to have the modifier keys added to the settable properties and then pushed on to the input component in RegisterDynamicBinding. You’d also want to update GetNodeTitle to display the modifier key elements as is done in UK2Node_InputKey (wouldn’t be the worst idea to factor that out to a shareable place, not sure where would be best off the top of my head).

In UPlayerInput::DetermineAxisValue before adding the GetKeyValue to AxisValue you would need to have a modifier key check block similar to the one you see in GetChordsForKeyMapping().

I’ve probably forgotten some piece that would need to be updated, but that should be the basics. And if you do take it on, we’d love to see it as a pull request to see if we could integrate it back in to the engine.

I’m not sure why it “isn’t something we are likely to be able to prioritize any time in the near future”. This is basic core functionality that should have been implemented when action key modifiers were implemented. This is a huge fundamental flaw in the engine and could be easily addressed, but not likely to be prioritized? Why??? I’ve been waiting for this to be added to the engine for years. What are UE4 engineers working on that is so much more important than taking a few hours and implementing core functionality that should have been there from the start?? There must be some hidden internal reason why this was not implemented that I don’t understand?..

I’m posting this so folks who are trying to figure out how to do this on their own, without having to modify the engine itself, can try it. I really wanted to be able to support modifiers with both axis and action mappings using a single solution, so this is what I came up with. It’s working well for me, but may require customization. Sorry, I do not have a Blueprints implementation available.

Note: the code is not totally finished, I was still in the middle of tweaking it while developing it. It has major room for improvement, etc. The point is to share the general concept.

For performance reasons: whenever the game loads, and/or the user updates their bindings, I keep a TMap updated with key and modifier information.
The code is very specific to how I set things up, but it gives you an idea:

void USomeSettingsClass::RefreshKeyboardCommandKeyCache()
{
  if (!GameEngineInputSettings)
    return;

  const auto SelectedProfile = GetSelectedProfile();

  auto KeyboardDevice = SelectedProfile.Controllers.Keyboard;

  KeyboardCommandKeyCache.Empty();

  for (const auto Binding : KeyboardDevice.Bindings)
  {
    if (Binding.FKeyValue.Len() <= 0)
      continue;

    if (Binding.WithShift || Binding.WithCtrl || Binding.WithAlt || Binding.WithCmd)
    {
      auto CacheStruct = FAscSettingsKeyboardCommandKeyCacheStruct();

      CacheStruct.BindingMappingType = Binding.BindingMappingType;
      CacheStruct.BindingType = Binding.BindingType;
      CacheStruct.WithShift = Binding.WithShift;
      CacheStruct.WithCtrl = Binding.WithCtrl;
      CacheStruct.WithAlt = Binding.WithAlt;
      CacheStruct.WithCmd = Binding.WithCmd;

      KeyboardCommandKeyCache.Add(FKey(FName(*Binding.FKeyValue)), CacheStruct);
    }
  }
}

Then, I have a utility method that checks the for validity of the key combinations. The TMap is essential for performance.
Note: You may want to pass in the axis binding name as a parameter; or however you want to handle it. My implementation is very specific to my game, so removed.

bool UYourClass::IsValidKeyboardCommand(
  AAscPlayerController *PlayerController,
  float Val,
  bool IsInputValid,
  bool IsActionBinding,
  FKey FKeyValue)
{
  if (!IsInputValid)
    return false;

  if (PlayerController == null || PlayerController->IsPendingKillPending())
    return false;

  if (PlayerController->PlayerInput == null)
    return false;

  auto ActualKeyValue = FKeyValue;

  if (!IsActionBinding)
  {
    const auto AxisName = TEXT(""); // Your axis binding name passed here as a parameter? or however you want to handle it
    const auto KeysForAxis = PlayerController->PlayerInput->GetKeysForAxis(FName(*AxisName));

    for (auto Item : KeysForAxis)
    {
      if (Item.Scale != Val)
        continue;

      if (!PlayerController->IsInputKeyDown(Item.Key))
        continue;

      ActualKeyValue = Item.Key;

      break;
    }
  }

  FAscSettingsKeyboardCommandKeyCacheStruct FoundCacheData;

  if (KeyboardCommandKeyCache.Contains(ActualKeyValue))
    FoundCacheData = KeyboardCommandKeyCache[ActualKeyValue];
  else
    return true;

  const auto IsShiftKeyDown = PlayerController->PlayerInput->IsShiftPressed();
  const auto IsCtrlKeyDown = PlayerController->PlayerInput->IsCtrlPressed();
  const auto IsAltKeyDown = PlayerController->PlayerInput->IsAltPressed();
  const auto IsCmdKeyDown = PlayerController->PlayerInput->IsCmdPressed();

  auto Result = true;

  if (FoundCacheData.WithShift && !IsShiftKeyDown)
    Result = false;

  if (FoundCacheData.WithCtrl && !IsCtrlKeyDown)
    Result = false;

  if (FoundCacheData.WithAlt && !IsAltKeyDown)
    Result = false;

  if (FoundCacheData.WithCmd && !IsCmdKeyDown)
    Result = false;

  return Result;
}

Finally, this is how it would look:

void APlayerController::MoveForwardBackward(float Val)
{
  const auto IsValidKeyboardCommand = UYourClass->IsValidKeyboardCommand(this, Val, Val != 0.f, false);

  if (!IsValidKeyboardCommand)
    Val = 0.f; // do whatever

  // rest of your code
}

Note: the solution will also work for action bindings.

void APlayerController::MyAction(FKey ActualFKey)
{
  const auto IsValidKeyboardCommand = UYourClass->IsValidKeyboardCommand(this, 0, true, true, ActualFKey);

  // rest of your code
}