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;
}
}
}
}
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.
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
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.
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;
}
}