Virtual mouse in Godot
Overview
Section titled “Overview”A “virtual mouse” in this case is something like using a controller to emulate the mouse.
Note to self: private code link is here.
Basic implementation
Section titled “Basic implementation”Clicking
Section titled “Clicking”// Without taking ContentScaleFactor into account, the mouse coordinates you get could be wrong.var mousePosition = GetViewport().GetMousePosition() * GetTree().Root.ContentScaleFactor;if (@event.IsActionPressed("ui_page_down")){ Input.ParseInputEvent( new InputEventMouseButton { Device = (int)InputEvent.DeviceIdEmulation, Position = mousePosition, ButtonIndex = MouseButton.Left, Pressed = true, } );}
// Do the same thing for ui_page_up but with Pressed = false…when you press PgDn, it will send a mouse-down event. This on its own is not an entire click! You also need to press PgUp.
Motion
Section titled “Motion”public override void _Process(double delta){ const int speed = 20; Vector2 diff = Input.GetVector("cursor_left", "cursor_right", "cursor_up", "cursor_down") * speed; if (!diff.IsZeroApprox()) { Vector2 mousePos = GetViewport().GetMousePosition() * GetTree().Root.ContentScaleFactor;
InputEventMouseMotion eventMotion = new() { Device = (int)InputEvent.DeviceIdEmulation, Position = mousePos, Relative = diff, Velocity = diff * (float)delta, };
// The event is still needed for the sake of MouseEntered/Exited signals Input.ParseInputEvent(eventMotion);
// This only works on desktop platforms; see https://docs.godotengine.org/en/stable/classes/class_input.html#class-input-method-warp-mouse Input.WarpMouse(mousePos + diff); }}Troubleshooting
Section titled “Troubleshooting”Click events aren’t making it to a particular window
Section titled “Click events aren’t making it to a particular window”We had to do work around multiple viewports. I have this comment written in our code:
/// <summary>/// We keep this around just for <see cref="Popup"/>s. We run with <c>display/window/subwindows/embed_subwindows</c>/// set to <c>true</c>, which means <see cref="PopupMenu"/>s are not in a separate OS window, but they <em>are</em>/// in a separate viewport. As a result, two things have to happen:////// <list type="number">/// <item>We need to reparent the <see cref="VirtualCursor"/> so that it can even get events in the new/// viewport.</item>/// <item>We need to get the mouse position according to the original viewport since we embed subwindows.</item>/// </list>////// If we ever change that setting, we would need to change some code here.////// I couldn't figure out a way to fetch this at runtime after this <see cref="VirtualCursor"/> has been reparented,/// but that's okay; the original viewport shouldn't change./// </summary>private Viewport _startingViewport = null!;