Hello everyone,
I'm trying to make a transparent Cube follow the cursor except when the cursor is on the top side of an opaque Cube.
To do this I use a plane(map), parallel to the top side of the cubes. I cast 2 Rays, one of which only hits the plane and the other only hits the opaque cube. Then I compare the two raycasthit points.
If the Opaque cube is not hit or is hit at a point under the plane, the transparent cube moves.
However, when using RaycastHit.point.y to compare the two, the raycasthit of the plane returns a wrong value with RaycastHit.point.y. (Which should always be 0)
When I Log it out in the console the value in the vector object and the y coordinate which is put out are different.
The other raycast does not have this problem.
if(Physics.Raycast(ray, out RaycastHit raycastHit2, float.MaxValue, mapOverlayLayer)){
Debug.Log("r2");
Debug.Log(raycastHit2.point);
Debug.Log(raycastHit2.point.y);
if(Physics.Raycast(ray, out RaycastHit raycastHit, float.MaxValue, gameObjectLayer)){
Debug.Log("r1");
Debug.Log(raycastHit.point);
Debug.Log(raycastHit.point.y);
GameObject hitObject = raycastHit.transform.gameObject;
if(hitObject.tag == "GenericMapObject"){
if(raycastHit2.point.y <= raycastHit.point.y){
Debug.Log("Mapoverlay is lower/equal");
}else{
Debug.Log("Mapoverlay is higher");
MoveCursorCube(raycastHit2.point);
}
}
}else{
Debug.Log("Only Mapoverlay");
MoveCursorCube(raycastHit2.point);
}
}
}
I could just compare one raycast to 0 statically, but I'm still interested why this might occur. Any Ideas?
Related
I want to be able to interact with my gameobjects while organising my sprites with Sorting Layer.
Right now, Sorting Layer do not affect my Raycast/collider situation and only change the visual appearance of my order.
In my Test scene I have two sprites: a Background and one Object.
Both have a 2D Box Collider component.
My Raycast script is suppose to detect on which Object my mouse is hovering on; either the Background (BG) or the Object, which lays on top of the BG.
As long as the BG collider is enabled in the inspector, my script will ONLY recognize the BG, even if the other object lays on top of my BG (visually and according to the Sorting Layer) (both Z-positions = 0, Sorting Layer Default (BG: 0, Object 1)).
The collider have the correct size and if I disable or remove the Collider from the BG, my script will detect the other Object just fine.
The only way to make sure that my Object gets detected, while my BG collider is activated is by changing the Objects Z position to a position between my BG and my Camera (between smaller 0 and greater -10)
I tried to:
this is a fresh 2D project, so my cameras Projection is set to Orthograph, my BG and Object use Sprite Renderer and is use 2D collider
change the "Order in Layer", back and forth, including negative numbers.
add a Foreground (and Background) "Sorting Layer"
(both options only had a visual effect and did not affect the Raycast output)
change the positions in the hierarchy.
removing and re-adding the colliders. (!) I observed that the 2D Box collider I added last, will always be behind every other colliders. This is something I can work with for now. But will become a problem later in the future, when the project gets bigger.
I want to be able to interact with items and doors etc. (clicks and reactions to mouse hovering) in my 2D click adventure. If 2D Raycast is not the way to go to archive this, I would be glad to know about the proper technique to use in this situation.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RayCast : MonoBehaviour
{
[SerializeField] private Vector2 mousePosWorld2D;
RaycastHit2D hit;
void Update()
{
mousePosWorld2D = (Vector2)Camera.main.ScreenToWorldPoint(Input.mousePosition);
hit = Physics2D.Raycast(mousePosWorld2D, Vector2.zero);
if (hit.collider)
{
Debug.Log("Found collider: " + hit.collider.name);
}
else
{
Debug.Log("No collider found.");
}
}
}
I expected the "Sorting Layer" or "Order in Layer" to have an impact on my Raycast, while changing the Z position does nothing. But it is the other way around. Sorting Layer only have a visual effect and moving my Object closer to the camera (Z position) helps to detect the right collider.
How do I solve the problem?
What gets hit first from a Ray has only to do with how close the collider is to the camera, and nothing with the order in the Hierarchy or the SortinLayers.
To find the "highest" RaycastTarget based on the SortingLayer, I created the Script below.
We fire a Raycast and iterate over all the Colliders that we hit, by using a foreach-loop. Then the function will return the GameObject with the highest SortingLayer. Note: If there is more than one GameObject with the highest SortingLayer, the function will return the last one it found.
using UnityEngine;
public class Raycast : MonoBehaviour
{
// The name of the relevant Layer
[SerializeField] string layerToCheck = "Default";
void Update()
{
Vector2 mousePosWorld2D = (Vector2)Camera.main.ScreenToWorldPoint(Input.mousePosition);
GameObject result = GetHighestRaycastTarget(mousePosWorld2D);
// Do Stuff example:
Debug.Log($"Highest Layer: {result}");
if (Input.GetMouseButtonDown(0)
&& result != null)
{
Destroy(result);
}
}
// Get highest RaycastTarget based on the Sortinglayer
// Note: If multiple Objects have the same SortingLayer (e.g. 42) and this is also the highest SortingLayer, then the Function will return the last one it found
private GameObject GetHighestRaycastTarget(Vector2 mousePos)
{
GameObject topLayer = null;
RaycastHit2D[] hit = Physics2D.RaycastAll(mousePos, Vector2.zero);
foreach (RaycastHit2D ray in hit)
{
SpriteRenderer spriteRenderer = ray.transform.GetComponent<SpriteRenderer>();
// Check if RaycastTarget has a SpriteRenderer and
// Check if the found SpriteRenderer uses the relevant SortingLayer
if (spriteRenderer != null
&& spriteRenderer.sortingLayerName == layerToCheck)
{
if (topLayer == null)
{
topLayer = spriteRenderer.transform.gameObject;
}
if (spriteRenderer.sortingOrder >= topLayer.GetComponent<SpriteRenderer>().sortingOrder)
{
topLayer = ray.transform.gameObject;
}
}
}
return topLayer;
}
}
Have you tried with the script that it's in the Unity official documentation?
https://docs.unity3d.com/ScriptReference/Physics.Raycast.html
using UnityEngine;
// C# example.
public class ExampleClass : MonoBehaviour
{
void Update()
{
// Bit shift the index of the layer (8) to get a bit mask
int layerMask = 1 << 8;
// This would cast rays only against colliders in layer 8.
// But instead we want to collide against everything except layer 8. The ~ operator does this, it inverts a bitmask.
layerMask = ~layerMask;
RaycastHit hit;
// Does the ray intersect any objects excluding the player layer
if (Physics.Raycast(transform.position, transform.TransformDirection(Vector3.forward), out hit, Mathf.Infinity, layerMask))
{
Debug.DrawRay(transform.position, transform.TransformDirection(Vector3.forward) * hit.distance, Color.yellow);
Debug.Log("Did Hit");
}
else
{
Debug.DrawRay(transform.position, transform.TransformDirection(Vector3.forward) * 1000, Color.white);
Debug.Log("Did not Hit");
}
}
}
I have a plane object. This plane is fixed relative to camera (plane is camera's child and always facing the camera -- Fixed Plane).
This plane has some RenderTexture on it. Now I want to know, what UV coordinate is currently under the mouse cursor.
The following script is sitting on this plane object:
//....
_collider = gameObject.GetComponent<Collider>(); // Plane's mesh collider
//....
void FixedUpdate()
{
RaycastHit hit;
var p = UnityEngine.Input.mousePosition;
if (_collider.Raycast(Camera.main.ScreenPointToRay(p), out hit, 100f))
{
var meshCollider = hit.collider as MeshCollider;
var rend = hit.collider.GetComponent<Renderer>();
if (rend == null || rend.sharedMaterial == null || rend.sharedMaterial.mainTexture == null || meshCollider == null)
return;
var pixelUV = hit.textureCoord;
pixelUV.x *= rend.material.mainTexture.width;
pixelUV.y *= rend.material.mainTexture.height;
Debug.Log("UV=[" + hit.textureCoord.x + ";" + hit.textureCoord.y + "]" + ", XY=[" + pixelUV.x + ";" + pixelUV.y + "]");
}
}
But coordinates I see in the log are very strange. First of all, when I change aspect ratio in a viewport, point that had XY[51,466] in 16:10 becomes XY[95,464] in 4:3 and so on. Secondly, offset is so huge that I am getting UV readings even if mouse pointer is nowhere near this plane.
I can't figure out how to raycast this right.
How to correctly get these UV readings regardless of a screen size?
UPD:
I ended up ditching mouse pointer entirely. Code above actually works well if you hide cursor and just look at the collision detection. Now I am showing a really small sphere at the ray hit point, and when you move your mouse, this sphere smoothly follows plane surface: now this is my "pointer". It works really well, and even better than "real" pointer: my 3d-cursor actually follows object geometry. And as this sphere represent actual raycast hit point, precision is great.
I really like this workaround, but question is still open.
Take a look at RaycastHit.textureCoord. (Link for Reference)
You can get a RaycastHit-object using somethin like this:
...
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit, 100))
{
//use hit.textureCoord here
}
...
Source
Hope that helps.
I've the point of origin readily available where my mouse is on screen like so,
ray = Camera.main.ScreenPointToRay(Input.mousePosition);
Now imagine a cube. Anywhere you click on this cube, a line is drawn from the edge clicked on, through the object, and stops at the other end. Orientation, vertical or horizontal, is determined by which side is clicked on, one of the 4 sides, or top or bottom.
How does one determine the distance (from one edge of a mesh to the other), and orientation (vertical or horizontal)?
Thoughts?
Only idea I have so far is to use collision detection and using CollisionEnter as the start point and somehow draw a line that reaches the opposite end of the mesh and using CollisionExit to determine the destination (or exit) point. Then doing some calculation to determine the distance between the Enter and Exit methods.
The only way I can think of approaching this would be to cast a ray back in the other direction....
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
//offset the ray, keeping it along the XZ plane of the hit
Vector3 offsetDirection = -hit.normal;
offsetDirection.y = 0;
//offset a long way, minimum thickness of the object
ray.origin = hit.point + offsetDirection * 100;
//point the ray back at the first hit point
ray.direction = (hit.point - ray.origin).normalized;
//raycast all, because there might be other objects in the way
RaycastHit[] hits = Physics.RaycastAll(ray);
foreach (RaycastHit h in hits)
{
if (h.collider == hit.collider)
{
h.point; //this is the point you're interested in
}
}
}
This offsets the ray to a new location so that it retains the same XZ coordinates of the original hit, so the resulting endpoints form a line that is perpendicular with the world / scene Y axis. To do this we use the camera's Forward direction (as we want to get a point farther away from the view point). If we wanted to get a point for a line that is perpendicular to the hit surface (parallel to the surface normal) we could create an offset using the hit.normal instead.
You will probably want to put a layermask or maxdist parameter into the two raycast methods (so it checks fewer things and is faster), but that's on you.
Original code: which finds the two endpoints of a "single" ray cast through the object.
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
//offset the ray along its own direction by A LOT
//at a minimum, this would be the maximum thickness of any object we care about,
//PLUS the distance away from the camera that it is
ray.origin += ray.direction * 100;
//reverse the direction of the ray so it points towards the camera
ray.direction *= -1;
//raycast all, because there might be other objects in the way
RaycastHit[] hits = Physics.RaycastAll(ray);
foreach(RaycastHit h in hits)
{
if(h.collider == hit.collider)
{
h.point; //this is the point you're interested in
}
}
}
I have a reversed mesh sphere, with the normals pointing inside, and 6 planes making a cubemap outside this sphere, algo with the normals pointing inside.
I want to know the position of a raycast hit from camera in (0,0,0), also the center of those two figures, to the sphere.
I tryed to use convex mesh collider but the sphere has to much vertex and Unity doesn´t support it. My idea was to check the collision to the cube, and then make a collision to the sphere (with a normal sphere collider) with the direction reversed from the camera.
The second Raycast should come to the point of the first collision and collide with the sphere, but it reports that "print("I'm looking at nothing!");"
public void launchBttn()
{
GameObject.FindGameObjectWithTag("coordText").GetComponent<Text>().text = transform.forward.ToString();
Ray rayBox = GetComponent<Camera>().ViewportPointToRay(transform.forward);
RaycastHit hit;
if (Physics.Raycast(rayBox, out hit))
print("I'm looking at " + hit.transform.name);
else
print("I'm looking at nothing!");
GameObject.FindGameObjectWithTag("collText").GetComponent<Text>().text = hit.transform.position.ToString();
Vector3 fwd = -transform.TransformDirection(Vector3.forward);
RaycastHit hitSphere;
if (Physics.Raycast(hit.transform.position, fwd, out hitSphere))
{
print("I'm looking at " + hitSphere.transform.name);
}
else
{
print("I'm looking at nothing!");
}
GameObject.FindGameObjectWithTag("sphereCollText").GetComponent<Text>().text = hitSphere.transform.position.ToString();
}
First, I'd like to apologize because this is a very basic and repetitive question, but I am completely new to game development.
I understand that I can find a distance between two objects using:
float dist = Vector3.Distance(other.position, transform.position);
However, how can I find the distance between a point of one object to other object?
For instance let's say my object is this sphere
Now, how can I return an array that says that there are no objects to the left (null), in the front there is an object at 1, and to the right there is an object at 0.5?
Thank you for your patience and understanding
I'm not exactly sure what you wan't to achieve...
If you wan't to get potential objects at 0.5, 1, 1.5, etc. on lets say the Z Axis you probably would want to do this with raycasting.
If you wish to check for any objects returning the direction dependant to the Z Axis (0.5, 0.856, 1.45, etc) in contrast, you probably would either
use a scaled sphere collider and add the colliding object with the OnCollisionEnter Callback to the array/List
iterate through every object of the scene and check it's relative pos
Use Raycast Probes procedurally (using a density float and Raycasting every density offset on the Z Axis and checking if the ray hit anything ...)
...
Totally dependant on your case of use, and whether you use 2D or 3D.
Seneral
EDIT: Here's what you would want in 2D to get a wall withing range maxRange on the layer optionalWallLayer in direction y 1 (up in unity 2D)
float maxRange = 10.0f;
RaycastHit hit;
Vector3 dir = transform.TransformDirection(new Vector3 (0, 1, 0));
if (Physics.Raycast(transform.position, dir, maxRange, out hit, LayerMask optionalWallLayer))
{
Debug.Log ("Wall infront of this object in Range" + hit.distance);
// hit contains every information you need about the hit
// Look into the docs for more information on these
}
This would likely go into the Update Function of your Sphere's MonoBehaviour.
The option with the Sphere collider is useful if you are not sure if you are going to hit your obstacles. If these are small, you would likely want to add the said Sphere Collider as a component to your sphere, scale it up to your maximum distance, and add a MonoBehaviour script to the sphere, which would contain something like this:
public List<Transform> allObjectsInRange; // GameObjects to enclose into calculations, basically all which ever entered the sphere collider.
public List<float> relatedDistances;
void Update () {
// basic loop to iterate through each object in range to update it's distance in the Lists IF YOU NEED...
for (int cnt = 0; cnt < allObjectsInRange.Count; cnt++) {
relatedDistances[cnt] = Vector2.Distance (transform.position, allObjectsInRange[cnt].position);
}
}
// Add new entered Colliders (Walls, entities, .. all Objects with a collider on the same layer) to the watched ones
void OnCollisionEnter (Collision col) {
allObjectsInRange.Add (col.collider.transform);
relatedDistances.Add (Vector2.Distance (transform.position, col.collider.transform));
}
// And remove them if they are no longer in reasonable range
void OnCollisionExit (Collision col) {
if (allObjectsInRange.Contains (col.collider.transform)) {
relatedDistances.RemoveAt (allObjectsInRange.IndexOf (col.collider.transform));
allObjectsInRange.Remove (col.collider.transform);
}
}
That should do it, either of the options, based on your exact case, again:) Note both are pseudo-codes... Hope it helps!