Raycast consuming too much ! (VR)

Hi !

I am trying to make a “UI interaction system” using a raycast in my VR project and here is what I came up with :

I use a raycast from a start point (from the hand/controller on 5 meters maximum) and check if it collides with anything with the “UIElement” Tag. If so, it displays a line (so the user can see where he is aiming). It also checks if the gameobject that was hit by the raycast has a Button component and, if it has one and the player presses the trigger, it calls the button’s onClick() method.

All of this is used into a method that is called constantly in the FixedUpdate. But I have a huge problem. Since VR needs a good 75-90 FPS to run smoothly, I can’t afford the fps drop that occurs with my solution. So what happens is that everything runs correctly, until I aim at a UIElement and the line appears. And at that point, I go from a constant 90 FPS to 45-62 FPS.

What can I do to improve this ?

In the profiler, here is what I got :

    void FixedUpdate()
    {
        RayForUI();
    }

    //-------------------------------------------------


    private void RayForUI()
    {
        RaycastHit hit;

        LineRenderer rayLine = rayCastStartPoint.parent.GetComponent<LineRenderer>();

        Physics.Raycast(rayCastStartPoint.position, rayCastStartPoint.forward, out hit, raycastMaxDistance, RaycastCollidableLayers);

        if (hit.collider != null)
        {
            if(hit.collider.CompareTag("UIElement"))
            {
                Button bt = hit.collider.GetComponent<Button>();

                rayLine.colorGradient = GradColor(); //just a method to set a gradient to the line
                rayLine.startWidth = lineWidthStart;
                rayLine.endWidth = lineWidthEnd;

                Debug.Log(hit.collider.name);

                Debug.DrawRay(rayCastStartPoint.transform.position, rayCastStartPoint.forward * hit.distance, Color.blue);

                Vector3 endPos = hit.point;

                rayLine.SetPositions(new Vector3[] { rayCastStartPoint.position, endPos });

                rayLine.enabled = true;

                if (bt != null)
                {
                    Button button = hit.collider.GetComponent<Button>();
                    ColorBlock colors = button.colors;
                    colors.normalColor = HoverONColor;
                    button.colors = colors;

                    if (SteamVR_Input._default.inActions.GrabPinch.GetStateUp(hand))
                    {
                        bt.onClick.Invoke();
                    }
                }
                else //here, It is supposed to reset the color of the button when I don't hover over it (but I think it a little overhead) what do you suggest ?
                {
                    Button button = this.gameObject.GetComponent<Button>();
                    ColorBlock colors = button.colors;
                    colors.normalColor = HoverOFFColor;
                    button.colors = colors;
                }

well lets go little by little, since what is consuming your resources isnt the ray

create the linerenderer ooutside the method and cast it in the start, using getcomponent is expensive.

LineRenderer rayLine;

void Start()
{
   rayLine = rayCastStartPoint.parent.GetComponent<LineRenderer>();
}

remove the button since is just the same as bt

is there any need to change the gradient color and size all the time? if its constant just set it in the start/awake event

Button button = hit.collider.GetComponent<Button>();//REMOVE THIS LINE AND CHANGE button FOR bt

avoid changing the normal colors, getting a copy of the color block changing and re-change it is slower than just changing the Image.color (and should have the exact same effect) the last part of the code is a bit weird, if you dont hit a button what button color you reset? since if you are resetting the color of the button attached to to that script it makes me assume that ALL the buttons have the same script? or when you stop hovering you set a default button? i am lost there.

@xxmariofer Thank you for your response. I was trying to fix it while waiting for answers and it went well, I think.

Now, the framerate is stable (~ 90 FPS). I did as you suggested. The rayline is setup in the Awake function (with its width and length). But what was really consuming was the Debug.Log stuff. As soon as I commented them, the framerate got stable again.

I also added an if statement before the Physics.raycast line, I’ve never understood why, but in the Unity Documentation, it is used this way, so I did the same and I guess it helped stabilizing the fps ?

About the last part, you’re right. The script is attached to every button I want to interact with. I know, it’s not optimal at all, but I coded this stuff yesterday, just straight forward, without second thoughts. Is it a problem if I leave it that way ? For now, it does what I want and doesn’t seem ressources-taking.

I have two other minor problems tho … the first one, is about the line that is rendered. It is supposed to disappear when the raycast doesn’t collide with a “UIElement”. But what it does right now, is that when the user doesn’t aim at a “UIElement” anymore, the line is still there, but it is not attached to the controller. It’s like it is floating at the last position known when the raycast was hitting the “UIElement”.

This line is supposed to fix this, but it doesn’t :

                if (hit.collider.CompareTag("UIElement"))
                {
                    rayLine.enabled = true;
                    //some code
                }
                else
                {
                    rayLine.enabled = false;
                }

The other problem : when I use the onClick.invoke() on a Smartphone UI that I created in the scene (the user can compose a number of 10 digits and call it) , it is supposed to be called once, when the user presses the trigger on the number he wants to add. He should be able to do so until he reachs the 10 digits limit, one number at a time, after each trigger pressure.

But for now, as soon as I press the trigger, the line is fullfilled with the same button that I pressed only once. So if I press the button “2” I got “2222222222” instead of just “2” on the number line.

My guess is that onClick is called multiple time and not juste once. Any solution for that ?

Here is my improved code btw :

    private LineRenderer rayLine;

    //=============================================== End of variables declaration ===============================================//

    private void Awake()
    {
        if (GetComponent<SteamVR_Behaviour_Pose>())
        {
            hand = GetComponent<SteamVR_Behaviour_Pose>().inputSource;
        }

        rayLine = rayCastStartPoint.parent.GetComponent<LineRenderer>();
        
        rayLine.colorGradient = GradColor();
        rayLine.startWidth = lineWidthStart;
        rayLine.endWidth = lineWidthEnd;
    }

    //Used when we need to do something with physics over time
    void Update()
    {
        RayForUI();
    }

    //-------------------------------------------------


    private void RayForUI()
    {
        RaycastHit hit;

        if(Physics.Raycast(rayCastStartPoint.position, rayCastStartPoint.forward, out hit, raycastMaxDistance, RaycastCollidableLayers))
        {
            if (hit.collider != null)
            {
                if (hit.collider.CompareTag("UIElement") || hit.collider.CompareTag("Screen_BG"))
                {
                    Button bt = hit.collider.GetComponent<Button>();

                    //Debug.Log(hit.collider.name);

                    //Debug.DrawRay(rayCastStartPoint.transform.position, rayCastStartPoint.forward * hit.distance, Color.blue);

                    Vector3 endPos = hit.point;

                    rayLine.SetPositions(new Vector3[] { rayCastStartPoint.position, endPos });

                    rayLine.enabled = true;

                    if (bt != null)
                    {                        
                        ColorBlock colors = bt.colors;
                        colors.normalColor = HoverONColor;
                        bt.colors = colors;

                        if (SteamVR_Input._default.inActions.GrabPinch.GetStateDown(hand))
                        {
                                bt.onClick.Invoke();
                        }
                    }
                    else
                    {
                        if(GetComponent<Button>() != null)
                        {
                            Button button = this.gameObject.GetComponent<Button>();
                            ColorBlock colors = button.colors;
                            colors.normalColor = HoverOFFColor;
                            button.colors = colors;
                        }
         }