- Home /
Is there a better way to find where to start my raycast?
I'm making a little game about lasers for fun and have run into an inconvenience with my splitters. The point of the splitter is to reflect the laser and also allow it to pass through it at the same time. But the way I have made it work makes it that it gets trapped if i start the raycast where i hit from the last. My solution was to start the raycast 1 unit in the previous direction past the splitter because the objects are movable & spinable by the player and is about 1 unit long. Here is a picture, the light blue rectangle s are my splitters. I use gizmos to help visualize my raycasting. This isn't really optimal because what if the player wants to put a mirror, splitter, or other object near after it? Is there any way to find the exact opposite end of the sprite i hit? heres my code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Laser : MonoBehaviour
{
public int maxReflectionCount = 5;
public int maxSplitCount = 5;
public float maxStepDistance = 100;
private void OnDrawGizmos()
{
if (!Application.isPlaying)
{
return;
}
DrawPredictedReflection(this.transform.position, this.transform.up, maxReflectionCount, maxSplitCount);
}
void DrawPredictedReflection(Vector2 position, Vector2 direction, int reflectionsRemaining, int splitsRemaining)
{
var gizmoHue = (reflectionsRemaining / (this.maxReflectionCount + 1f));
Gizmos.color = Color.HSVToRGB(gizmoHue, 1, 1);
RaycastHit2D hit2D = Physics2D.Raycast(position, direction, maxStepDistance);
if (hit2D) //did we hit somthing?
{
Gizmos.DrawLine(position, hit2D.point);
Gizmos.DrawWireSphere(hit2D.point, 0.25f);
if (hit2D.transform.gameObject.tag == "Receiver")
{
Debug.Log("Receiver hit");
}
if (hit2D.transform.gameObject.tag == "Mirror") //mirror hit. set new pos where hit. reflect angle and make that new direction
{
Debug.Log("Mirror Hit");
direction = Vector2.Reflect(direction, hit2D.normal);
position = hit2D.point + direction * 0.01f;
if (reflectionsRemaining > 0)
DrawPredictedReflection(position, direction, --reflectionsRemaining, splitsRemaining);
}
if (hit2D.transform.gameObject.tag == "Splitter") //reflect and go ahead
{
Debug.Log("Splitter hit");
if (splitsRemaining > 0)//go ahead
{
Debug.Log("Splitting");
Vector2 splitPosition = hit2D.point + direction * 1f;
DrawPredictedReflection(splitPosition, direction, reflectionsRemaining, --splitsRemaining);
}
direction = Vector2.Reflect(direction, hit2D.normal);
position = hit2D.point + direction * 0.01f;
if (reflectionsRemaining > 0)//reflect too
{
DrawPredictedReflection(position, direction, --reflectionsRemaining, splitsRemaining);
}
}
}
}
}
I'll accept all help and/or if you spot a way to optimize my script please let me know! :)
Answer by jkpenner · Jan 19, 2020 at 06:59 AM
Instead of using just Raycast, try to use RaycastAll. RaycastAll will return all objects hit along the ray. You will need to just order all the objects hit by their distance from the origin since the order is not guaranteed. Then you can process each object in order.
Physics.RacastAll Reference
Yes, that's probably the easiest solution if there's no "refraction" inside the splitter. i.e. the ray just passes through the splitter in a straight line.
If refraction is desired you would need to calculate the refraction angle inside the material and just cast a ray from the other side towards your first hit in the opposite refraction direction to deter$$anonymous$$e the hit point where the ray should leave. There again you need to calculate the proper refraction angle to deter$$anonymous$$e where the outgoing ray should go. Of course if you want internal reflection as well this can be done as well. For more information see Refraction (wikipedia) and / or one of those videos.
$$anonymous$$eep in $$anonymous$$d that reflection / refraction does not always happen and not equally strong. Though since it's a game of course you can make up any kind of mechanic that suits your gameplay.
I love this, this is great. Raycast all is a good short solution but casting a ray backwards to deter$$anonymous$$e the point on the other side is what i need. I do want to add all i can like lenses, prisms and stuff so this sounds like the best solution.
Answer by Major_Lag · Jan 19, 2020 at 08:16 PM
New working code from @Bunny83 solution:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Laser : MonoBehaviour
{
public int maxReflectionCount = 5;
public int maxSplitCount = 5;
public float maxStepDistance = 100;
private void OnDrawGizmos()
{
if (!Application.isPlaying)
{
return;
}
DrawPredictedReflection(this.transform.position, this.transform.up, maxReflectionCount, maxSplitCount);
}
void DrawPredictedReflection(Vector2 position, Vector2 direction, int reflectionsRemaining, int splitsRemaining)
{
var gizmoHue = (reflectionsRemaining / (this.maxReflectionCount + 1f));
Gizmos.color = Color.HSVToRGB(gizmoHue, 1, 1);
RaycastHit2D hit2D = Physics2D.Raycast(position, direction, maxStepDistance);
if (hit2D) //did we hit somthing?
{
Gizmos.DrawLine(position, hit2D.point);
Gizmos.DrawWireSphere(hit2D.point, 0.25f);
if (hit2D.transform.gameObject.tag == "Receiver")
{
Debug.Log("Receiver hit");
}
if (hit2D.transform.gameObject.tag == "Mirror") //mirror hit. set new pos where hit. reflect angle and make that new direction
{
Debug.Log("Mirror Hit");
direction = Vector2.Reflect(direction, hit2D.normal);
position = hit2D.point + direction * 0.01f;
if (reflectionsRemaining > 0)
DrawPredictedReflection(position, direction, --reflectionsRemaining, splitsRemaining);
}
if (hit2D.transform.gameObject.tag == "Splitter") //reflect and go ahead
{
Debug.Log("Splitter hit");
if (splitsRemaining > 0)//go ahead
{
Debug.Log("Splitting");
Vector2 splitPosition = new Vector2();
Vector2 findOppBegin = hit2D.point + direction * 1f;
RaycastHit2D[] findOppHit = Physics2D.RaycastAll(findOppBegin, -direction);
for ( int i = 0; i <= findOppHit.Length; i++) //findOppHit[i].transform.gameObject != hit2D.transform.gameObject
{
if (findOppHit[i].transform.gameObject == hit2D.transform.gameObject)
{
splitPosition = findOppHit[i].point + direction * 0.01f;
break;
}
}
DrawPredictedReflection(splitPosition, direction, reflectionsRemaining, --splitsRemaining);
}
direction = Vector2.Reflect(direction, hit2D.normal);
position = hit2D.point + direction * 0.01f;
if (reflectionsRemaining > 0)//reflect too
{
DrawPredictedReflection(position, direction, --reflectionsRemaining, splitsRemaining);
}
}
}
}
}
again any errors or optimizations let me know!
I would be a bit careful with the code. First, if you still start the reverse raycasts from a unit away, if your reflective surface is somehow larger than a unit in any angle, your second raycasts could start inside.
Similarly, if another object is less than a unit away, it could get caught on the reverse raycasts.
This can be solved by checking colliders in RaycastHit2D against each other, and either increase or reduce the reverse raycasts offset, but there might be a better solution.
Create an object of the exact same size as your mirrors, but inversed. The collider in the center is just empty space, and the space around it is filled with colliders.
When a raycasts hits, convert the hit location into local space.
Using that point, start a new raycasts in the inversed mirror, to see what it would hit. Possibly change the angle to account for refraction.
Convert the result again into worldspace using the original mirror, you've got your new raycast origin.
This is a very theoretical idea, but it should work. Also do not that the transform of the inverse mirror must be completely neutral (0 pos, 0 rot, 1 scaling)
if you still start the reverse raycasts from a unit away, if your reflective surface is somehow larger than a unit in any angle, your second raycasts could start inside.
I had made it 1 unit because i know i'm not going to be making any objects longer than 1 unit however if i ever decide against its a easy fix by multiplying the direction by a larger number in
Vector2 findOppBegin = hit2D.point + direction * 1f;
if another object is less than a unit away, it could get caught on the reverse raycasts.
I have thought of this and decided to use RaycastAll in the opposite direction ins$$anonymous$$d so it gets all objects hit in that direction. Then use a forloop to go through the array of objects until i find a match of the object i last hit.
RaycastHit2D[] findOppHit = Physics2D.RaycastAll(findOppBegin, -direction);
for ( int i = 0; i <= findOppHit.Length; i++)
{
if (findOppHit[i].transform.gameObject == hit2D.transform.gameObject)
{
splitPosition = findOppHit[i].point + direction * 0.01f;
break;
}
}
Nice, then your solution is pretty much sound to the best of my knowledge.