Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Shoot parallel relative to ground towards mouse position (Isometric Aiming)

Discussion in 'Scripting' started by Troas, Aug 8, 2014.

  1. Troas

    Troas

    Joined:
    Jan 26, 2013
    Posts:
    157
    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.



    *White Square denotes mouse position (Wouldn't show the mouse when print screening).




    Furthermore, the ray will not follow the mouse all 360 around it's origin, at the point it will see it as a y-axis movement instead of along the z-axis.



    Here is my code:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class Weapon : MonoBehaviour {
    5.  
    6.     public float fireRate = 0;
    7.     public float damage = 10;
    8.     public LayerMask notToHit;
    9.  
    10.     float timeToFire = 0;
    11.     Transform firePoint;
    12.  
    13.     void Awake () {
    14.         firePoint = transform.FindChild ("FirePoint");
    15.         if (firePoint == null) {
    16.             Debug.LogError ("firePoint is null");    
    17.         }
    18.     }
    19.  
    20.     void Update () {
    21.        
    22.         // Repeated this from the Shoot() method so I could see what the ray was actually doing, will be removed upon fixing
    23.         Vector3 mousePostion = new Vector3 ((Camera.main.ScreenToWorldPoint (Input.mousePosition).x), 0, (Camera.main.ScreenToWorldPoint (Input.mousePosition).z));
    24.         Vector3 firePointPosition = new Vector3 (firePoint.position.x, firePoint.position.y, firePoint.position.z);
    25.         Debug.DrawLine (firePointPosition, mousePostion);
    26.  
    27.         if (fireRate == 0) {
    28.             if (Input.GetButtonDown("Fire1")) {
    29.                 Shoot();
    30.             }
    31.         }
    32.         else{
    33.             if (Input.GetButtonDown ("Fire1") && Time.time > timeToFire) {
    34.                 timeToFire = Time.time + 1/fireRate;
    35.                 Shoot();
    36.             }
    37.         }
    38.     }
    39.  
    40.     void Shoot () {
    41.         Vector3 mousePostion = new Vector3 ((Camera.main.ScreenToWorldPoint (Input.mousePosition).x), 0, (Camera.main.ScreenToWorldPoint (Input.mousePosition).z));
    42.         Vector3 firePointPosition = new Vector3 (firePoint.position.x, firePoint.position.y, firePoint.position.z);
    43.         Debug.DrawLine (firePointPosition, mousePostion);
    44.         Debug.Log ("Fire!");
    45.     }
    46. }
    47.  
    How do I fix this?

    (BTW I have been trying to figure out raycasting for the past month or so, do not link me to the API, tutorial, ect. unless it specifically answers this questions. I have looked through them all).
     
  2. Troas

    Troas

    Joined:
    Jan 26, 2013
    Posts:
    157
    Code (CSharp):
    1. void Update () {
    2.  
    3.         Vector3 mousePostion = Camera.main.ScreenToWorldPoint (Input.mousePosition);
    4.         mousePostion.y = 0;
    5.         Vector3 firePointPosition = new Vector3 (firePoint.position.x, firePoint.position.y, firePoint.position.z);
    6.    
    7.         float distToMouse = Vector3.Distance(mousePostion, firePointPosition) ;
    8.    
    9.         RaycastHit hit;
    10.         Ray myRay = new Ray (firePointPosition, mousePostion);
    11.  
    12.         Debug.DrawRay (firePointPosition, mousePostion);
    13.  
    14.         if (fireRate == 0) {
    15.             if (Input.GetButtonDown("Fire1") && Physics.Raycast(myRay, out hit, distToMouse, notToHit)) {
    16.                 Shoot();
    17.             }
    18.         }
    19. }
    20.  
    21. void Shoot () {
    22.         Debug.Log ("Fire!");
    23. }
    Fire is seen in the console, mouse position is now 0 on Y. Debug line still does not follow mouse accurately. It's as if it records the mouse position with the same degree of error every-time. Anyone know how to fix? Should I make a video for it instead?
     
  3. Troas

    Troas

    Joined:
    Jan 26, 2013
    Posts:
    157
    Just updating this in-case anyone cares to know how it's solved. I'm going to try the following method that someone suggested to me.

    Code (CSharp):
    1. Plane plane = new Plane(Vector3.up, player.position);
    2. Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
    3. float dist;
    4. plane.Raycast(ray, out dist);
    5. Vector3 pos = ray.GetPoint(dist);
    Basically scrapping most the stuff above and instead casting a ray from the camera to the mouse position and intersect the plane. Then make a float that is stored as the output of the ray hitting the plane and store that position as a Vector3. Once I've done that I will have a point in space that represents where the mouse is at all times based on the plane created on the player's position. Then I will use that point to fire a bullet toward it with a Shoot() method.

    Will update when this is complete.
     
  4. Troas

    Troas

    Joined:
    Jan 26, 2013
    Posts:
    157
    So I came up with this code:

    Code (CSharp):
    1. void Update () {
    2.  
    3.         Plane plane = new Plane (Vector3.up, transform.position);
    4.         Ray rayToMouse = Camera.main.ScreenPointToRay (Input.mousePosition);
    5.         float dist;
    6.         plane.Raycast (rayToMouse, out dist);
    7.         Vector3 targetPos = rayToMouse.GetPoint (dist);
    8.  
    9.         Debug.DrawRay (transform.position, targetPos);
    10. }
    Ends up doing this. Once Again the White square denotes mouse position





     
    Last edited: Aug 10, 2014
  5. Troas

    Troas

    Joined:
    Jan 26, 2013
    Posts:
    157
    Tried changing "Vector3.up" to "Vector3.zero" but it didn't really do anything. Just discovered now what other issues there are. As the player moves, if the mouse stays still, the ray will also move. I think the fact that the camera is a child of player is messing everything up but I have no clue how to fix that.

    Please help I literally have no clue where to go next.




     
  6. Troas

    Troas

    Joined:
    Jan 26, 2013
    Posts:
    157
    I have now discovered that the ray is going from the players vector3.zero and pointing to the actual camera object. It is also registering the mouse's position as it follows the mouse input precisely but it is not centered on the mouse.

    This explains why it changes when I rotate or move as it is for some reason pointing to the camera object instead of the mouse.

    How do I make the ray point from the player to the mouse?

    Please help this is driving me nuts and I still have no clue where to go despite all the things I've tried looking up.
     
  7. Troas

    Troas

    Joined:
    Jan 26, 2013
    Posts:
    157
    I've found that I need to use the RaycastAll method in order to accomplish this. The only problem is I don't understand where I should cast it from.

    Should it be from the Camera.main.transform.position,Camera.main.transform.forward? Once I do that how should I go about debugging a drawray to see the raycast? (I know I need to tag it and the use an if statement to see if I hit that tag and store it).
     
  8. Vaidoras

    Vaidoras

    Joined:
    Sep 5, 2013
    Posts:
    21
    Hey I will be honest and say that I skimmed through the thread really fast, so forgive me I completely miss the target here. But I think I dealt with similar problem not long ago and came up with this solution.

    Essentialy youre creating a plane at y=0 and use its cast method to find out where your camera - mouse ray intersects it

    Code (CSharp):
    1.     public class ObjectSelectionManager : MonoBehaviour
    2.         {
    3.         private Camera ref_mainCamera;
    4.  
    5.         public Vector3 MousePosition;
    6.         private Plane zeroYPlane;
    7.  
    8.         void Start( )
    9.             {
    10.             ref_mainCamera = GameObject.FindGameObjectWithTag("MainCamera").camera;
    11.             //This is the tool you need - plane
    12.             zeroYPlane = new Plane(Vector3.up, Vector3.zero);
    13.             }
    14.  
    15.         void Update( )
    16.             {
    17.             Ray ray = ref_mainCamera.ScreenPointToRay(Input.mousePosition);
    18.  
    19.  
    20.             float _hitDistance;
    21.              //cast ray using zerpYplane to find out mouse position the ray intersects it
    22.             zeroYPlane.Raycast(ray, out _hitDistance);
    23.             MousePosition = ray.GetPoint(_hitDistance);
    24.             }
    25.  
    26.  
    27.         }//class
     
    Troas likes this.
  9. Troas

    Troas

    Joined:
    Jan 26, 2013
    Posts:
    157
    So once I get the MousePosition stored will I then use this to draw a new ray from the player position to the mouseposition in order to fire objects along that line?

    I actually went to it through this method but it only gets the position by casting a ray foward from the camera, not a ray from the camera to the mouse position and then everything behind it (which is essential if I want it to follow the mouse).

    Code (CSharp):
    1. void Update () {
    2.      
    3.         RaycastHit[] hits;
    4.         Vector3 ray_point_onfloor;
    5.         hits = Physics.RaycastAll(Camera.main.transform.position, Camera.main.transform.forward);
    6.         int i = 0;
    7.         while (i < hits.Length) {
    8.             RaycastHit hit = hits[i];
    9.          
    10.             if (hit.transform.tag == "floor") {
    11.                 ray_point_onfloor = hit.point;
    12.                 Debug.DrawLine (transform.position, ray_point_onfloor);
    13.                 break;
    14. }
    Code above makes a nice flat ray from player to the position but it doesn't follow the mouse. I'll try your method.
     
  10. Vaidoras

    Vaidoras

    Joined:
    Sep 5, 2013
    Posts:
    21
    No, you use the rays to find out the position you want to fire to. When you know your shooter position and the position he needs to fire at you will have enough info to figure out how to do it using Vectors. http://docs.unity3d.com/Manual/UnderstandingVectorArithmetic.html

    So here is sort of pseudoCode:
    Code (CSharp):
    1.  
    2. //1. Create a ray that fires from you camera to your mouse
    3. Ray ray = ref_mainCamera.ScreenPointToRay(Input.mousePosition);
    4.  
    5. //2. Create a flat invisible y=0 plane in 3D space,
    6. //and use its Raycast() to find out where that ray intersects the plane.
    7. zeroYPlane.Raycast(ray, out _hitDistance);
    8.  
    9. //3. Assign that intersection point to the mouse position
    10. MousePosition = ray.GetPoint(_hitDistance);
    11.  
    12.  
    13. //Now you have location you are targeting with your mouse. Time for vector math :)
    14.  
    15. //1.Figure out where is your target relative to the object that will do the shooting.
    16. [code=CSharp]Vector3 relativeVector = target.transform.position  -  Shooter.transform.position
    17.  
    18. //2. Adjust that Vectors y value so it would shoot straight instead of at the ground
    19. //    OR you can raise the whole plane a bit above ground level to avoid this step
    20. mousePosition = new Vector3 (MousePosition.x, y = whatevertheheightyourweaponisat, mousePosition.z)
    21.  
    22. //3. Use Shooter.Transform.Quaternion.LookAt() to face the shooter in the right direction
    23. //and then  shoot the weapon.
     
    Troas likes this.
  11. Troas

    Troas

    Joined:
    Jan 26, 2013
    Posts:
    157
    Ah ok this was exactly what I was thinking to do but I kept getting stuck with the ray going from the player to the mouse as if it were attached to the camera, so it pointed to the camera.. not to the point where the ray going from the cam->mouse intersected the y-plane.

    Thanks this helps a lot.

    Code (CSharp):
    1. zeroYPlane = new Plane(Vector3.up, Vector3.zero);
    This is where I was messing up. I didn't understand that I needed a "normal" before I set where the plane was cast from.
     
  12. Vaidoras

    Vaidoras

    Joined:
    Sep 5, 2013
    Posts:
    21
    Glad I could help :) good luck
     
    Troas likes this.
  13. Troas

    Troas

    Joined:
    Jan 26, 2013
    Posts:
    157
    Alright it works perfectly as you described the only problem is for w/e reason it is just a tiny bit offset from the mouses position just a little bit.

    Do I need to calculate the camera's angle into my code in order to get a proper "pinpoint" location of where my mouse is on screen vs where it intersects the plane?



    http://i58.tinypic.com/2dtq0l0.png

    It is indeed perfectly flat, it's just not super precise but I'm willing to bet the camera angle is responsible. I was thinking to create another camera that is perpendicular to the plane and cast a ray through the mouse that way, maybe that will project more accurately? Maybe that would be less accurate.
     
    Last edited: Aug 12, 2014
  14. Vaidoras

    Vaidoras

    Joined:
    Sep 5, 2013
    Posts:
    21
    I have hard time imagining what is causing this. And I also don't know what you mean by camera floor
    1. try setting you camera projection to perspective in inspector. I never worked with ortho cameras so cant tell if that's throwing it off. I think it is.
    2. I see that you'r ground cube is elevated from y=0 plane, assuming the plane you cast your ray with is at Y=0 this is not good. You want the plane to be at the height of the thing you'll be targeting with your mouse. Although I don't think this is what is causing the issue.

    if neither works I cant think of anything from the top of my head so post your code?
     
  15. Troas

    Troas

    Joined:
    Jan 26, 2013
    Posts:
    157
    This was the culprit.

    And I meant Camera Angle lol not floor, bad typo haha.

    Code (CSharp):
    1. mousePosition = new Vector3 (mousePosition.x, mousePosition.y = 1, mousePosition.z);
    Now though it does follow mouse precisely but it shoot towards the ground so all I'm guessing I have to do is cast the plan from the player's Vector3.zero and not the floors. I'll post here when I figure out how to do that lol.

    (I'm going to raise the whole plane because setting mousePosition.y = 1 causes and offset from the mouse position.)
     
    Last edited: Aug 12, 2014
  16. Troas

    Troas

    Joined:
    Jan 26, 2013
    Posts:
    157
    Sorry to re-open this issue but I cannot solve this problem still.

    I've been hacking at it on/off for the last 3 weeks but with my limited knowledge of coding and how positioning works in virtual space I am having a hard time wrapping my head around a solution.

    Here's the problem:



    As you can see the ray is going through the ground. It perfectly follows the mouse but I am guessing the plane is being cast on the Global vector3.zero and not the player's zero position. I want it to cast a plane through the middle of a player so the ray will shoot straight out from the player to the mouse and not into the ground.

    I've tried everything, I thought maybe trying to "add 1 y-position" to it would solve it but it just ends up going wacky.

    Here's my code:

    Code (CSharp):
    1. public Vector3 mousePosition;
    2. private Plane zeroYPlane;
    3.  
    4.     void Awake () {
    5.  
    6.         ref_mainCamera = GameObject.FindGameObjectWithTag("MainCamera").camera;
    7.  
    8.         //This is the tool you need - plane
    9.     }
    10.  
    11.     void Update () {
    12.  
    13.         zeroYPlane = new Plane(Vector3.up, Vector3.zero);
    14.  
    15.         // Fire ray from Camera to mouse
    16.         Ray ray = ref_mainCamera.ScreenPointToRay(Input.mousePosition);
    17.        
    18.        
    19.         float _hitDistance;
    20.         //cast ray using zerpYplane to find out mouse position the ray intersects it
    21.         zeroYPlane.Raycast(ray, out _hitDistance);
    22.         mousePosition = ray.GetPoint(_hitDistance);
    23.  
    24.         mousePosition = new Vector3 (mousePosition.x, mousePosition.y = 0, mousePosition.z);
    25.         Debug.DrawLine (transform.position, mousePosition);
    26.  
    27. }
    I am at a loss of how to make it work like the one in this game:

     
  17. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    You actually don't need a plane for it. You can calculate everything with a bit of vector math, there are actually a few different ways to get started. My first thought:

    - Raycast from the ScreenPoint
    - determine the total y-difference between your ray's starting position and the raycast's hit position
    - determine the y-difference between your player's position and the hitpoint and substract it from the total y-difference of the directional vector (your ray)
    - now you can calculate the relative factor which you need in order to adjust the directional vector of your ray.
    - ray's starting position + the new vector = new world position

    The direction is the same, heading from the screen point to the hitpoint, but it's now adjusted so that the position you get will be at your mouse' position and parallel to the xz-plane. Of course this will always shoot in the xz direction, so if you're on ground that is not even (parallel to the xz-plane) you will still shoot 'horizontally'.
     
  18. Troas

    Troas

    Joined:
    Jan 26, 2013
    Posts:
    157

    Thanks this helps alot.

    To solve for inclined planes would I need to do something like this (so it shoots up/down any inclined plane):

    1. Create an empty game object at player's mid-section, at player's feet, and one that follows the "new world position"
    2. Use trigonometry between these three points to determine the angle every update
    3. If the angle is more/less than 0deg adjust the bullet to go along the angle from the player position so it now shoots horizontally relative to the plane

    Hopefully that makes sense.
     
  19. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    I have to think about that one first, here's one example for how it could be done without parallel shooting to 'inclined' planes. I hope it's not too confusing. Rays are shown in editor mode, with 'movePrefab' you can move an object to the target position for better visualization.

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class Targeting : MonoBehaviour
    5. {
    6.     private Vector3 mousePositionInWorld;
    7.     private Vector3 targetPos;
    8.     public GameObject prefab;
    9.     public bool movePrefab = false; // toggle in order to move an object to the position for visualization
    10.  
    11.     void Awake()
    12.     {
    13.         if (prefab)
    14.             prefab.layer = 2; // index of the raycast-ignore layer
    15.         else
    16.             Debug.Log("no prefab assigned");
    17.     }
    18.  
    19.     void Update()
    20.     {
    21.         Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
    22.         RaycastHit rayCastHit;
    23.         if (Physics.Raycast(ray, out rayCastHit, Mathf.Infinity))
    24.         {
    25.             mousePositionInWorld = rayCastHit.point;
    26.             CalculateTargetPosition(rayCastHit.point);
    27.             if(movePrefab && prefab)
    28.                 prefab.transform.position = targetPos;
    29.         }
    30.         Debug.DrawLine(transform.position, mousePositionInWorld, Color.green); // the original ray
    31.         Debug.DrawLine(transform.position, targetPos, Color.red);              // ray with new target position
    32.     }
    33.  
    34.     void CalculateTargetPosition(Vector3 hit)
    35.     {
    36.         Vector3 mouse_ScreenToWorld = Camera.main.ScreenToWorldPoint(Input.mousePosition);
    37.         float y_total = hit.y - mouse_ScreenToWorld.y;                          // calculating the total y difference
    38.         float newY = (y_total - (hit.y - transform.position.y));                // calculating the difference between hit.y and player's y position ...
    39.                                                                                 // ... and subtracting it from the total y difference
    40.         float factor = newY / y_total;                                          // multiplier in order to adjust the original length and reach the target position
    41.         targetPos = mouse_ScreenToWorld + ((hit - mouse_ScreenToWorld) * factor); // start of at the starting point and add the adjusted directional vector
    42.     }
    43. }
    *edit

    Just saw that the lines are messed up in this script, i'll try to make it look better.
     
    Last edited: Sep 12, 2014