Raycast Lagging Behind Collider

I’ve found a few threads and “Unity Answers” posts that seem to be related to this, but nothing that I’ve found therein has helped.

I’m sure this is a familiar problem to many of you though: I have a space ship that, when you hold down the right mouse button, shoots a laser sight. I’m just using a plain red line renderer to visualize it for now. I was pretty happy with how my whole “scope” idea was coming out until I started using it while moving. Doing so revealed that the raycast seems to lag behind the ship, causing it to hit the ship itself. The ship has a child object positioned in front of the ship (outside of its collider) which is used to set the origin of the raycast.

It’s probably worth mentioning that the ships’ forward movement is handled by the physics engine, but rotation is applied directly to the transform. I tried to use physics for rotation with a number of settings, and it never felt right. Anyways, the lag issue happens even if you’re just moving straight, so I don’t think that’s the source of the problem anyways.

Any ideas? I’ve heard that raycasts should be done through FixedUpdate, but I’m not entirely sure my flag set-up is accomplishing that properly. Also, I saw one person say something about multiplying the ray length by Time.deltaTime. I did that in lines 70, 72, and both, but it didn’t solve the problem.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ScopeScript : MonoBehaviour
{
    public LayerMask layerMask;

    [Tooltip("A reference to the GO from which the laser will fire.")]
    public GameObject origin;

    private float defaultPrecisionModeSpeed;
    private float scopePrecisionModeSpeed = 1;
    private float laserLengthMax = 1000;
    private float laserWidth = 0.5f;

    private LineRenderer lineRenderer;
    private ShipController ship;
    private GameObject scopeCam;

    private bool fireLaser = false;


    private void Start()
    {
        lineRenderer = GetComponent<LineRenderer>();
        lineRenderer.startWidth = laserWidth;
        lineRenderer.endWidth = laserWidth;
        ship = GetComponent<ShipController>();
        defaultPrecisionModeSpeed = ship.precisionRotationSpeed;
        scopeCam = ship.gameManager.scopeCamera;
    }

    // Update is called once per frame
    void Update()
    {
        if (ship.GetIsSelectedShip() && ship.canAct && !ship.gameManager.cycleMode && ship.isAlive)
        {
            if ((Input.GetKey(KeyCode.LeftShift) || Input.GetMouseButton(1) && !ship.GetIsShutDown()))
            {
                fireLaser = true;
                ship.precisionRotationSpeed = scopePrecisionModeSpeed;
            }
            else if (Input.GetKeyUp(KeyCode.LeftShift) || Input.GetMouseButtonUp(1))
            {
                fireLaser = false;
                lineRenderer.enabled = false;
                ship.precisionRotationSpeed = defaultPrecisionModeSpeed;
            }
        }
        else
        {
            fireLaser = false;
            lineRenderer.enabled = false;
        }
    }

    private void FixedUpdate()
    {
        if (fireLaser)
            FireRaycast();
    }

    private void FireRaycast()
    {
        Vector2 dir = origin.transform.position - ship.transform.position;
        dir = ship.transform.up;
        Ray2D ray = new Ray2D(origin.transform.position, dir);
        RaycastHit2D raycastHit;
        Vector3 endPosition = ray.origin + (laserLengthMax * ray.direction);

        raycastHit = Physics2D.Raycast(ray.origin, ray.direction, laserLengthMax, layerMask);

        if (raycastHit)
        {
            if (!(raycastHit.collider.GetComponent<StealthShipController>() && raycastHit.collider.GetComponent<StealthShipController>().cloakingController.IsCloaked()))
                endPosition = raycastHit.point;
        }

        if (raycastHit && raycastHit.collider.gameObject == ship.gameObject)
            Debug.Log("stop hitting yourself");

        lineRenderer.SetPosition(0, ray.origin);
        lineRenderer.SetPosition(1, endPosition);
        lineRenderer.enabled = true;

        scopeCam.transform.position = new Vector3(endPosition.x, endPosition.y, scopeCam.transform.position.z);
    }
}
  1. have you tried placing raycasting in Update and if you did, what did you end up with?
  2. is this script on your ship object?
  3. have you set up your objects like this?
    ship object (parent) HAS two child objects: laser (with line renderer component), and reticle object

generally, what you need is rather simple, but has to be intentionally designed with positional stability in mind.

  • to achieve this you absolutely need to parent both reticle and something with a linerenderer to a ship.
  • it doesn’t matter if a ship is put in motion by rigidbody, because the children objects will always go with it.
  • to raycast from the ship, make the ray with the parent’s (world) position and ship’s direction.
  • you get a direction if you take parent’s transform.forward (if forward is the front of your ship, or maybe it’s up I’m not sure in 2D, depends on your set up).
  • if there is a hit, keep this point;
  • if there are no hits, produce a point by multiplying some off-screen distance with a direction you already have, add parent’s position to this.
  • use the point as the 2nd point for the the line renderer; the first point is either the parent’s position, or some artificial point in front of the ship (compute this the same way like you did for a case when there were no hits, but decrease the distance to something smaller).
  • to position the reticle, simply place it in front of the ship, it will rotate with the ship automatically, and the laser will always shoot through it.
1 Like

Thank you for the response; I really appreciate the help.

  1. I initially had this in Update. I made the fireLaser flag to try to fix the problem by calling FireRaycast in FixedUpdate instead, but of course, that didn’t work.

  2. Yes, this script is on my ship. Pretty much any script on a ship will have a reference to that ship which, in turn, has a reference to the GameManager. It’s been very handy.

  3. The line renderer component is also on the ship. The visual is a bit misleading though. The reticle you see is not actually being used at all for my raycast or line renderer. In fact, that graphic will probably be turned off when a long-ranged weapon is in use. The “origin” GameObject is a separate object that is a child of the ship, positioned slightly in front of it.

You might have noticed the redundant code that I used to set “dir”. I wasn’t sure if I’d be able to use ship.transform.up because I had the “Use World Space” box checked on the Line Renderer component. It turns out, I can.

On that note, I was trying some stuff without “Use World Space”. For the ray initialization, I used origin.transform.localPosition, just to see what would happen. Interestingly, the “lag” issue was completely resolved, but the rotation was messed up and I couldn’t figure out how to fix it. That’s all the progress I’ve made, unfortunately.

  1. The advice for doing physics raycasting in FixedUpdate is generally good, because raycasting in Update can get excessive if your framerate is high, almost without any benefit because the rigidbodies do their thing only when physics is updated.

To fight against this – to have it synced with Update, yet stop needlessly wasting CPU – introduce a control flag (bool) that gates the raycasting logic so that you evaluate exactly one until the next FixedUpdate call. Very simple solution. This pattern is basically set & invalidate. Any update on your rigidbodies should invalidate the previous “known” state. And so you only work when it makes sense. And your work is always valid for all intermediate Update calls.

  1. Yes I got that. I see the redundant line of code.

Regarding the local space, you still need to fire the ray in the world space, so this doesn’t help you.
And besides it shouldn’t be the problem, if everything else is set up just right.

I can’t see anything wrong with your code. It’s fine. The problem is definitely elsewhere.
You probably have an interfering script somewhere or a wrongly set up hierarchies.

Hmmm, well that’s very strange then. I don’t think there’s anything in other scripts that could be interfering, and the hierarchy is very simple (ship has Line Renderer and the script that I shared, and the origin object is just a child, as I mentioned). I’m not saying you’re wrong, I just can’t see where the problem could be.

I tried playing around with different rigidbody settings. Currently, all ships are set to “continuous” and “interpolate”, as that seems to give the most accurate collision results (or that’s what it seems like). I tried all combinations of collision detection and interpolation modes, and none of them provided acceptable results. It’s very strange that the only way I’ve had a stable raycast is when using local position, but as you mentioned, I ought to use world space, which makes sense.

I really wasn’t expecting this to be such a snag. :confused:

EDIT: Interesting. I took out the flag so the raycast is only done during Update and I tried different rigidbody settings. Both “extrapolate” and “none” gave better results, and I’m pretty sure that “none” is giving perfect (non-lagging) results. Unless anybody would suggest otherwise, I’m going to keep my projectiles set to “interpolate” as I thought I remembered reading that it helps with smaller, faster objects. The ships, on the other hand, will all just be set to “none” (interpolation) unless I run into problems.

EDIT 2: Now that I see that there is a problem with raycasting from interpolating rigidbodies, I have a better idea of what to search for to see if others have encountered this problem. This thread seems to be dedicated to a similar issue (though it’s hard to tell for sure). One of the mods, hippocoder, responds:

“For objects which have interpolation and you want to cast from inside reliably, you should be using rigidbody.position as this is the “true” position of the object as far as physx is concerned instead of .transform (which holds interpolated position).”

I had no idea rigidbody.position was any different from transform.position. I tried turning interpolation back on, using the ship’s rigidbody position as the raycast origin, then removing the “ship” layer from the layermask (just to test), and the results were even worse than before. I’m not sure why, and I feel like I have the solution to my problem. I’m just interested in what exactly is going on with the interpolating rigidbodies and raycasts.

1 Like

those are great hints, and yes, it’s slightly complicated because of physics.

yes, rigidbody.position is the true position that is updated in sync with FixedUpdate.
and your transform.position is likely interpolated (Update is usually more frequent than FixedUpdate).

man it’s not something I can help you with unless I recreate the whole thing for myself. it’s been a while I did anything with physics, can’t do it from my head alone.

what I told you initially IS the solution, however you need to come up with a proper set up because it’s fiddly due to exact timings.

you know what? make 1 very reliable LineRenderer laser beam coming out of your ship, do just that. make it always on, and make it to always fire forward, through geometry, without raycasting. try to adjust your code until it renders perfectly in sync with the ship. start from there.

1 Like

The fact is if colliders rotate or move, they are only updated every fixed update. Unity IMHO has made a tragic decision to show only the interpolated positions, so if any of these objects interpolate, disable interpolation while testing and debugging for a clear view.

Next up, raycasts are just queries. That means they don’t do anything different in fixedupdate vs update. They just query the current state of the physics world which is only updated without interpolation, every fixed update step.

Raycasts are just a tool you probe the world with and it is up to you to keep that world fresh for when you do.

1 Like

When you say what you told me is the solution, you mean using the control gate to ensure that the raycast logic only happens once per FixedUpdate, yes? If I’m understanding correctly, this is almost what I was doing with the “fireLaser” flag. I think all that was missing was setting the flag to false at the end of FireRaycast(). I did this, and still get a lag thing happening. Either I’m misunderstanding you or there’s something else at play outside of code, as you suggested.

I took your advice and tried to just get a LineRenderer to work properly (always on, nevermind raycasting or layermasks). I was successful at getting this to work when calling the LineRenderer function in Update. If I used FixedUpdate, I got the lag again. Note: It didn’t make any difference whether I used the ship’s position as the origin or the “origin” child object as the origin.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ScopeScript : MonoBehaviour
{
    public LayerMask layerMask;

    [Tooltip("A reference to the GO from which the laser will fire.")]
    public GameObject origin;

    private float defaultPrecisionModeSpeed;
    private float scopePrecisionModeSpeed = 3;
    private float laserLengthMax = 1000;
    private float laserWidth = 0.25f;

    private LineRenderer lineRenderer;
    private ShipController ship;
    private GameObject scopeCam;

    private bool fireRaycastFlag = false;


    private void Start()
    {
        lineRenderer = GetComponent<LineRenderer>();
        lineRenderer.startWidth = laserWidth;
        lineRenderer.endWidth = laserWidth;
        ship = GetComponent<ShipController>();
        defaultPrecisionModeSpeed = ship.precisionRotationSpeed;
        scopeCam = ship.gameManager.scopeCamera;

        lineRenderer.enabled = true;        // FOR TEST ONLY
    }

    // Update is called once per frame
    void Update()
    {
        //if (ship.GetIsSelectedShip() && ship.canAct && !ship.gameManager.cycleMode && ship.isAlive)
        //{
        //    if ((Input.GetKey(KeyCode.LeftShift) || Input.GetMouseButton(1)) && !ship.GetIsShutDown())
        //    {
        //        fireRaycastFlag = true;
        //        //FireRaycast();
        //        ship.precisionRotationSpeed = scopePrecisionModeSpeed;
        //    }
        //    else if (Input.GetKeyUp(KeyCode.LeftShift) || Input.GetMouseButtonUp(1))
        //    {
        //        fireRaycastFlag = false;
        //        lineRenderer.enabled = false;
        //        ship.precisionRotationSpeed = defaultPrecisionModeSpeed;
        //    }
        //}
        //else
        //{
        //    fireRaycastFlag = false;
        //    lineRenderer.enabled = false;
        //}

        FireLineRendererTest();
    }

    private void FixedUpdate()
    {
        //if (fireRaycastFlag)
        //    FireRaycast();

       // FireLineRendererTest();

    }

    private void FireLineRendererTest()
    {
        lineRenderer.SetPosition(0, origin.transform.position);
        lineRenderer.SetPosition(1, origin.transform.position + origin.transform.up * laserLengthMax);
    }

    private void FireRaycast()
    {
        Vector2 dir = ship.transform.up;
        Ray2D ray = new Ray2D(origin.transform.position, dir);
        RaycastHit2D raycastHit;
        Vector3 endPosition = ray.origin + (laserLengthMax * ray.direction);

        raycastHit = Physics2D.Raycast(ray.origin, ray.direction, laserLengthMax, layerMask);

        if (raycastHit)
        {
            if (!(raycastHit.collider.GetComponent<StealthShipController>() && raycastHit.collider.GetComponent<StealthShipController>().cloakingController.IsCloaked()))
                endPosition = raycastHit.point;
        }

        if (raycastHit && raycastHit.collider.gameObject == ship.gameObject)
            Debug.Log("stop hitting yourself");

        lineRenderer.SetPosition(0, ray.origin);
        lineRenderer.SetPosition(1, endPosition);
        lineRenderer.enabled = true;

        scopeCam.transform.position = new Vector3(endPosition.x, endPosition.y, scopeCam.transform.position.z);

        fireRaycastFlag = false;
    }
}

I really appreciate your input on this. Technically, the problem is “solved” in that I can get the behaviour that I want (by turning off interpolation on my ships). It’s just unsatisfying to not understand why it wasn’t working when I think it should have been.

Among the other things, yes.

So it was the interpolation all along? Does it work as expected when it’s off?

If the interpolation is on, you should be able to use transform instead, and you should be able to achieve smooth motion. However, I can’t help you any further from the top of my head. I would have to recreate your setup, with some kind of ship and controls, frankly I do this in my leisure time, and I’m a bit too lazy for all that jazz. At least at this moment in time.

1 Like

Yeah, once I turned off interpolation and moved the FireRaycast() call to Update, it worked perfectly. I’m just stubbornly trying to make it work with interpolation for no other reason than because I think it should work.

I drop into these forums every so often, looking for stuff that’s basic enough that I can help, so it’s nice to have that come around a bit with more experienced folks dropping some insight to help me out. :slight_smile: