- Home /
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;
}
Answer by xxmariofer · May 08, 2019 at 01:57 PM
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.
Answer by a5exiadz2019 · May 08, 2019 at 03:43 PM
@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;
}
}
physics.raycast returns true if it collides with something and false if it doesnt, so using the method inside the if saves you unneded comprobations of the collider etc since the if wont trigger when it didnt collide with anything. yes all logs and the editor low you fps, if you want to test the fps you should remove the logs andbuild the game for getting the real fps of the game. having that exact script in each button will affect a lot to the performance, it is creating the exact same ray for each button rather than just once, even if you needed a lot of different rays you should try having a raymanager. for the line issue you have 2 options, the vector3 from the pooints array and just give them their default value, or just desactivate the line likethis
rayLine.gameObject.SetActive(true);
you will need to reactivate it whehenever you use it with this method. last your invoke issue is that you are calling the invoke every fixedupdate iteration so you are calling a lot oif times per second, i have never done vr but there should be something like onclick event in the S$$anonymous$$mVR_Input._default.inActions, if not you will have to add a bool called something like isPressed, set the ispressed value to true when you call invoke and only let the invoke be called when the ispressed is false, and set it again to false when you stop hovering the button
Your answer
Follow this Question
Related Questions
Spawn script on mesh crashes Unity 0 Answers
How to move the main camera in Google Cardboard? 0 Answers
UI Text showing weird string value? 1 Answer
How to access scripts in other game object with raycasts? 2 Answers
Vertical distance indication 1 Answer