Videogamers0 / MGUI

UI framework for MonoGame game engine.
MIT License
67 stars 8 forks source link

Does MGUI support gamepad input? #13

Closed redPanda5000 closed 3 weeks ago

redPanda5000 commented 3 weeks ago

I was searching but didn't see any reference to gamepad support, just mouse and keyboard. Is gamepad input currently supported or planned? And thanks for the work on this, I love having a maintained XAML framework.

Videogamers0 commented 3 weeks ago

No, there's no gamepad support. You'd have to implement it yourself, such as by providing your own implementation of IRenderHost to spoof mouse/keyboard state based on gamepad inputs.

To instantiate an MGDesktop, you'd usually do something like:

MGUI.Shared.Rendering.MainRenderer mr = new MainRenderer(new MGUI.Shared.Rendering.GameRenderHost<Game1>(this));
MGDesktop desktop = new MGDesktop(mr);

Instead of using GameRenderHost, you'd have to write your own implementation of IRenderHost with different logic for the IRenderHost.GetMouseState() and IRenderHost.GetKeyboardState() methods.

public bool IsUsingGamepad { get; set; }
public MouseState SpoofedMouseState { get; set; }
public MouseState IRenderHost.GetMouseState() => IsUsingGamepad ? SpoofedMouseState : Mouse.GetState();

And then in your update method, set the spoofed mouse state based on gamepad inputs. For example, if you had a very simple window with 3 buttons named Button_1, Button_2, and Button_3 laid out in a horizontal stackpanel, you might start by creating some kind of 'neighbors' lookup table:

enum Direction
{
    Up,
    Down,
    Left,
    Right
}

...

MGWindow window = ...
MGButton btn1 = window.GetElementByName<MGButton>("Button_1");
MGButton btn2 = window.GetElementByName<MGButton>("Button_2");
MGButton btn3 = window.GetElementByName<MGButton>("Button_3");

//  This dictionary keeps track of which MGElements are positioned next to other MGElements.
//  In our simple example, the 3 buttons are laid out horizontally in a single row, so btn2 is the right-neighbor of btn1, btn1 is the left-neighbor of btn2 etc.
Dictionary<MGElement, Dictionary<Direction, MGElement>> Neighbors = new Dictionary<MGElement, Dictionary<Direction, MGElement>>()
{
    {
        btn1, new Dictionary<Direction, MGElement>()
        {
            { Direction.Left, btn3 }, // wrap back around if navigating left of the left-most button
            { Direction.Right, btn2 }
        }
    },
    {
        btn2, new Dictionary<Direction, MGElement>()
        {
            { Direction.Left, btn1 },
            { Direction.Right, btn3 }
        }
    },
    {
        btn3, new Dictionary<Direction, MGElement>()
        {
            { Direction.Left, btn2 },
            { Direction.Right, btn1 }
        }
    }
};

Then keep track of which MGElement is currently selected by the gamepad:

MGElement selectedGamepadElement = btn1;

And react to gamepad events to update the selected element and the spoofed mouse state:

//  EX: When the left thumbstick is moved right, navigate to the right-neighbor of the selected MGElement
if (GamePad.GetState(PlayerIndex.One).IsButtonDown(Buttons.LeftThumbstickRight))
{
    if (Neighbors.TryGetValue(selectedGamepadElement, out var selectedElementNeighbors) && selectedElementNeighbors.TryGetValue(Direction.Right, out MGElement rightNeighbor))
    {
        selectedGamepadElement = rightNeighbor;
        //  Spoof the new mouse position to wherever the newly-selected gamepad element is located
        MouseState newSpoofedMouseState = new MouseState(selectedGamepadElement.ActualLayoutBounds.Center.X, selectedGamepadElement.ActualLayoutBounds.Center.Y, 
            SpoofedMouseState.ScrollWheelValue, SpoofedMouseState.LeftButton, SpoofedMouseState.MiddleButton, SpoofedMouseState.RightButton, SpoofedMouseState.XButton1, SpoofedMouseState.XButton2, SpoofedMouseState.HorizontalScrollWheelValue);
        SpoofedMouseState = newSpoofedMouseState;
    }
}

And then obviously you'd have to do similar logic to spoof mouse presses, such as spoofing a mousedown when the gamepad A-button is pressed.

redPanda5000 commented 3 weeks ago

Thanks for the detailed write up on this! I have some programming experience in JS/HTML (which is why I like XAML based) but am new to C#, Monogame, and your framework so please forgive me if this is a dumb question, but would I actually need to use your provided input framework (or make something analogous on my own) if I am to use your UI?

For instance: https://gamefromscratch.com/monogame-tutorial-handling-keyboard-mouse-and-gamepad-input/

that tutorial uses "Microsoft.XNA.Framework.Input" to handle mouse, keyboard, and gamepad. I take it you provided mouse and keyboard input handling because your UI objects have some unique kind of event handling / subscription required, but if I'm already using the basic MS input framework to handle inputs for non-UI gameplay could it be easier to just apply those input tools to your UI?

Again I'm in a bit over my head here so apologies if my question doesn't make sense. Thanks again!

Videogamers0 commented 3 weeks ago

No, you don't have to use MGUI's input framework. But if you don't, then you'll have to add additional logic to ensure you're not double-processing the same input. If the MGUI interface processed the input, then you probably shouldn't also process it for your game logic.

For example, suppose you had a UI overlay with a button on it, and the button just happened to be positioned overtop of a lever or something interactable in your game world. If the user clicked the button, the UI would handle the input, but then you shouldn't execute your game logic for clicking the lever. So you'd want to check if the input was already handled by the UI, by checking if (MGDesktop.InputTracker.Mouse.CurrentButtonPressedEvents[MouseButton.Left]?.IsHandled == true)