Making a virtual mouse controller by a gamepad

I want to make a virtual mouse that the player can control using a gamepad so that players can navigate menus and move the mouse to play minigames.

I used this code in order to simulate a click and move the cursor based on the joystick input, but this code only works in a Windows build and uses an unreliable “DllImport” that doesn’t work in the WebGL build.

Do you know how can I achieve the same thing using a better system or why the “DllImport” doesn’t work in the WebGL build?

Code:

using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;
using UnityEngine.UI;


public class InputManager : MonoBehaviour, ISettingsSavedData
{
    [SerializeField] float joystickMouseSensitivity;
    [SerializeField] float minJoystickMouseSensitivity, maxJoystickMouseSensitivity;
    // Deadzone to prevent small joystick movements interapting with mouse movement
    [SerializeField] float joystickDeadzone = 0.1f;
    public bool cursorLocked, cursorVisible;

    [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
    public static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint cButtons, uint dwExtraInfo);
    //Mouse actions
    private const int MOUSEEVENTF_LEFTDOWN = 0x02;
    private const int MOUSEEVENTF_LEFTUP = 0x04;
    private const int MOUSEEVENTF_RIGHTDOWN = 0x08;
    private const int MOUSEEVENTF_RIGHTUP = 0x10;


    public Inputs inputActions { get; private set; }
    public static InputManager Instance { get; private set; }

    private void Awake()
    {
        HandleSingletonLogic();
        CreateNewInput();
        inputActions.Player.TestMenuScene.performed += TestMenuScene_performed;
    }

    private void Start()
    {
        inputActions.Joystick.MousePress.started += MousePress_started;
        inputActions.Joystick.MousePress.canceled += MousePress_canceled;
    }
    private void Update()
    {
        HandleControllerMouse();
    }

    private void OnDestroy()
    {
        inputActions.Joystick.MousePress.started -= MousePress_started;
        inputActions.Joystick.MousePress.canceled -= MousePress_canceled;
    }

    public void SaveMyData(ref SettingsMenuData settingsData)
    {
        // no need to save data
    }

    public void LoadGameData(SettingsMenuData settingsData)
    {
        joystickMouseSensitivity = Utilities.MapValueToNormalizeMinMax(settingsData.joystickSensitivity,
            minJoystickMouseSensitivity, maxJoystickMouseSensitivity);
    }

    public void ApplyCursor()
    {
        Cursor.lockState = cursorLocked ? CursorLockMode.Locked : CursorLockMode.None;
        Cursor.visible = cursorVisible;
    }

    private void CreateNewInput()
    {
        if (inputActions != null) return;
        inputActions = new Inputs();
        inputActions.Enable();
    }

    private void TestMenuScene_performed(InputAction.CallbackContext obj)
    {
        /*Cursor.lockState = CursorLockMode.None;
        Cursor.visible = true;
        GameSaveManager.Instance.SaveGame();
        LevelManager.Instance.LoadScene(0);*/
        FindObjectOfType<GameManager>().IncreaseMoneyAmount(10000);
    }

    private void HandleSingletonLogic()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }

    private void MousePress_canceled(InputAction.CallbackContext obj)
    {
        //Call the imported function with the cursor's current position
        uint X = (uint)Input.mousePosition.x;
        uint Y = (uint)Input.mousePosition.y;
        mouse_event(MOUSEEVENTF_LEFTUP, X, Y, 0, 0);
    }

    private void MousePress_started(InputAction.CallbackContext obj)
    {
        //Call the imported function with the cursor's current position
        uint X = (uint)Input.mousePosition.x;
        uint Y = (uint)Input.mousePosition.y;
        mouse_event(MOUSEEVENTF_LEFTDOWN, X, Y, 0, 0);
    }

    private void HandleControllerMouse()
    {
        Vector2 joystickInput = inputActions.Joystick.JoystickDelta.ReadValue<Vector2>();

        if (joystickInput.magnitude > joystickDeadzone)
        {
            // Handle deltaTime only if the game is not paused
            float deltaTime = Time.unscaledDeltaTime;

            Vector3 moveDelta = new Vector3(joystickInput.x, joystickInput.y, 0) * joystickMouseSensitivity * deltaTime;
            Vector3 newMousePosition = Mouse.current.position.ReadValue() + (Vector2)moveDelta;

            // Clamp the position
            newMousePosition.x = Mathf.Clamp(newMousePosition.x, 0, Screen.width);
            newMousePosition.y = Mathf.Clamp(newMousePosition.y, 0, Screen.height);

            // Update mouse position
            Mouse.current.WarpCursorPosition(newMousePosition);
        }
    }

    private void SetInputMap(InputAction inputAction, bool active)
    {
        if (active) inputAction.Enable();
        else inputAction.Disable();
    }

    public void SetPlayerControlsActive(bool active)
    {
        SetInputMap(inputActions.Player.Move, active);
        SetInputMap(inputActions.Player.CameraRotation, active);
        SetInputMap(inputActions.Player.Interact, active);
        //SetInputMap(inputActions.Joystick.JoystickDelta, !active);
        Cursor.lockState = active ? CursorLockMode.Locked : CursorLockMode.None;
        Cursor.visible = !active;
    }


}

That DllImport is a Win32 dll so it wouldn’t exist in WebGL or any other platform.

To do what you want you probably want to:

  • hide the system cursor
  • make and manage your own cursor
  • bridge the input intents (positions and clicks) over to your own class derived from the InputModule

https://docs.unity3d.com/Packages/com.unity.ugui@1.0/manual/InputModules.html

Can I make it so that the cursor that I will create can press UI buttons?