Using crosshair or Raycast for UI Input

Hello everyone!

To give a bit of context first:

We (My friend and myself) built some UI Canvases in a 3D space, where you can interact with the game objects in our scene (For example change settings for the generator to increase power). To do that you go in front of the display and use the ALT key to enable the mouse and interact with the sliders and buttons on display. (Currently, the cursor is locked, and only unlocked when you press ALT.) This works fine and is nice, but it is not user-friendly and awkward with the ALT Key.

Now to the problem:

We want to use the center of the screen as a cursor. But a Raycast apparently cannot interact with the UI Elements, eventually were able to trigger the button with the GraphicRaycaster and the EventData result List. which gives us the opportunity to use GetComponent().onClick.Invoke() to trigger it furthermore we are not able to trigger the OnPointerEnter() or OnPointerExit method unless we unhide the cursor with ALT to use highlights or much more important to use a slider. And unlocking the cursor would cause more issues if you have more Multiscreen setup or window mode.

Solidify our question:

Is it possible to build a crosshair that behaves like a centered cursor ( To use sliders and buttons)?
Or is there any other way? How? (Some pointers here would already help)

We wasted so much time already trying to figure this out - that it is possible that we somehow now miss the obvious…

Thank you in advance!

Ok, I (the other part of the team) figured it out. If anyone else is struggling with this problem, here is my solution. Just attach this script to the canvas. I’m not sure, but I would guess that the raycast needs to be fired from the camera. I did this anyway in one of my PlayerController scripts using Camera.main.ScreenPointToRay(). The canvas background image must be Raycast Target, as well as the image from the button and, depending on the case, the whole slider or just the handle if it is only to be moved to it.

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

public class GraphicRaycasterRaycaster : MonoBehaviour
{
    private GraphicRaycaster m_GrapicRaycaster;
    private PointerEventData m_PointerEventData;
    private EventSystem m_EventSystem;

    [Header("OPTIONAL ATTACHMENTS")]
    [Space]
    [SerializeField] private Slider mySlider;
    [Space]
    [SerializeField] private bool DEBUG = false;

    private bool oneClickOnly = true;
    [HideInInspector] public bool clickedOnSlider = false;

    private Vector2 localPos;
    
    private float valueMultiplier;
       

    void Start()
    {
        //Fetch GrapicRaycaster, EventSystem and Slider
        m_GrapicRaycaster = GetComponent<GraphicRaycaster>();
        m_EventSystem = FindObjectOfType<EventSystem>();
    }

    void Update()
    {
        //Set Pointer Event
        m_PointerEventData = new PointerEventData(m_EventSystem);

        //Set Pointer Event Position to mouse position        
        m_PointerEventData.position = Mouse.current.position.ReadValue();

        //List the current Raycast Results
        List<RaycastResult> results = new List<RaycastResult>();

        //Raycast using Graphics Raycaster
        m_GrapicRaycaster.Raycast(m_PointerEventData, results);
        

        //Check if in front of UI and calculate local canvas position you are pointing to with the center of your screen 
        foreach (RaycastResult result in results)
        {
            //Check if you are close enough to interact with the UI
            if (result.distance <= 1)
            {
                //pass if slider is null
                if (mySlider)
                {
                    //get local position on the canvas relativ to mouseposition through mainCam
                    RectTransformUtility.ScreenPointToLocalPointInRectangle(gameObject.GetComponent<RectTransform>(), m_PointerEventData.position, Camera.main, out localPos);

                    //Calculate slider position on cancvas and it´s relative start and end position (Slider parent must be as big as the canvas)
                    Vector3 sliderPosition = mySlider.GetComponent<RectTransform>().localPosition;
                    float sliderwidth = mySlider.GetComponent<RectTransform>().rect.width;
                    float sliderwidthMultiplier = mySlider.GetComponent<RectTransform>().localScale.x;

                    float sliderArea = Mathf.Abs((sliderwidth / 2) * sliderwidthMultiplier);

                    float minValue = sliderPosition.x - sliderArea;
                    float maxValue = sliderPosition.x + sliderArea;

                    //set slider Value relative to localposition.x if slider is horizontal
                    valueMultiplier = Mathf.InverseLerp(minValue, maxValue, localPos.x);

                    if (DEBUG)
                    {
                        print("Area " + sliderArea);
                        print("min " + minValue);
                        print("max " + maxValue);
                    }
                }

                // --- DEBUG including visual ray

                if (DEBUG)
                {                   

                    Ray ray = Camera.main.ScreenPointToRay(m_PointerEventData.position);
                    Debug.DrawRay(ray.origin, ray.direction, Color.cyan, 5);

                    if (result.gameObject.GetComponent<Button>())
                        Debug.Log("Hit Button " + result.gameObject.name);

                    if (mySlider && result.gameObject.GetComponentInParent<Slider>())
                        Debug.Log("Hit Slider " + result.gameObject.name);
                }
            }
        }       
        
        //Execute button/slider command if available when pressing left mouse button
                
        if (Mouse.current.leftButton.isPressed)
        {            
            foreach (RaycastResult result in results)
            {
                if (result.distance <= 1) 
                {
                    if (result.gameObject.GetComponent<Button>() && oneClickOnly)
                    {
                        result.gameObject.GetComponent<Button>().onClick.Invoke();
                    }

                    if (mySlider && result.gameObject.GetComponentInParent<Slider>() && oneClickOnly)
                    {
                        clickedOnSlider = true;
                    }
                }
            }

            if (clickedOnSlider)
                mySlider.value = valueMultiplier * mySlider.maxValue;

            oneClickOnly = false;
        }
        else
        {
            clickedOnSlider = false;
            oneClickOnly = true;
        }        
    }    
}