The typical UE4-recommended setup is to use multiple PlayerControllers and switch between them.
I prefer having one PlayerController as my “InputController”. I have turned my PlayerController into a complete input management system with events though so it would be too difficult to use as an example.
If you’d prefer to keep input handled by one PlayerController, rather than switching out to other PlayerControllers, you could store a state for the input mode representing each input mode/type (Vehicle/Pilot, Character, Build/Construction) in the PlayerController you are using for input. This way, when you switch input mode you just set the current input mode state; when you receive input through binds, you can then do a check for what the current input mode is and call the correct function/event.
Don’t be hesitant to make input binds that may be bound to keys used by another input mode as well. If you want to make binds for each input mode, you simply do a check on the current input mode state when the bound function is called to ensure it is the correct input mode.
Example of using the same bind for multiple input modes/types:
void APlayerInputController::SetupInputComponent()
{
Super::SetupInputComponent();
check(InputComponent); // InputComponent is inherited; making sure it is valid before using it.
InputComponent->BindAxis("MoveForward", this, &APlayerInputController::MoveForward);
}
void APlayerInputController::MoveForward(float _axisAmount)
{
if (CurrentInputMode == EInputModeType::Character) // where CurrentInputMode is a member variable created in header file of type EInputModeType; EInputModeType is an enum you would create containing each mode.
{
Character->MoveForward(_axisAmount); // where Character is a member variable pointer to the character created in the header file.
}
else if (CurrentInputMode == EInputModeType::Pilot)
{
Ship->ApplyForwardThrusters(_axisAmount); // where Ship is a member variable pointer to the ship created in the header file.
}
}
Example of using seperate binds for multiple input modes/types:
void APlayerInputController::SetupInputComponent()
{
Super::SetupInputComponent();
check(InputComponent); // InputComponent is inherited.
InputComponent->BindAxis("CharacterMoveForward", this, &APlayerInputController::CharacterMoveForward);
InputComponent->BindAxis("PilotMoveForward", this, &APlayerInputController::PilotMoveForward);
}
void APlayerInputController::CharacterMoveForward(float _axisAmount)
{
// Ensure the current input mode is Character, otherwise ignore input:
if (CurrentInputMode == EInputModeType::Character)
{
Character->MoveForward(_axisAmount); // where Character would be a reference to the character.
}
}
void APlayerInputController::PilotMoveForward(float _axisAmount)
{
// Ensure the current input mode is Pilot, otherwise ignore input:
if (CurrentInputMode == EInputModeType::Pilot)
{
Ship->ApplyForwardThrusters(_axisAmount);
}
}
Having separate binds for each allows for player (and future personal) customisation of input binds without having to worry about affecting the bindings for another input type.
I also have input binds setup for each input device I use so that I can change how input is handled depending on the device (mainly for how input is activated - eg. I may have the player do an emote when holding B on a controller while on keyboard you just tap the ‘J’ key instead of having to also hold it). That also allows me to easily know what device type is being used to determine if I should display control feedback for a keyboard & mouse, gamepad or another device (eg. “Press the ‘A’ button to jump” for controller compared to “Press spacebar to jump” for keyboard).
EDIT:
Thought it would be worth adding for more clarity - wherever you change to pilot a vehicle or return to being on foot, you can either call a function directly on your PlayerInputController to notify it that the input mode has changed or use an event.
void AMyCharacter::EnterShip() // example function in your character class that may be called when you are transitioning to pilot the ship.
{
PlayerInputControllerReference->ChangeInputMode(EInputModeType::Pilot); // where PlayerInputControllerReference is a member variable pointer in the character header file.
}
void AMyCharacter::ExitShip()
{
PlayerInputControllerReference->ChangeInputMode(EInputModeType::Character);
}
void APlayerInputController::ChangeInputMode(EInputModeType _newInputMode)
{
CurrentInputMode = _newInputMode;
}