- Home /
Make ray follow mouse position from player origin (Isometric)
I'm trying to get a firing mechanic working in my isometric game. I want to be able to shoot along the x/z-axis but not have the ray go up or down the y-axis. Instead I want it "zero'd" out on the y-axis at all times.
Also the ray doesn't point precisely to the mouse position unless it is directly infront. As I veer left or right of the origin it keeps getting significantly less accurate at point at the mouse to the point it will be nearly 10 degrees off.
The white square denotes where the mouse cursor was. The print screen would not capture the mouse cursor for some reason.
using UnityEngine;
using System.Collections;
public class Weapon : MonoBehaviour {
public float fireRate = 0;
public float damage = 10;
public LayerMask notToHit;
float timeToFire = 0;
Transform firePoint;
void Awake () {
firePoint = transform.FindChild ("FirePoint");
if (firePoint == null) {
Debug.LogError ("firePoint is null");
}
}
void Update () {
Vector3 mousePostion = Camera.main.ScreenToWorldPoint (Input.mousePosition);
mousePostion.y = 0;
Vector3 firePointPosition = new Vector3 (firePoint.position.x, firePoint.position.y, firePoint.position.z);
float distToMouse = Vector3.Distance(mousePostion, firePointPosition) ;
RaycastHit hit;
Ray myRay = new Ray (firePointPosition, mousePostion);
Debug.DrawRay (firePointPosition, mousePostion);
if (fireRate == 0) {
if (Input.GetButtonDown("Fire1") && Physics.Raycast(myRay, out hit, distToMouse, notToHit)) {
Shoot();
}
}
}
void Shoot () {
Debug.Log ("Fire!");
}
}
How do I make a ray that stays on the same Y-axis as the player, can follow the mouse's movement along the x/z-axis in the world with precision?
Answer by robertbu · Aug 10, 2014 at 02:53 AM
When a camera is at an angle to a surface, you cannot use ScreenToWorldPoint() to find that position. The solution is a Raycast(). You could Raycast() against the green block, but if the surface is flat, then there is a cheaper way. You can use Unity's mathematical Plane class. How to setup the code will depend on how you setup your object. In particular whether the green block surface is on the XZ plane or the XY plane. Let me assume the XZ plane. To find the position it is best to use the position of the player (or whatever object you are going to place). A bit of untested example code:
Plane plane = new Plane(Vector3.up, player.position);
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
float dist;
plane.Raycast(ray, out dist);
Vector3 pos = ray.GetPoint(dist);
So pos is where you would move an object in world space so that it would be under the mouse position. Note this code assumes that the Raycast() cannot fail. If you allow the camera to look parallel to the green block or away from the green block, then you need to check the return value of the plane.Raycast() call.
Another solution is to use Collider.Raycast() to cast against the block (or against an invisible plane that passes through the player's pivot point).
plane.Raycast(ray, out dist);
Vector3 pos = ray.GetPoint(dist);
This is the only part that confuses me. Is the plane itself casting a ray? Or is the "ray" being cast through the mouse position from the camera intersecting the plane and that is what you are detecting with "plane.Raycast(Ray, out dist);"
Then the Vector3 pos is the point on the ray that the mouse lies when intersecting the plane?
So if I wanted to fire something to that point where the mouse lies would all I need to do is use the "ray.GetPoint(dist);" and fire the the object to the "Vector3 pos"?
$$anonymous$$aking sure I understand everything here.
I tried it though and ended up with this:
Plane plane = new Plane (Vector3.up, transform.position);
Ray rayTo$$anonymous$$ouse = Camera.main.ScreenPointToRay (Input.mousePosition);
float dist;
plane.Raycast (rayTo$$anonymous$$ouse, out dist);
Vector3 targetPos = rayTo$$anonymous$$ouse.GetPoint (dist);
Debug.DrawRay (transform.position, targetPos);
Ends up causing things like this:
A mouse moves on a 2D surface. Unity is 3D, so the world position of the mouse in 3D space will vary based on the distance the position is from the mouse/camera. A Raycast() of any sort (Physics.Raycast(), Collider.Raycast(), Plane.Raycast(), Physics2D.Raycast()), shoots at beam starting at some position going in some direction to see what/where something is hit.
So based on the perspective geometry, a ray is constructed (ScreenPointToRay()). A Ray has an origin and a direction. Plane.Raycast() takes the specified ray and casts it against the mathematical plane. It returns two pieces of information. The return value from the function is a boolean that says whether the ray hit anything. If the direction of the ray is parallel to the plane or away from the plane, then it will fail to hit the plane. The 'dist' is filled in by the function as well. Assu$$anonymous$$g the Plane.Raycast() succeeds, then 'dist' is the distance from the origin of the ray that the ray hit the plane. We can convert that distance into a world point by using Ray.GetPoint() and passing the distance from the origin. GetPoint() walks down the ray from the origin the specified distance to deter$$anonymous$$e a position.
Note given the geometry in the image, there is no way for the ray to miss the plane, so I did not check its return value. But I did indicate that if you allow the camera to look away, then you would need to check the return value to see if the plane was hit. Also this is a mathematical plane parallel to the XZ plane and passing through the pivot point of the 'character'.
I missed your images with your comment above. Not sure why. Did you add them in an edit? Anyway, something is off. Just to make sure, your grey surface is on the XZ plane? If not, then is one source of the problem. Also I'm not sure of your na$$anonymous$$g which makes be consider I missed what you are trying to here. 'targetPos' should be the position directly under the mouse just above the grey surface (half the height of a cube if your cubes are resting on the grey surface). If you had an object and did:
transform.position = targetPos;
...that that object should appear under the mouse cursor just above the grey surface.
Yes you are exactly right in your thinking and yes I did add them in an edit so here are the direct links.
The ray can now go 360 deg around the player so that problem is solved. The plane is 0'd on the X/Z so that is not the issue. The issue is it seems to not get where the mouse precisely is but it does accurately follow its movement.
It is almost as if it is "offset" by a certain amount. I thought centering the camera on the player would help but maybe I did that wrong. Could it also be because the camera is a child of the player yet is not at the "0,0,0" position? The camera's transform is currently (0,0,-15) because that was the only way I could center it on the player, I do not know how else to do it.
The issue is likely where you are placing the plane for your plane.Raycast(). Here is a diagram. It is not an accurate representation of your current geometry, but only serves to illustrate how plane placement impacts the final position.
Image this is cut-away side view of your grey plane with a blue block (for scale) placed on the plane. Imagine that your plane is centered at Vector3.zero. The large arrow is the view onto the plane. The small green arrow is the raycast. The black and red lines represent three different placements of the plane used in the Plane.Raycast(). And the black dots represent the raycast hit for each of the possible planes. The bottom plane uses Vector3.zero as its position. The middle one uses transform.position of the block and the upper one is the top of the block...something like:
var pos = block.position + Vector3.up * 0.5 * blockHeight;
As you can see, which plane used impacts the position of the hit point as view by the user. So you need to pick the point for your plane (second parameter) that works for your geometry. I'd assumed you were looking for the calculation to position a player on the plane when I suggested player.position, but Vector3.zero may be what you are looking for.