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
}
}
}
Related
I'm making a top down space resource-gathering game, with 3D models.
I'm trying to cast a ray towards my mouse position, to detect collisions with the objects in my game,
for my little spaceship to know which direction to head to find resources.
The resources are in the form of asteroids the player has to break to gather.
In the code, I use the "center" vector to find the middle of the screen/player position, as the camera follows the player around. All movement happens along the X,Y axis. center.Z is set to 15 simply to counteract the -15Z position of the main camera in worldspace.
So far, the code "works" to the extent that it can detect a hit, but the ray doesn't travel in the direction of the mouse. I have to hold it way off for it to hit, and it's difficult to replicate, to the point where it almost seems random. Might the ray not be able to make sense of the mouse position?
In case I've worded myself poorly, this search ability is not meant to break the asteroid, simply locate it. The breaking ability is a separate script.
Code:
public class AbilitySearch : MonoBehaviour
{
Vector3 center;
Vector3 mousePos;
Vector3 direction;
private float range = 100f;
void Update()
{
center = Camera.main.ViewportToWorldPoint(new Vector3(0.5f, 0.5f, 15.0f));
mousePos = Input.mousePosition;
direction = mousePos - transform.position;
if (Input.GetMouseButton(1))
{
Search();
}
}
void Search()
{
RaycastHit hit;
if (Physics.Raycast(center, direction, out hit, range))
{
Debug.Log("You hit " + hit.transform.name);
}
}
}
Thanks in advance
When using Input.mousePosition the position is a pixel coordinate on your screen, so if your mouse was in the bottom left corner of your screen it would be 0,0. This causes the direction to be inaccurate. When what you need is to use the game space coordinates, which is done by using Camera.ScreenToWorldPoint(Input.mousePosition), as an example.
This solution allows you to click on a gameObject (provided it has a collider attached), move the current gameObject towards the clicked object, as well as gather an array of possible gameObjects (RaycastHit[s]) in the direction of the clicked one.
Vector3 destination;
RaycastHit[] possibleTargets;
bool isTravelling;
float searchDistance = 5f;
private void Update()
{
//If right mouse button has been pressed down
if (Input.GetMouseButtonDown(1))
{
RaycastHit hit;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hit)) //If ray collides with something
{
Search(hit);
}
}
//If going to destination
if (isTravelling)
{
float step = 2f * Time.deltaTime;
transform.position = Vector3.MoveTowards(transform.position, destination, step);
//If approx arrived
if (Vector3.Distance(transform.position, destination) < 0.1f)
{
isTravelling = false; //Stop moving to destination
Debug.Log("You've arrived");
}
}
}
private void Search(RaycastHit _hit)
{
//Normalise directional vectors, so (if needed) they can be multipled as expected
Vector3 direction = (_hit.point - transform.position).Normalized();
RaycastHit secondHit;
//Grab objects in direction of clicked object (including the one clicked)
possibleTargets = Physics.RaycastAll(transform.position, direction, searchDistance);
Debug.Log($"There are {possibleTargets.Length} possible targets ahead");
Debug.Log($"You hit {_hit.transform.name}");
//Set destination, and set to move
destination = _hit.point;
isTravelling = true;
}
You used the ViewportToWorldPoint method, which expects normalized viewport coordinates in range 0 to 1, but you supplied what seems to me as world coordinates of your camera as its parameter.
You only need to cast a ray from camera to mouse pointer world position (see first line of code in method FindAsteroid) to check for collision with asteroid. The returned RaycastHit provides you with information about the collision - hit position, gameobject, collider - which you can use for other game logic, e.g. shooting a projectile from spaceship to asteroid hit point.
I edited your class and included a screenshot from my simple scene below, which shows the two different "rays":
The yellow ray goes from camera to asteroid hit point
The magenta ray goes from spaceship position to asteroid hit point.
I would also recommend filtering the raycast to affect only specific colliders -
see LayerMask (section Casting Rays Selectively)
public class AbilitySearch : MonoBehaviour
{
private float range = 100f;
private Camera mainCam;
private void Awake()
{
// TIP: Save reference to main camera - avoid internal FindGameObjectWithTag call
mainCam = Camera.main;
}
private void Update()
{
if (Input.GetMouseButton(1))
{
if (FindAsteroid(out var asteroidHit))
{
// Draw a line from spaceship to asteroid hit position
Debug.DrawLine(transform.position, asteroidHit.point, Color.magenta, Time.deltaTime);
Debug.Log($"You hit {asteroidHit.transform.name}");
}
}
}
private bool FindAsteroid(out RaycastHit hit)
{
// Create a ray going from camera position to mouse position in world space
var ray = mainCam.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hit, range))
{
// Draw a line from camera to world mouse position
Debug.DrawLine(ray.origin, hit.point, Color.yellow, Time.deltaTime);
// An asteroid was found
return true;
}
// An asteroid was NOT found
return false;
}
}
Sample spaceship scene
I'm trying to fix up some bugs in my project and I've come across a problem with a mechanic where I want an object to move to my mouse cursor on click and stay 3 units above where I clicked.
Here is my code:
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
RaycastHit hit;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hit))
{
var newPosition = hit.point;
currentDestination = newPosition + new Vector3(0, 3.0f, 0);//mainly this
notAtDestinationYet = true;
}
}
However by using raycasts, If I hit anything above my terrain like the top of a building or something, the object with this script attached will always move 3 units above where the raycast hits.
How do I make it stay at a constant Y-Axis value of 3 units (or whatever amount) no matter what? Say by using a public int.
Simply change the line where you set the currentDestination to use the x and z values from newPosition and a constant y value, instead of adding 3f to the y value of newPosition.
currentDestination = new Vector3(newPosition.x, 3.0f, newPosition.z);
My camera casts a ray to the centre of the screen and I have an object that looks at that direction. The problem is, I made it such that certain keys rotate the camera, so there's a new centre, and the ray from the camera moves too but the object doesn't look at the new direction
Here's my code:
void Update (){
int x = Screen.width / 2;
int y = Screen.height / 2;
//Get centre of screen from camera
Ray ray = Camera.main.ScreenPointToRay (new Vector3 (x, y));
Debug.DrawRay (ray.origin, ray.direction * 1000, new Color (1f, 0.922f, 0.016f, 1f));
//Set object direction
object.transform.LookAt (ray.direction);
}
I'd really appreciate some help with this, thank you!
I found a solution. Instead of looking at ray direction, I got a point from the ray using GetPoint and made the object look at that. It's working fine now.
Well the ray would be starting from the camera and going towards the middle of the screen. So if you wanted the object to look at the camera every Update, the direction would be wrong, but also what if the object wasn't in the center of the screen? If I'm misinterpreting your question, please comment and explain further. I think a better way to get the forward direction of the camera would be to use Camera.main.transform.forward, and you could put that in a Physics.Raycast as well. To have something always look at the camera you could try object.transform.LookAt(Camera.main.transform.position, -Vector3.Up);.
Edit: Also, if you mean the have the object look the same direction as the camera, you could always try object.transform.forward = Camera.main.transform.forward, or keep up with object.transform.rotation = Quaternion.LookRotation(Camera.main.transform.forward, Transform.up);
Alright new idea:
Ray ray = Camera.main.ViewportPointToRay(new Vector3(0.5F, 0.5F, 0));
RaycastHit hit;
if(Physics.Raycast(ray, out hit))
{
Vector3 point = hit.point;
object.transform.LookAt(point, Vector3.Up);
}
So I'm trying to make the gun on the turret of my tank point towards the center of the screen just like in World of tanks where you have a crosshair for where the gun is pointing and the crosshair for the center of the screen.
The problem is that the gun crosshair which is a UI image in world space parented to the gun, doesn't exactly line up with the center of the screen which is the big crosshair in the image [enter image description here][1].
edit: so this works but how can I change it to the x axis?
public class CenterCursor : MonoBehaviour
{
// speed is the rate at which the object will rotate
public float speed;
void FixedUpdate()
{
// Generate a plane that intersects the transform's position with an upwards normal.
Plane playerPlane = new Plane(Vector3.up, transform.position);
// Generate a ray from the cursor position
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
// Determine the point where the cursor ray intersects the plane.
// This will be the point that the object must look towards to be looking at the mouse.
// Raycasting to a Plane object only gives us a distance, so we'll have to take the distance,
// then find the point along that ray that meets that distance. This will be the point
// to look at.
float hitdist = 0.0f;
// If the ray is parallel to the plane, Raycast will return false.
if (playerPlane.Raycast(ray, out hitdist))
{
// Get the point along the ray that hits the calculated distance.
Vector3 targetPoint = ray.GetPoint(hitdist);
// Determine the target rotation. This is the rotation if the transform looks at the target point.
Quaternion targetRotation = Quaternion.LookRotation( targetPoint - transform.position);
// Smoothly rotate towards the target point.
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, speed * Time.deltaTime);
}
}
You can also try putting your turret object as a child of camera object...
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();
}