Is there an engine bug with Line renderer reflection in 2D space?

Hi! I’ve hit a bug while working on a 2D Unity project and I just can’t fix it

I hope the attached .gif file shows the problem, but to summarize; I’m making a laser reflection system that makes my laser bounce on some surfaces but not others. I tried this in 3D space before and it worked fine, but for some reason, it just won’t work in 2D space projects. As you can see, the line stutters and doesn’t reflect continuously like it should
I’ve added the code employed as well

Thanks

using UnityEngine;

[RequireComponent(typeof(Rigidbody2D))]
[RequireComponent(typeof(LineRenderer))]
public class PlayerController : MonoBehaviour
{
    #region Movement
    [Header("Movement")]
    Rigidbody2D rigidbody;
    [SerializeField] float moveSpeed = 10;
    #endregion
    [Space]

    #region Laser data
    [Header("Laser Data")]
    LineRenderer lineRenderer;
    Ray2D ray;
    RaycastHit2D hit;
    [SerializeField] LayerMask hitMask;
    [SerializeField] Material laserMat;
    [SerializeField] Transform emitter;
    [SerializeField] Transform mesh;
    [SerializeField] float totalLength = 30f;
    [SerializeField] int numberOfReflections = 3;
    #endregion

    private void Awake()
    {
        InitRigidBody();
        InitLineRenderer();
    }

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        Move(GetInput(), moveSpeed);
        UpdateRotation();
        OnMainFireButton();
        //CastRay();
    }

    void InitRigidBody()
    {
        rigidbody = GetComponent<Rigidbody2D>();
        rigidbody.gravityScale = 0;
        rigidbody.freezeRotation = true;
    }

    void InitLineRenderer()
    {
        lineRenderer = GetComponent<LineRenderer>();
        lineRenderer.startWidth = lineRenderer.endWidth = .1f;
        lineRenderer.material = laserMat;
    }

    Vector2 GetInput()
    {
        Vector2 input = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical"));
        input.Normalize();
        return input;
    }

    void Move(Vector2 _input, float _speed)
    {
        rigidbody.velocity = _input * _speed;
    }

    void UpdateRotation() //Updates the player character's rotation based on the cursor's current position (Called in fixed update to fix a stuttering problem, idk why that happens, but this seems to fix it)
    {
        Vector3 pos = Camera.main.WorldToScreenPoint(transform.position);
        Vector3 dir = Input.mousePosition - pos;
        float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg - 90; //Subtract 90 degrees for adjustment
        transform.rotation = Quaternion.AngleAxis(angle, Vector3.forward);
    }

    void OnMainFireButton()
    {
        if(Input.GetButton("Fire1"))
        {
            CastRay();
        }
        else
        {
            lineRenderer.positionCount = 0;
        }
    }

    Vector2 DirectionReflection(Vector2 _direction, Vector2 _surfaceNormal)
    {
        Vector2 reflection;
        _surfaceNormal.Normalize();
        reflection = _direction - (2 * (Vector2.Dot(_direction, _surfaceNormal)) * _surfaceNormal);
        return reflection;
    }

    void CastRay()
    {
        ray = new Ray2D(emitter.position, emitter.up);
        lineRenderer.positionCount = 1;
        lineRenderer.SetPosition(0, emitter.position);
        float remainingLength = totalLength;

        for(int i = 0; i < numberOfReflections; i++)
        {
            hit = Physics2D.Raycast(ray.origin, ray.direction, remainingLength, hitMask);
            if (hit.collider != null)
            {
                lineRenderer.positionCount++;
                lineRenderer.SetPosition(lineRenderer.positionCount - 1, hit.point);
                remainingLength -= Vector2.Distance(ray.origin, hit.point);
                ray = new Ray2D(hit.point, Vector2.Reflect(ray.direction, hit.normal));

                if (hit.collider.tag != "Mirror")
                {
                    break;
                }
            }
            else
            {
                lineRenderer.positionCount++;
                lineRenderer.SetPosition(lineRenderer.positionCount - 1, ray.origin + ray.direction * remainingLength);
                break;
            }
        }
    }
}

Hi Ahmed,

In which course and lecture are you? The tag is/was an “Unreal Engine” tag but your gif animation shows Unity.

Ah! My apologies! I posted in the Unreal discussion thread, but I meant Unity

My question regards Unity

Should I re-post on a Unity thread?

That’s not necessary. I changed the tags for you. :slight_smile:

1 Like

So after a lot of hair pulling I think I am quite sure the issue is with the fact that ray appears to be hitting the last hit item multiple times causing the bounce to not continue. I fixed this by setting the layer on a item that was hit to a different one so it wont be hit a second time and setting it back to the “reflection” layer every time it starts checking the hits. This makes my solution problematic as multiple bounces on the same object wont work but for single bounces it works as shown in the screen grab.

out

Thanks man! I did notice that there are multiple reflection point with the same coordinates when the bug happens, which means that the line bounces multiple times… on the same surface? what???

I have spent all of yesterday trying to figure this out, but no one is talking about this issue for some reason

Thanks again for your input bro!

Yea I wouldn’t call it a bug in the engine since the point is on a collider and its supposed to detect that as a hit, so its like something to take into consideration in the bounce logic unless they have claimed it handles this automatically.

1 Like

So, I solved it… kinda

Using codemonk’s solution, I was able to make the system work to an extent
This will work for single reflector objects and it will not bounce on the same object more than once

Unfortunately, this will not work for tilemaps as Unity treats the entire tile collection as a single object, so the line will bounce only once and will then ignore the reflector objects

Thanks for codemonk for the help and if anyone has a better idea for fixing this issue, please share

using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(Rigidbody2D))]
[RequireComponent(typeof(LineRenderer))]
public class PlayerController : MonoBehaviour
{
    #region Movement
    [Header("Movement")]
    Rigidbody2D rigidbody;
    [SerializeField] float moveSpeed = 10;
    Vector2 input;
    #endregion
    [Space]

    #region Laser data
    [Header("Laser Data")]
    LineRenderer lineRenderer;
    [SerializeField] LayerMask hitMask;
    [SerializeField] Material laserMat;
    [SerializeField] Transform emitter;
    [SerializeField] Transform mesh;
    [SerializeField] float totalLength = 30f;
    [SerializeField] int numberOfReflections = 3;
    #endregion

    private void Awake()
    {
        InitRigidBody();
        InitLineRenderer();
    }

    // Update is called once per frame
    void Update()
    {        
        input = GetInput();
        Move(input, moveSpeed);
        OnMainFireButton();
    }

    private void FixedUpdate()
    {
        UpdateRotation();
    }

    void InitRigidBody()
    {
        rigidbody = GetComponent<Rigidbody2D>();
        rigidbody.gravityScale = 0;
        rigidbody.freezeRotation = true;
    }

    void InitLineRenderer()
    {
        lineRenderer = GetComponent<LineRenderer>();
        lineRenderer.startWidth = lineRenderer.endWidth = .1f;
        lineRenderer.material = laserMat;
    }

    Vector2 GetInput()
    {
        Vector2 input = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical"));
        input.Normalize();
        return input;
    }

    void Move(Vector2 _input, float _speed)
    {
        rigidbody.velocity = _input * _speed;
    }

    void UpdateRotation() //Updates the player character's rotation based on the cursor's current position (Called in fixed update to fix a stuttering problem, idk why that happens, but this seems to fix it)
    {
        Vector3 pos = Camera.main.WorldToScreenPoint(transform.position);
        Vector3 dir = Input.mousePosition - pos;
        float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg - 90; //Subtract 90 degrees for adjustment
        transform.rotation = Quaternion.AngleAxis(angle, Vector3.forward);
    }

    void OnMainFireButton()
    {
        if(Input.GetButton("Fire1"))
        {
            CastRay();
        }
        else
        {
            lineRenderer.positionCount = 0;
        }
    }
    
    void CastRay()
    {
        List<Reflector> reflectors = new List<Reflector>();
        Ray2D ray = new Ray2D(emitter.position, emitter.up);
        lineRenderer.positionCount = 1;
        lineRenderer.SetPosition(0, emitter.position);
        float remainingLength = totalLength;

        for(int i = 0; i < numberOfReflections; i++)
        {
            RaycastHit2D hit = Physics2D.Raycast(ray.origin, ray.direction, remainingLength, hitMask);
            if (hit.collider != null)
            {
                lineRenderer.positionCount++;
                lineRenderer.SetPosition(lineRenderer.positionCount - 1, hit.point);
                remainingLength -= Vector2.Distance(ray.origin, hit.point);
                ray = new Ray2D(hit.point, Vector2.Reflect(ray.direction, hit.normal));

                if (hit.collider.tag != "Mirror")
                {
                    break;
                }
                else
                {
                    Reflector newReflector = new Reflector(hit.collider, LayerMask.LayerToName(hit.collider.gameObject.layer));
                    reflectors.Add(newReflector);
                    hit.collider.gameObject.layer = LayerMask.NameToLayer("Default"); //Set to the default layer since it is gets created by default with every project, so there's little chance that it doesn't exist
                }
            }
            else
            {
                lineRenderer.positionCount++;
                lineRenderer.SetPosition(lineRenderer.positionCount - 1, ray.origin + ray.direction * remainingLength);
                break;
            }
        }

        foreach(Reflector reflector in reflectors) //This part is needed to fix the reflection bug
        {
            reflector.collider.gameObject.layer = LayerMask.NameToLayer(reflector.layerName);
        }
    }
}
using UnityEngine;

public class Reflector //Used to store a reflector object's data (part of the reflection bug fix)
{
    public Collider2D collider;
    public string layerName;

    public Reflector(Collider2D _collider, string _layerName)
    {
        collider = _collider;
        layerName = _layerName;
    }
}

1 Like

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.

Privacy & Terms