I’m making a game with a blend of RTS/MMO/3rd person style controls. I want to implement an edge scrolling feature that uses mouse input to scroll the edge, instead of the traditional edge auto-scrolling. This is using hardware cursor.
This feature works well with Input Mode Game Only, and lock to viewport. Show mouse cursor is also on - it needs to be visible. The problem is, on mouse release after click + edge movement, my HW cursor tries to escape to windows, going outside the game window. My custom cursor gfx also keeps resetting to windows default.
How can one keep a HW cursor locked to the game window on click and release?
Alternatively, is there a way to intercept the mouse focus or re-bind whatever Unreal is doing with mouse input before the UI stack intercepts it (I’m not using any widgets or HUDs). I simply want to launch trigger events on the player controller in-game (that have nothing to do with UI) when clicking, without having the cursor escape.
Set Input Mode Game and UI keeps the cursor properly in place, but then I can’t use non-automatic edge scrolling as mouse position events beyond the edge don’t register anymore. Could there be a potential workaround in this mode, too?
Appreciate any help.
It is a known engine problem which people have been complaining about for years, but there is still no engine fix. There is however, my workaround.
From my own documentation:
bool APlayerControllerBase::InputKey(FKey InKey, EInputEvent InEventType, float InAmountDepressed, bool bInIsGamepad) {
bool bIsHandled = Super::InputKey(InKey, InEventType, InAmountDepressed, bInIsGamepad);
// This is a workaround for what appears to be an engine issue (undesired focus loss) in the SceneViewport.
// Trigger: bShowMouseCursor == true && InputMode == InputModeGame (BlueprintWidgetLibrary), after releasing any mouse button.
// Symptoms are loss of focus and any mouse axis value reading 0 unless a button is pressed.
// References:
// https://answers.unrealengine.com/questions/395313/view.html
// https://answers.unrealengine.com/questions/260284/mousex-bindaxis-returning-0-unless-mouse-button-de.html
// Only if the trigger situation is the current situation, we need to apply a workaround.
if (bShowMouseCursor && InKey.IsMouseButton() && (InEventType == EInputEvent::IE_Released) && (InputMode == E_PlayerControllerInputModes::Game)) {
// Simply re-applying the Game input mode seems to do the trick.
UWidgetBlueprintLibrary::SetInputMode_GameOnly(this);
}
return bIsHandled;
}
When the condition for the bug is met, simply setting InputModeGame again at that point should be enough work around the focus problem.
Note that I chose to dump the default cursor system in my implementation and base everything on the analog cursor (software cursor) system, so my bShowMouseCursor is normally set to false anyway. Let me know if this fixes your problem.
Thanks! I spent way too many hours on this issue today before I saw your reply, but made a little progress. I made some changes to FInputModeGameAndUI::ApplyInputMode directly in PlayerController.cpp:
// Copied from game only and adapted
TSharedRef<SViewport> ViewportWidgetRef = ViewportWidget.ToSharedRef();
SlateOperations.UseHighPrecisionMouseMovement(ViewportWidgetRef);
SlateOperations.SetUserFocus(ViewportWidgetRef);
SlateOperations.LockMouseToWidget(ViewportWidgetRef);
GameViewportClient.SetMouseLockMode(MouseLockMode);
// Commented out release mouse capture from game and ui.
// SlateOperations.ReleaseMouseCapture();
With the above it turned into a ‘game mode’ but with proper window locking. It goes right into UI mode on release unfortunately. But calling the now-edited Set Game and UI (in BP) and Set Viewport Capture Mode to CapturePermanently_IncludingInitialMouseDown brings it back to ideal. The fix takes one frame though. I tried calling it on event tick: It 100% eats a little FPS as well as clicks shortly after being called. So it should kinda be used on release only, which hopefully registers correctly.
Just for clarity’s sake I tried this with a widget. With Focusable and Behavior all off in the target widget it still stops by on mouse release. OnMouseMove triggers, and I made sure to bind it and override mouse events (MouseButtonDown, Up, Move) to Unhandled. This is a problem in theory if the game slows down and if the player manages to click really fast. Keyboard events fortunately seem to be ignored just fine. All in all not too bad.
For now I stopped using Game Only for hidden cursor, since it still glitches out of the edges on release when the cursor isn’t centered on tick. Can probably apply the centered cursor save/load position fix all FPS games use, might try later. It shouldn’t be too different from the modified Game+UI mode though.
I’ll be looking closer at your workaround tomorrow trying to implement it and learn more about this, since I’m not entirely happy with the workaround I found. That one frame in UI mode, the FPS drop on release and lack of actual flexibility isn’t all that great. It was really strange to see so little documentation on these issues. Especially given how important they can be to certain genres. So thanks for sharing your knowledge.
At the very minimum I’m glad that there are two modes I can work with that keep the cursor inside:
- Edited ‘Game and UI’ mode with reinitialize after mouse release, trying to account for as much potential input-blocking in widgets as possible for that 1 frame.
- UI only mode for true pause menu/management.
Really hoping to get to the bottom of this!
There’s quite a few obstacles with the default mouse system to overcome.
You might want to consider using the FAnalogCursor (SoftwareCursor) instead of the hardware cursor and keeping the bShowMouseCursor off on the PlayerController. The only common downside of this is that softwarecursors are a few frames behind the hardwarecursor and that you need to defer its rendering in case you want them to work with menu anchor widgets properly.
there are many benefits of using software cursors though, in this situation it would be very easy to lock a software cursor to a position or within screen geometry while this is often glitchy at best when attempting it with the hardware cursor:
You can’t properly center a hardware cursor on tick. Setting cursor position through PlayerController will hijack the cursor position aggressively but if you are fast enough you will see the cursor moves away from the center before UE sets its position every tick.
The problem is not caused by the PlayerController however. It’s just a bad name for a bool used in more complex processed (bShowMouseCursor).
Take a look at:
UE4.26, SceneViewport.cpp, FSceneViewport::OnMouseButtonUp, Line 558-628:
If I remember right, this is the part where cursor visibility is used as a condition for focus release, which is undesired in input mode Game if you want to use the cursor for game interaction…